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  }