github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-munin/lib/munin.go (about)

     1  package mpmunin
     2  
     3  import (
     4  	"bufio"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"regexp"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  
    17  	mp "github.com/mackerelio/go-mackerel-plugin"
    18  )
    19  
    20  type serviceEnvs map[string]string
    21  
    22  type services map[string]serviceEnvs
    23  
    24  // MuninMetric metric of munin
    25  type MuninMetric struct {
    26  	Label string
    27  	Type  string
    28  	Draw  string
    29  	Value string
    30  }
    31  
    32  // MuninPlugin mackerel plugin for munin
    33  type MuninPlugin struct {
    34  	PluginPath    string
    35  	PluginConfDir string
    36  	GraphTitle    string
    37  	GraphName     string
    38  	MuninMetrics  map[string](*MuninMetric)
    39  }
    40  
    41  var exp = map[string](*regexp.Regexp){}
    42  
    43  func getExp(expstr string) *(regexp.Regexp) {
    44  	if exp[expstr] == nil {
    45  		exp[expstr] = regexp.MustCompile(expstr)
    46  	}
    47  	return exp[expstr]
    48  }
    49  
    50  func getEnvSettingsReader(s *services, plg string, reader io.Reader) {
    51  	svc := ""
    52  	scanner := bufio.NewScanner(reader)
    53  
    54  	for scanner.Scan() {
    55  		line := strings.TrimRight(scanner.Text(), "\n")
    56  		line = getExp("([^\\\\])#.*?$").ReplaceAllString(line, "$1")
    57  		line = getExp("\\\\#").ReplaceAllString(line, "#")
    58  		line = getExp("(^\\s+|\\s+$)").ReplaceAllString(line, "")
    59  
    60  		svcM := getExp("^\\s*\\[([^\\]]*?)(\\*?)\\]\\s*$").FindStringSubmatch(line)
    61  		if svcM != nil {
    62  			if svcM[2] == "" && plg == svcM[1] { // perfect match
    63  				svc = svcM[1]
    64  			} else if svcM[2] == "*" && len(svcM[1]) <= len(plg) && plg[0:len(svcM[1])] == svcM[1] { // left-hand match
    65  				svc = svcM[1] + svcM[2]
    66  			} else {
    67  				svc = ""
    68  			}
    69  			continue
    70  		}
    71  
    72  		if svc == "" {
    73  			continue
    74  		}
    75  
    76  		envM := getExp("^\\s*env\\.(\\w+)\\s+(.+)$").FindStringSubmatch(line)
    77  		if envM != nil && svc != "" {
    78  			if (*s)[svc] == nil {
    79  				(*s)[svc] = serviceEnvs{}
    80  			}
    81  			(*s)[svc][envM[1]] = envM[2]
    82  		}
    83  	}
    84  }
    85  
    86  func getEnvSettingsFile(s *services, plg string, file string) {
    87  	fp, err := os.Open(file)
    88  	if err != nil {
    89  		return
    90  	}
    91  	defer fp.Close()
    92  
    93  	getEnvSettingsReader(s, plg, fp)
    94  }
    95  
    96  func compileEnvPairs(s *services, plg string) *map[string]string {
    97  	servenvs := *s
    98  
    99  	// ordered services
   100  	srvs := make([]string, 0, len(servenvs))
   101  	for k := range servenvs {
   102  		if k == plg {
   103  			continue
   104  		}
   105  		srvs = append(srvs, k)
   106  	}
   107  	sort.Strings(srvs)
   108  	if servenvs[plg] != nil {
   109  		srvs = append(srvs, plg)
   110  	}
   111  
   112  	// apply envs
   113  	envs := make(map[string]string)
   114  	for _, srv := range srvs {
   115  		for k, v := range servenvs[srv] {
   116  			envs[k] = v
   117  		}
   118  	}
   119  
   120  	return &envs
   121  }
   122  
   123  func setPluginEnvironments(plg string, confdir string) {
   124  	files, err := os.ReadDir(confdir)
   125  	if err != nil {
   126  		log.Fatalln(err)
   127  	}
   128  
   129  	filenames := make([]string, 0, len(files))
   130  	for _, f := range files {
   131  		if f.IsDir() {
   132  			continue
   133  		}
   134  		filenames = append(filenames, f.Name())
   135  	}
   136  	sort.Strings(filenames)
   137  
   138  	servenvs := make(services)
   139  	for _, f := range filenames {
   140  		getEnvSettingsFile(&servenvs, plg, path.Join(confdir, f))
   141  	}
   142  
   143  	for k, v := range *compileEnvPairs(&servenvs, plg) {
   144  		os.Setenv(k, v)
   145  	}
   146  }
   147  
   148  func parsePluginConfig(str string, m *map[string](*MuninMetric), title *string) {
   149  	for _, line := range strings.Split(str, "\n") {
   150  		lineM := getExp("^([^ ]+) +(.*)$").FindStringSubmatch(line)
   151  		if lineM == nil {
   152  			continue
   153  		}
   154  
   155  		if lineM[1] == "graph_title" {
   156  			(*title) = lineM[2]
   157  			continue
   158  		}
   159  
   160  		keyM := getExp("^([^\\.]+)\\.(.*)$").FindStringSubmatch(lineM[1])
   161  		if keyM == nil {
   162  			continue
   163  		}
   164  
   165  		if (*m)[keyM[1]] == nil {
   166  			(*m)[keyM[1]] = &(MuninMetric{})
   167  		}
   168  		met := (*m)[keyM[1]]
   169  
   170  		switch keyM[2] {
   171  		case "label":
   172  			met.Label = lineM[2]
   173  		case "type":
   174  			met.Type = lineM[2]
   175  		case "draw":
   176  			met.Draw = lineM[2]
   177  		}
   178  	}
   179  }
   180  
   181  func parsePluginVals(str string, m *map[string](*MuninMetric)) {
   182  	for _, line := range strings.Split(str, "\n") {
   183  		lineM := getExp("^([^ ]+) +(.*)$").FindStringSubmatch(line)
   184  		if lineM == nil {
   185  			continue
   186  		}
   187  
   188  		keyM := getExp("^([^\\.]+)\\.(.*)$").FindStringSubmatch(lineM[1])
   189  		if keyM == nil {
   190  			continue
   191  		}
   192  
   193  		if (*m)[keyM[1]] == nil {
   194  			(*m)[keyM[1]] = &(MuninMetric{})
   195  		}
   196  		met := (*m)[keyM[1]]
   197  
   198  		switch keyM[2] {
   199  		case "value":
   200  			met.Value = lineM[2]
   201  		}
   202  	}
   203  }
   204  
   205  func removeUselessMetrics(m *map[string](*MuninMetric)) {
   206  	// remove metrics which have an empty Value
   207  	for name, mmet := range *m {
   208  		if mmet.Value == "" {
   209  			delete(*m, name)
   210  		}
   211  	}
   212  }
   213  
   214  func (p *MuninPlugin) prepare() error {
   215  	var err error
   216  
   217  	if p.PluginConfDir != "" {
   218  		setPluginEnvironments(path.Base(p.PluginPath), p.PluginConfDir)
   219  	}
   220  
   221  	outConfig, err := exec.Command(p.PluginPath, "config").Output()
   222  	if err != nil {
   223  		return fmt.Errorf("%s: %s", err, outConfig)
   224  	}
   225  
   226  	outVals, err := exec.Command(p.PluginPath).Output()
   227  	if err != nil {
   228  		return fmt.Errorf("%s: %s", err, outVals)
   229  	}
   230  
   231  	p.MuninMetrics = make(map[string](*MuninMetric))
   232  	parsePluginConfig(string(outConfig), &p.MuninMetrics, &p.GraphTitle)
   233  	parsePluginVals(string(outVals), &p.MuninMetrics)
   234  	removeUselessMetrics(&p.MuninMetrics)
   235  
   236  	return nil
   237  }
   238  
   239  // FetchMetrics interface for mackerelplugin
   240  func (p MuninPlugin) FetchMetrics() (map[string]float64, error) {
   241  	stat := make(map[string]float64, len(p.MuninMetrics))
   242  	for name, mmet := range p.MuninMetrics {
   243  		parsed, err := strconv.ParseFloat(mmet.Value, 64)
   244  		if err != nil {
   245  			log.Printf("Failed to parse value of %s: %s", name, err)
   246  			continue
   247  		}
   248  
   249  		stat[name] = parsed
   250  	}
   251  
   252  	return stat, nil
   253  }
   254  
   255  // GraphDefinition interface for mackerelplugin
   256  func (p MuninPlugin) GraphDefinition() map[string]mp.Graphs {
   257  	metrics := make([]mp.Metrics, 0, len(p.MuninMetrics))
   258  	for name, mmet := range p.MuninMetrics {
   259  		met := mp.Metrics{Name: name}
   260  		if mmet.Label == "" {
   261  			met.Label = name
   262  		} else {
   263  			met.Label = mmet.Label
   264  		}
   265  		if mmet.Draw == "STACK" {
   266  			met.Stacked = true
   267  		}
   268  		switch mmet.Type {
   269  		case "COUNTER", "DERIVE", "ABSOLUTE":
   270  			met.Diff = true
   271  		}
   272  
   273  		metrics = append(metrics, met)
   274  	}
   275  
   276  	return map[string]mp.Graphs{p.GraphName: {
   277  		Label:   p.GraphTitle,
   278  		Unit:    "float",
   279  		Metrics: metrics,
   280  	}}
   281  }
   282  
   283  // Do the plugin
   284  func Do() {
   285  	optPluginPath := flag.String("plugin", "", "Munin plugin path")
   286  	optPluginConfDir := flag.String("plugin-conf-d", "", "Munin plugin-conf.d path")
   287  	optGraphName := flag.String("name", "", "Graph name")
   288  	optTempfile := flag.String("tempfile", "", "Temp file name")
   289  	flag.Parse()
   290  
   291  	var munin MuninPlugin
   292  	if *optPluginPath == "" {
   293  		log.Fatalln("Munin plugin path is required")
   294  	}
   295  	munin.PluginPath = *optPluginPath
   296  	munin.PluginConfDir = *optPluginConfDir
   297  	if *optGraphName == "" {
   298  		munin.GraphName = "munin." + path.Base(munin.PluginPath)
   299  	} else {
   300  		munin.GraphName = *optGraphName
   301  	}
   302  
   303  	err := munin.prepare()
   304  	if err != nil {
   305  		log.Fatalln(err)
   306  	}
   307  
   308  	helper := mp.NewMackerelPlugin(munin)
   309  	if *optTempfile != "" {
   310  		helper.Tempfile = *optTempfile
   311  	} else {
   312  		helper.SetTempfileByBasename(fmt.Sprintf("mackerel-plugin-munin-%s", munin.GraphName))
   313  	}
   314  
   315  	helper.Run()
   316  }