github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-h2o/lib/h2o.go (about) 1 package mph2o 2 3 import ( 4 "encoding/json" 5 "errors" 6 "flag" 7 "fmt" 8 "io" 9 "net/http" 10 "strings" 11 12 mp "github.com/mackerelio/go-mackerel-plugin" 13 ) 14 15 const prefix = "h2o" 16 17 var graphdef = map[string]mp.Graphs{ 18 "uptime": { 19 Label: "H2O Uptime", 20 Unit: mp.UnitFloat, 21 Metrics: []mp.Metrics{ 22 {Name: "uptime", Label: "Seconds"}, 23 }, 24 }, 25 "connections": { 26 Label: "H2O Connections", 27 Unit: mp.UnitInteger, 28 Metrics: []mp.Metrics{ 29 {Name: "connections", Label: "Active connections"}, 30 {Name: "max_connections", Label: "Max connections"}, 31 }, 32 }, 33 "listeners": { 34 Label: "H2O Listeners", 35 Unit: mp.UnitInteger, 36 Metrics: []mp.Metrics{ 37 {Name: "listeners", Label: "Listeners"}, 38 }, 39 }, 40 "worker_threads": { 41 Label: "H2O Worker Threads", 42 Unit: mp.UnitInteger, 43 Metrics: []mp.Metrics{ 44 {Name: "worker_threads", Label: "Worker Threads"}, 45 }, 46 }, 47 "num_sessions": { 48 Label: "H2O Sessions", 49 Unit: mp.UnitInteger, 50 Metrics: []mp.Metrics{ 51 {Name: "num_sessions", Label: "Sessions"}, 52 }, 53 }, 54 "requests": { 55 Label: "H2O Requests", 56 Unit: mp.UnitInteger, 57 Metrics: []mp.Metrics{ 58 {Name: "requests", Label: "In-flight Requests"}, 59 }, 60 }, 61 "status_errors": { 62 Label: "H2O Status Errors", 63 Unit: mp.UnitInteger, 64 Metrics: []mp.Metrics{ 65 {Name: "status_errors_503", Label: "Error 503", Diff: true}, 66 {Name: "status_errors_502", Label: "Error 502", Diff: true}, 67 {Name: "status_errors_500", Label: "Error 500", Diff: true}, 68 {Name: "status_errors_417", Label: "Error 417", Diff: true}, 69 {Name: "status_errors_416", Label: "Error 416", Diff: true}, 70 {Name: "status_errors_405", Label: "Error 405", Diff: true}, 71 {Name: "status_errors_404", Label: "Error 404", Diff: true}, 72 {Name: "status_errors_403", Label: "Error 403", Diff: true}, 73 {Name: "status_errors_400", Label: "Error 400", Diff: true}, 74 }, 75 }, 76 "http2_errors": { 77 Label: "H2O HTTP2 Errors", 78 Unit: mp.UnitInteger, 79 Metrics: []mp.Metrics{ 80 {Name: "http2_errors_protocol", Label: "Error protocol", Diff: true}, 81 {Name: "http2_errors_internal", Label: "Error internal", Diff: true}, 82 {Name: "http2_errors_flow_control", Label: "Error Flow Control", Diff: true}, 83 {Name: "http2_errors_settings_timeout", Label: "Error Setting Timeout", Diff: true}, 84 {Name: "http2_errors_frame_size", Label: "Error Frame Size", Diff: true}, 85 {Name: "http2_errors_refused_stream", Label: "Error Refused Stream", Diff: true}, 86 {Name: "http2_errors_cancel", Label: "Error Cancel", Diff: true}, 87 {Name: "http2_errors_compression", Label: "Error Compression", Diff: true}, 88 {Name: "http2_errors_connect", Label: "Error Connect", Diff: true}, 89 {Name: "http2_errors_enhance_your_calm", Label: "Error Enhance Your Calm", Diff: true}, 90 {Name: "http2_errors_inadequate_security", Label: "Error Inadequate Security", Diff: true}, 91 }, 92 }, 93 "read_closed": { 94 Label: "H2O Read Closed", 95 Unit: mp.UnitInteger, 96 Metrics: []mp.Metrics{ 97 {Name: "http2_read_closed", Label: "Read Closed", Diff: true}, 98 }, 99 }, 100 "write_closed": { 101 Label: "H2O Write Closed", 102 Unit: mp.UnitInteger, 103 Metrics: []mp.Metrics{ 104 {Name: "http2_write_closed", Label: "Write Closed", Diff: true}, 105 }, 106 }, 107 "connect_time": { 108 Label: "H2O Connect Time", 109 Unit: mp.UnitFloat, 110 Metrics: []mp.Metrics{ 111 {Name: "connect_time_99", Label: "99 Percentile"}, 112 {Name: "connect_time_75", Label: "75 Percentile"}, 113 {Name: "connect_time_50", Label: "50 Percentile"}, 114 {Name: "connect_time_25", Label: "25 Percentile"}, 115 {Name: "connect_time_0", Label: "0 Percentile"}, 116 }, 117 }, 118 "header_time": { 119 Label: "H2O Header Time", 120 Unit: mp.UnitFloat, 121 Metrics: []mp.Metrics{ 122 {Name: "header_time_99", Label: "99 Percentile"}, 123 {Name: "header_time_75", Label: "75 Percentile"}, 124 {Name: "header_time_50", Label: "50 Percentile"}, 125 {Name: "header_time_25", Label: "25 Percentile"}, 126 {Name: "header_time_0", Label: "0 Percentile"}, 127 }, 128 }, 129 "body_time": { 130 Label: "H2O Body Time", 131 Unit: mp.UnitFloat, 132 Metrics: []mp.Metrics{ 133 {Name: "body_time_99", Label: "99 Percentile"}, 134 {Name: "body_time_75", Label: "75 Percentile"}, 135 {Name: "body_time_50", Label: "50 Percentile"}, 136 {Name: "body_time_25", Label: "25 Percentile"}, 137 {Name: "body_time_0", Label: "0 Percentile"}, 138 }, 139 }, 140 "request_total_time": { 141 Label: "H2O Request Total Time", 142 Unit: mp.UnitFloat, 143 Metrics: []mp.Metrics{ 144 {Name: "request_total_time_99", Label: "99 Percentile"}, 145 {Name: "request_total_time_75", Label: "75 Percentile"}, 146 {Name: "request_total_time_50", Label: "50 Percentile"}, 147 {Name: "request_total_time_25", Label: "25 Percentile"}, 148 {Name: "request_total_time_0", Label: "0 Percentile"}, 149 }, 150 }, 151 "process_time": { 152 Label: "H2O Process Time", 153 Unit: mp.UnitFloat, 154 Metrics: []mp.Metrics{ 155 {Name: "process_time_99", Label: "99 Percentile"}, 156 {Name: "process_time_75", Label: "75 Percentile"}, 157 {Name: "process_time_50", Label: "50 Percentile"}, 158 {Name: "process_time_25", Label: "25 Percentile"}, 159 {Name: "process_time_0", Label: "0 Percentile"}, 160 }, 161 }, 162 "response_time": { 163 Label: "H2O Response Time", 164 Unit: mp.UnitFloat, 165 Metrics: []mp.Metrics{ 166 {Name: "response_time_99", Label: "99 Percentile"}, 167 {Name: "response_time_75", Label: "75 Percentile"}, 168 {Name: "response_time_50", Label: "50 Percentile"}, 169 {Name: "response_time_25", Label: "25 Percentile"}, 170 {Name: "response_time_0", Label: "0 Percentile"}, 171 }, 172 }, 173 "duration": { 174 Label: "H2O Duration", 175 Unit: mp.UnitFloat, 176 Metrics: []mp.Metrics{ 177 {Name: "duration_99", Label: "99 Percentile"}, 178 {Name: "duration_75", Label: "75 Percentile"}, 179 {Name: "duration_50", Label: "50 Percentile"}, 180 {Name: "duration_25", Label: "25 Percentile"}, 181 {Name: "duration_0", Label: "0 Percentile"}, 182 }, 183 }, 184 } 185 186 type stringSlice []string 187 188 func (s *stringSlice) Set(v string) error { 189 *s = append(*s, v) 190 return nil 191 } 192 193 func (s *stringSlice) String() string { 194 return fmt.Sprintf("%v", *s) 195 } 196 197 // H2OPlugin mackerel plugin for H2O 198 type H2OPlugin struct { 199 Prefix string 200 URI string 201 Header stringSlice 202 } 203 204 // MetricKeyPrefix interface for mackerelplugin 205 func (h2o H2OPlugin) MetricKeyPrefix() string { 206 if h2o.Prefix == "" { 207 h2o.Prefix = prefix 208 } 209 return h2o.Prefix 210 } 211 212 // GraphDefinition interface for mackerelplugin 213 func (h2o H2OPlugin) GraphDefinition() map[string]mp.Graphs { 214 return graphdef 215 } 216 217 // FetchMetrics interface for mackerelplugin 218 func (h2o H2OPlugin) FetchMetrics() (map[string]float64, error) { 219 req, err := http.NewRequest("GET", h2o.URI, nil) 220 if err != nil { 221 return nil, err 222 } 223 for _, h := range h2o.Header { 224 kv := strings.SplitN(h, ":", 2) 225 var k, v string 226 k = strings.TrimSpace(kv[0]) 227 if len(kv) == 2 { 228 v = strings.TrimSpace(kv[1]) 229 } 230 if http.CanonicalHeaderKey(k) == "Host" { 231 req.Host = v 232 } else { 233 req.Header.Set(k, v) 234 } 235 } 236 // set default User-Agent unless specified by h2o.Header 237 if _, ok := req.Header["User-Agent"]; !ok { 238 req.Header.Set("User-Agent", "mackerel-plugin-h2o") 239 } 240 241 resp, err := http.DefaultClient.Do(req) 242 if err != nil { 243 return nil, err 244 } 245 defer resp.Body.Close() 246 247 return h2o.parseStats(resp.Body) 248 } 249 250 func (h2o H2OPlugin) parseStats(body io.Reader) (map[string]float64, error) { 251 stat := make(map[string]interface{}) 252 metrics := make(map[string]float64) 253 254 b, err := io.ReadAll(body) 255 if err != nil { 256 return nil, err 257 } 258 259 err = json.Unmarshal(b, &stat) 260 if err != nil { 261 return nil, err 262 } 263 264 r := strings.NewReplacer(".", "_", "-", "_") 265 266 for k, v := range stat { 267 switch k { 268 case "server-version", "openssl-version", "current-time", "restart-time", "generation": 269 case "requests": 270 requests, ok := stat["requests"].([]interface{}) 271 if !ok { 272 return nil, errors.New("cannot get \"requests\" value") 273 } 274 metrics["requests"] = float64(len(requests)) 275 default: 276 f, ok := v.(float64) 277 if !ok { 278 return nil, fmt.Errorf("cannot get %q value", k) 279 } 280 metrics[r.Replace(k)] = f 281 } 282 } 283 284 return metrics, nil 285 } 286 287 // Do the plugin 288 func Do() { 289 optURI := flag.String("uri", "", "URI") 290 optScheme := flag.String("scheme", "http", "Scheme") 291 optHost := flag.String("host", "localhost", "Hostname") 292 optPort := flag.String("port", "8080", "Port") 293 optPath := flag.String("path", "/server-status/json", "Path") 294 optTempfile := flag.String("tempfile", "", "Temp file name") 295 optPrefix := flag.String("metric-key-prefix", prefix, "Metric key prefix") 296 optHeader := &stringSlice{} 297 flag.Var(optHeader, "header", "Set http header (e.g. \"Host: servername\")") 298 flag.Parse() 299 300 var h2o = H2OPlugin{ 301 Prefix: *optPrefix, 302 Header: *optHeader, 303 } 304 if *optURI != "" { 305 h2o.URI = *optURI 306 } else { 307 h2o.URI = fmt.Sprintf("%s://%s:%s%s", *optScheme, *optHost, *optPort, *optPath) 308 } 309 310 helper := mp.NewMackerelPlugin(h2o) 311 helper.Tempfile = *optTempfile 312 helper.Run() 313 }