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

     1  package mpplack
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strconv"
    10  
    11  	mp "github.com/mackerelio/go-mackerel-plugin-helper"
    12  	"golang.org/x/text/cases"
    13  	"golang.org/x/text/language"
    14  )
    15  
    16  // PlackPlugin mackerel plugin for Plack
    17  type PlackPlugin struct {
    18  	URI         string
    19  	Prefix      string
    20  	LabelPrefix string
    21  }
    22  
    23  // {
    24  //   "Uptime": "1410520211",
    25  //   "TotalAccesses": "2",
    26  //   "IdleWorkers": "2",
    27  //   "TotalKbytes": "0",
    28  //   "BusyWorkers": "1",
    29  //   "stats": [
    30  //     {
    31  //       "pid": 11062,
    32  //       "method": "GET",
    33  //       "ss": 51,
    34  //       "remote_addr": "127.0.0.1",
    35  //       "host": "localhost:8000",
    36  //       "protocol": "HTTP/1.1",
    37  //       "status": "_",
    38  //       "uri": "/server-status?json"
    39  //     },
    40  //     {
    41  //       "ss": 41,
    42  //       "remote_addr": "127.0.0.1",
    43  //       "host": "localhost:8000",
    44  //       "protocol": "HTTP/1.1",
    45  //       "pid": 11063,
    46  //       "method": "GET",
    47  //       "status": "_",
    48  //       "uri": "/server-status?json"
    49  //     },
    50  //     {
    51  //       "ss": 0,
    52  //       "remote_addr": "127.0.0.1",
    53  //       "host": "localhost:8000",
    54  //       "protocol": "HTTP/1.1",
    55  //       "pid": 11064,
    56  //       "method": "GET",
    57  //       "status": "A",
    58  //       "uri": "/server-status?json"
    59  //     }
    60  //   ]
    61  // }
    62  
    63  // field types vary between versions
    64  
    65  // PlackRequest request
    66  type PlackRequest struct{}
    67  
    68  // PlackServerStatus sturct for server-status's json
    69  type PlackServerStatus struct {
    70  	Uptime        interface{}    `json:"Uptime"` // Plack::Middleware::ServerStatus::Lite 0.35 outputs Uptime as a JSON number, though pre-0.35 outputs it as a JSON string.
    71  	TotalAccesses interface{}    `json:"TotalAccesses"`
    72  	TotalKbytes   interface{}    `json:"TotalKbytes"`
    73  	BusyWorkers   interface{}    `json:"BusyWorkers"`
    74  	IdleWorkers   interface{}    `json:"IdleWorkers"`
    75  	Stats         []PlackRequest `json:"stats"`
    76  }
    77  
    78  // FetchMetrics interface for mackerelplugin
    79  func (p PlackPlugin) FetchMetrics() (map[string]interface{}, error) {
    80  	req, err := http.NewRequest(http.MethodGet, p.URI, nil)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	req.Header.Set("User-Agent", "mackerel-plugin-plack")
    85  
    86  	resp, err := http.DefaultClient.Do(req)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	defer resp.Body.Close()
    91  
    92  	return p.parseStats(resp.Body)
    93  }
    94  
    95  func parseNumber(s interface{}) (float64, error) {
    96  	switch v := s.(type) {
    97  	case string:
    98  		if f, err := strconv.ParseFloat(v, 64); err == nil {
    99  			return f, nil
   100  		}
   101  	case float64:
   102  		return v, nil
   103  	}
   104  	return 0, fmt.Errorf("failed to parse %v as Number", s)
   105  }
   106  
   107  func (p PlackPlugin) parseStats(body io.Reader) (map[string]interface{}, error) {
   108  	stat := make(map[string]interface{})
   109  	decoder := json.NewDecoder(body)
   110  
   111  	var s PlackServerStatus
   112  	err := decoder.Decode(&s)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	if s, err := parseNumber(s.BusyWorkers); err == nil {
   118  		stat["busy_workers"] = s
   119  	}
   120  
   121  	if s, err := parseNumber(s.IdleWorkers); err == nil {
   122  		stat["idle_workers"] = s
   123  	}
   124  
   125  	if s, err := parseNumber(s.TotalAccesses); err == nil {
   126  		stat["requests"] = uint64(s)
   127  	}
   128  
   129  	if s, err := parseNumber(s.TotalKbytes); err == nil {
   130  		stat["bytes_sent"] = uint64(s)
   131  	}
   132  
   133  	return stat, nil
   134  }
   135  
   136  // GraphDefinition interface for mackerelplugin
   137  func (p PlackPlugin) GraphDefinition() map[string]mp.Graphs {
   138  	var graphdef = map[string]mp.Graphs{
   139  		(p.Prefix + ".workers"): {
   140  			Label: p.LabelPrefix + " Workers",
   141  			Unit:  "integer",
   142  			Metrics: []mp.Metrics{
   143  				{Name: "busy_workers", Label: "Busy Workers", Diff: false, Stacked: true},
   144  				{Name: "idle_workers", Label: "Idle Workers", Diff: false, Stacked: true},
   145  			},
   146  		},
   147  		(p.Prefix + ".req"): {
   148  			Label: p.LabelPrefix + " Requests",
   149  			Unit:  "integer",
   150  			Metrics: []mp.Metrics{
   151  				{Name: "requests", Label: "Requests", Diff: true, Type: "uint64"},
   152  			},
   153  		},
   154  		(p.Prefix + ".bytes"): {
   155  			Label: p.LabelPrefix + " Bytes",
   156  			Unit:  "integer",
   157  			Metrics: []mp.Metrics{
   158  				{Name: "bytes_sent", Label: "Bytes Sent", Diff: true, Type: "uint64"},
   159  			},
   160  		},
   161  	}
   162  
   163  	return graphdef
   164  }
   165  
   166  // Do the plugin
   167  func Do() {
   168  	optURI := flag.String("uri", "", "URI")
   169  	optScheme := flag.String("scheme", "http", "Scheme")
   170  	optHost := flag.String("host", "localhost", "Hostname")
   171  	optPort := flag.String("port", "5000", "Port")
   172  	optPath := flag.String("path", "/server-status?json", "Path")
   173  	optPrefix := flag.String("metric-key-prefix", "plack", "Prefix")
   174  	optLabelPrefix := flag.String("metric-label-prefix", "", "Label Prefix")
   175  	optTempfile := flag.String("tempfile", "", "Temp file name")
   176  	flag.Parse()
   177  
   178  	plack := PlackPlugin{URI: *optURI, Prefix: *optPrefix, LabelPrefix: *optLabelPrefix}
   179  	if plack.URI == "" {
   180  		plack.URI = fmt.Sprintf("%s://%s:%s%s", *optScheme, *optHost, *optPort, *optPath)
   181  	}
   182  	if plack.LabelPrefix == "" {
   183  		plack.LabelPrefix = cases.Title(language.Und, cases.NoLower).String(plack.Prefix)
   184  	}
   185  
   186  	helper := mp.NewMackerelPlugin(plack)
   187  	helper.Tempfile = *optTempfile
   188  
   189  	helper.Run()
   190  }