github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-apache2/lib/apache2.go (about) 1 package mpapache2 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "log" 8 "net/http" 9 "os" 10 "regexp" 11 "strconv" 12 "strings" 13 14 mp "github.com/mackerelio/go-mackerel-plugin-helper" 15 "github.com/urfave/cli" 16 ) 17 18 // Apache2Plugin for fetching metrics 19 type Apache2Plugin struct { 20 Host string 21 Port uint16 22 Path string 23 Header []string 24 Tempfile string 25 Prefix string 26 LabelPrefix string 27 } 28 29 // MetricKeyPrefix interface for PluginWithPrefix 30 func (c Apache2Plugin) MetricKeyPrefix() string { 31 if c.Prefix == "" { 32 c.Prefix = "apache2" 33 } 34 return c.Prefix 35 } 36 37 // GraphDefinition Graph definition 38 func (c Apache2Plugin) GraphDefinition() map[string]mp.Graphs { 39 labelPrefix := c.LabelPrefix 40 41 // metric value structure 42 var graphdef = map[string]mp.Graphs{ 43 "workers": { 44 Label: (labelPrefix + " Workers"), 45 Unit: "integer", 46 Metrics: []mp.Metrics{ 47 {Name: "busy_workers", Label: "Busy Workers", Diff: false, Stacked: true}, 48 {Name: "idle_workers", Label: "Idle Workers", Diff: false, Stacked: true}, 49 }, 50 }, 51 "bytes": { 52 Label: (labelPrefix + " Bytes"), 53 Unit: "bytes", 54 Metrics: []mp.Metrics{ 55 {Name: "bytes_sent", Label: "Bytes Sent", Diff: true, Type: "uint64"}, 56 }, 57 }, 58 "cpu": { 59 Label: (labelPrefix + " CPU Load"), 60 Unit: "float", 61 Metrics: []mp.Metrics{ 62 {Name: "cpu_load", Label: "CPU Load", Diff: false}, 63 }, 64 }, 65 "req": { 66 Label: (labelPrefix + " Requests"), 67 Unit: "integer", 68 Metrics: []mp.Metrics{ 69 {Name: "requests", Label: "Requests", Diff: true, Type: "uint64"}, 70 }, 71 }, 72 "scoreboard": { 73 Label: (labelPrefix + " Scoreboard"), 74 Unit: "integer", 75 Metrics: []mp.Metrics{ 76 {Name: "score-_", Label: "Waiting for connection", Diff: false, Stacked: true}, 77 {Name: "score-S", Label: "Starting up", Diff: false, Stacked: true}, 78 {Name: "score-R", Label: "Reading request", Diff: false, Stacked: true}, 79 {Name: "score-W", Label: "Sending reply", Diff: false, Stacked: true}, 80 {Name: "score-K", Label: "Keepalive", Diff: false, Stacked: true}, 81 {Name: "score-D", Label: "DNS lookup", Diff: false, Stacked: true}, 82 {Name: "score-C", Label: "Closing connection", Diff: false, Stacked: true}, 83 {Name: "score-L", Label: "Logging", Diff: false, Stacked: true}, 84 {Name: "score-G", Label: "Gracefully finishing", Diff: false, Stacked: true}, 85 {Name: "score-I", Label: "Idle cleanup", Diff: false, Stacked: true}, 86 {Name: "score-", Label: "Open slot", Diff: false, Stacked: true}, 87 }, 88 }, 89 } 90 return graphdef 91 } 92 93 // main function 94 func doMain(c *cli.Context) error { 95 96 var apache2 Apache2Plugin 97 98 apache2.Host = c.String("http_host") 99 apache2.Port = uint16(c.Int("http_port")) 100 apache2.Path = c.String("status_page") 101 apache2.Header = c.StringSlice("header") 102 apache2.Prefix = c.String("metric-key-prefix") 103 apache2.LabelPrefix = c.String("metric-label-prefix") 104 105 helper := mp.NewMackerelPlugin(apache2) 106 helper.Tempfile = c.String("tempfile") 107 108 helper.Run() 109 return nil 110 } 111 112 // FetchMetrics fetch the metrics 113 func (c Apache2Plugin) FetchMetrics() (map[string]interface{}, error) { 114 data, err := getApache2Metrics(c.Host, c.Port, c.Path, c.Header) 115 if err != nil { 116 return nil, err 117 } 118 119 stat := make(map[string]interface{}) 120 errStat := parseApache2Status(data, &stat) 121 if errStat != nil { 122 return nil, errStat 123 } 124 errScore := parseApache2Scoreboard(data, &stat) 125 if errScore != nil { 126 return nil, errScore 127 } 128 129 return stat, nil 130 } 131 132 var scoreboardLine = regexp.MustCompile("Scoreboard(.*)") 133 134 // parsing scoreboard from server-status?auto 135 func parseApache2Scoreboard(str string, p *map[string]interface{}) error { 136 for _, line := range strings.Split(str, "\n") { 137 if !scoreboardLine.MatchString(line) { 138 continue 139 } 140 record := strings.Split(line, ":") 141 for _, sb := range strings.Split(strings.Trim(record[1], " "), "") { 142 if sb == "." { 143 sb = "" 144 } 145 name := fmt.Sprintf("score-%s", sb) 146 c, assert := (*p)[name].(float64) 147 if !assert { 148 c = 0.0 149 } 150 (*p)[name] = c + 1.0 151 } 152 return nil 153 } 154 155 return errors.New("scoreboard data is not found") 156 } 157 158 // parsing metrics from server-status?auto 159 func parseApache2Status(str string, p *map[string]interface{}) error { 160 Params := map[string]string{ 161 "Total Accesses": "requests", 162 "Total kBytes": "bytes_sent", 163 "CPULoad": "cpu_load", 164 "BusyWorkers": "busy_workers", 165 "IdleWorkers": "idle_workers"} 166 167 for _, line := range strings.Split(str, "\n") { 168 record := strings.Split(line, ":") 169 _, assert := Params[record[0]] 170 if !assert { 171 continue 172 } 173 var errParse error 174 (*p)[Params[record[0]]], errParse = strconv.ParseFloat(strings.Trim(record[1], " "), 64) 175 if errParse != nil { 176 return errParse 177 } 178 } 179 180 if len(*p) == 0 { 181 return errors.New("status data not found") 182 } 183 184 return nil 185 } 186 187 // Getting apache2 status from server-status module data. 188 func getApache2Metrics(host string, port uint16, path string, header []string) (string, error) { 189 uri := "http://" + host + ":" + strconv.FormatUint(uint64(port), 10) + path 190 req, err := http.NewRequest("GET", uri, nil) 191 if err != nil { 192 return "", err 193 } 194 for _, h := range header { 195 kv := strings.SplitN(h, ":", 2) 196 var k, v string 197 k = strings.TrimSpace(kv[0]) 198 if len(kv) == 2 { 199 v = strings.TrimSpace(kv[1]) 200 } 201 if http.CanonicalHeaderKey(k) == "Host" { 202 req.Host = v 203 } else { 204 req.Header.Set(k, v) 205 } 206 } 207 208 // set default User-Agent unless specified by header 209 if _, ok := req.Header["User-Agent"]; !ok { 210 req.Header.Set("User-Agent", "mackerel-plugin-apache2") 211 } 212 213 resp, err := http.DefaultClient.Do(req) 214 if err != nil { 215 return "", err 216 } 217 defer resp.Body.Close() 218 if resp.StatusCode != http.StatusOK { 219 return "", fmt.Errorf("HTTP status error: %d", resp.StatusCode) 220 } 221 body, err := io.ReadAll(resp.Body) 222 if err != nil { 223 return "", err 224 } 225 return string(body[:]), nil 226 } 227 228 // Do the plugin 229 func Do() { 230 app := cli.NewApp() 231 app.Name = "apache2_metrics" 232 app.Usage = "Get metrics from apache2." 233 app.Author = "Yuichiro Saito" 234 app.Email = "saito@heartbeats.jp" 235 app.Flags = flags 236 app.Action = doMain 237 238 err := app.Run(os.Args) 239 if err != nil { 240 log.Fatalln(err) 241 } 242 }