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 }