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 }