1package main 2 3import ( 4 "crypto/tls" 5 "encoding/json" 6 "flag" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "log" 11 "net/http" 12 "os" 13 "time" 14 15 "github.com/prometheus/client_golang/prometheus" 16 "github.com/prometheus/client_golang/prometheus/promhttp" 17 "github.com/prometheus/common/version" 18) 19 20type NginxVts struct { 21 HostName string `json:"hostName"` 22 NginxVersion string `json:"nginxVersion"` 23 LoadMsec int64 `json:"loadMsec"` 24 NowMsec int64 `json:"nowMsec"` 25 Connections struct { 26 Active uint64 `json:"active"` 27 Reading uint64 `json:"reading"` 28 Writing uint64 `json:"writing"` 29 Waiting uint64 `json:"waiting"` 30 Accepted uint64 `json:"accepted"` 31 Handled uint64 `json:"handled"` 32 Requests uint64 `json:"requests"` 33 } `json:"connections"` 34 ServerZones map[string]Server `json:"serverZones"` 35 UpstreamZones map[string][]Upstream `json:"upstreamZones"` 36 FilterZones map[string]map[string]Upstream `json:"filterZones"` 37 CacheZones map[string]Cache `json:"cacheZones"` 38} 39 40type Server struct { 41 RequestCounter uint64 `json:"requestCounter"` 42 InBytes uint64 `json:"inBytes"` 43 OutBytes uint64 `json:"outBytes"` 44 RequestMsec uint64 `json:"requestMsec"` 45 Responses struct { 46 OneXx uint64 `json:"1xx"` 47 TwoXx uint64 `json:"2xx"` 48 ThreeXx uint64 `json:"3xx"` 49 FourXx uint64 `json:"4xx"` 50 FiveXx uint64 `json:"5xx"` 51 Miss uint64 `json:"miss"` 52 Bypass uint64 `json:"bypass"` 53 Expired uint64 `json:"expired"` 54 Stale uint64 `json:"stale"` 55 Updating uint64 `json:"updating"` 56 Revalidated uint64 `json:"revalidated"` 57 Hit uint64 `json:"hit"` 58 Scarce uint64 `json:"scarce"` 59 } `json:"responses"` 60 OverCounts struct { 61 MaxIntegerSize float64 `json:"maxIntegerSize"` 62 RequestCounter uint64 `json:"requestCounter"` 63 InBytes uint64 `json:"inBytes"` 64 OutBytes uint64 `json:"outBytes"` 65 OneXx uint64 `json:"1xx"` 66 TwoXx uint64 `json:"2xx"` 67 ThreeXx uint64 `json:"3xx"` 68 FourXx uint64 `json:"4xx"` 69 FiveXx uint64 `json:"5xx"` 70 Miss uint64 `json:"miss"` 71 Bypass uint64 `json:"bypass"` 72 Expired uint64 `json:"expired"` 73 Stale uint64 `json:"stale"` 74 Updating uint64 `json:"updating"` 75 Revalidated uint64 `json:"revalidated"` 76 Hit uint64 `json:"hit"` 77 Scarce uint64 `json:"scarce"` 78 } `json:"overCounts"` 79} 80 81type Upstream struct { 82 Server string `json:"server"` 83 RequestCounter uint64 `json:"requestCounter"` 84 InBytes uint64 `json:"inBytes"` 85 OutBytes uint64 `json:"outBytes"` 86 Responses struct { 87 OneXx uint64 `json:"1xx"` 88 TwoXx uint64 `json:"2xx"` 89 ThreeXx uint64 `json:"3xx"` 90 FourXx uint64 `json:"4xx"` 91 FiveXx uint64 `json:"5xx"` 92 } `json:"responses"` 93 ResponseMsec uint64 `json:"responseMsec"` 94 RequestMsec uint64 `json:"requestMsec"` 95 Weight uint64 `json:"weight"` 96 MaxFails uint64 `json:"maxFails"` 97 FailTimeout uint64 `json:"failTimeout"` 98 Backup bool `json:"backup"` 99 Down bool `json:"down"` 100 OverCounts struct { 101 MaxIntegerSize float64 `json:"maxIntegerSize"` 102 RequestCounter uint64 `json:"requestCounter"` 103 InBytes uint64 `json:"inBytes"` 104 OutBytes uint64 `json:"outBytes"` 105 OneXx uint64 `json:"1xx"` 106 TwoXx uint64 `json:"2xx"` 107 ThreeXx uint64 `json:"3xx"` 108 FourXx uint64 `json:"4xx"` 109 FiveXx uint64 `json:"5xx"` 110 } `json:"overCounts"` 111} 112 113type Cache struct { 114 MaxSize uint64 `json:"maxSize"` 115 UsedSize uint64 `json:"usedSize"` 116 InBytes uint64 `json:"inBytes"` 117 OutBytes uint64 `json:"outBytes"` 118 Responses struct { 119 Miss uint64 `json:"miss"` 120 Bypass uint64 `json:"bypass"` 121 Expired uint64 `json:"expired"` 122 Stale uint64 `json:"stale"` 123 Updating uint64 `json:"updating"` 124 Revalidated uint64 `json:"revalidated"` 125 Hit uint64 `json:"hit"` 126 Scarce uint64 `json:"scarce"` 127 } `json:"responses"` 128 OverCounts struct { 129 MaxIntegerSize float64 `json:"maxIntegerSize"` 130 InBytes uint64 `json:"inBytes"` 131 OutBytes uint64 `json:"outBytes"` 132 Miss uint64 `json:"miss"` 133 Bypass uint64 `json:"bypass"` 134 Expired uint64 `json:"expired"` 135 Stale uint64 `json:"stale"` 136 Updating uint64 `json:"updating"` 137 Revalidated uint64 `json:"revalidated"` 138 Hit uint64 `json:"hit"` 139 Scarce uint64 `json:"scarce"` 140 } `json:"overCounts"` 141} 142 143type Exporter struct { 144 URI string 145 146 infoMetric *prometheus.Desc 147 serverMetrics, upstreamMetrics, filterMetrics, cacheMetrics map[string]*prometheus.Desc 148} 149 150func newServerMetric(metricName string, docString string, labels []string) *prometheus.Desc { 151 return prometheus.NewDesc( 152 prometheus.BuildFQName(*metricsNamespace, "server", metricName), 153 docString, labels, nil, 154 ) 155} 156 157func newUpstreamMetric(metricName string, docString string, labels []string) *prometheus.Desc { 158 return prometheus.NewDesc( 159 prometheus.BuildFQName(*metricsNamespace, "upstream", metricName), 160 docString, labels, nil, 161 ) 162} 163 164func newFilterMetric(metricName string, docString string, labels []string) *prometheus.Desc { 165 return prometheus.NewDesc( 166 prometheus.BuildFQName(*metricsNamespace, "filter", metricName), 167 docString, labels, nil, 168 ) 169} 170 171func newCacheMetric(metricName string, docString string, labels []string) *prometheus.Desc { 172 return prometheus.NewDesc( 173 prometheus.BuildFQName(*metricsNamespace, "cache", metricName), 174 docString, labels, nil, 175 ) 176} 177 178func NewExporter(uri string) *Exporter { 179 return &Exporter{ 180 URI: uri, 181 infoMetric: newServerMetric("info", "nginx info", []string{"hostName", "nginxVersion"}), 182 serverMetrics: map[string]*prometheus.Desc{ 183 "connections": newServerMetric("connections", "nginx connections", []string{"status"}), 184 "requests": newServerMetric("requests", "requests counter", []string{"host", "code"}), 185 "bytes": newServerMetric("bytes", "request/response bytes", []string{"host", "direction"}), 186 "cache": newServerMetric("cache", "cache counter", []string{"host", "status"}), 187 "requestMsec": newServerMetric("requestMsec", "average of request processing times in milliseconds", []string{"host"}), 188 }, 189 upstreamMetrics: map[string]*prometheus.Desc{ 190 "requests": newUpstreamMetric("requests", "requests counter", []string{"upstream", "code", "backend"}), 191 "bytes": newUpstreamMetric("bytes", "request/response bytes", []string{"upstream", "direction", "backend"}), 192 "responseMsec": newUpstreamMetric("responseMsec", "average of only upstream/backend response processing times in milliseconds", []string{"upstream", "backend"}), 193 "requestMsec": newUpstreamMetric("requestMsec", "average of request processing times in milliseconds", []string{"upstream", "backend"}), 194 }, 195 filterMetrics: map[string]*prometheus.Desc{ 196 "requests": newFilterMetric("requests", "requests counter", []string{"filter", "filterName", "code"}), 197 "bytes": newFilterMetric("bytes", "request/response bytes", []string{"filter", "filterName", "direction"}), 198 "responseMsec": newFilterMetric("responseMsec", "average of only upstream/backend response processing times in milliseconds", []string{"filter", "filterName"}), 199 "requestMsec": newFilterMetric("requestMsec", "average of request processing times in milliseconds", []string{"filter", "filterName"}), 200 }, 201 cacheMetrics: map[string]*prometheus.Desc{ 202 "requests": newCacheMetric("requests", "cache requests counter", []string{"zone", "status"}), 203 "bytes": newCacheMetric("bytes", "cache request/response bytes", []string{"zone", "direction"}), 204 }, 205 } 206} 207 208func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { 209 for _, m := range e.serverMetrics { 210 ch <- m 211 } 212 for _, m := range e.upstreamMetrics { 213 ch <- m 214 } 215 for _, m := range e.filterMetrics { 216 ch <- m 217 } 218 for _, m := range e.cacheMetrics { 219 ch <- m 220 } 221} 222 223func (e *Exporter) Collect(ch chan<- prometheus.Metric) { 224 body, err := fetchHTTP(e.URI, time.Duration(*nginxScrapeTimeout)*time.Second)() 225 if err != nil { 226 log.Println("fetchHTTP failed", err) 227 return 228 } 229 defer body.Close() 230 231 data, err := ioutil.ReadAll(body) 232 if err != nil { 233 log.Println("ioutil.ReadAll failed", err) 234 return 235 } 236 237 var nginxVtx NginxVts 238 err = json.Unmarshal(data, &nginxVtx) 239 if err != nil { 240 log.Println("json.Unmarshal failed", err) 241 return 242 } 243 244 // info 245 uptime := (nginxVtx.NowMsec - nginxVtx.LoadMsec) / 1000 246 ch <- prometheus.MustNewConstMetric(e.infoMetric, prometheus.GaugeValue, float64(uptime), nginxVtx.HostName, nginxVtx.NginxVersion) 247 248 // connections 249 ch <- prometheus.MustNewConstMetric(e.serverMetrics["connections"], prometheus.GaugeValue, float64(nginxVtx.Connections.Active), "active") 250 ch <- prometheus.MustNewConstMetric(e.serverMetrics["connections"], prometheus.GaugeValue, float64(nginxVtx.Connections.Reading), "reading") 251 ch <- prometheus.MustNewConstMetric(e.serverMetrics["connections"], prometheus.GaugeValue, float64(nginxVtx.Connections.Waiting), "waiting") 252 ch <- prometheus.MustNewConstMetric(e.serverMetrics["connections"], prometheus.GaugeValue, float64(nginxVtx.Connections.Writing), "writing") 253 ch <- prometheus.MustNewConstMetric(e.serverMetrics["connections"], prometheus.GaugeValue, float64(nginxVtx.Connections.Accepted), "accepted") 254 ch <- prometheus.MustNewConstMetric(e.serverMetrics["connections"], prometheus.GaugeValue, float64(nginxVtx.Connections.Handled), "handled") 255 ch <- prometheus.MustNewConstMetric(e.serverMetrics["connections"], prometheus.GaugeValue, float64(nginxVtx.Connections.Requests), "requests") 256 257 // ServerZones 258 for host, s := range nginxVtx.ServerZones { 259 ch <- prometheus.MustNewConstMetric(e.serverMetrics["requests"], prometheus.CounterValue, float64(s.RequestCounter), host, "total") 260 ch <- prometheus.MustNewConstMetric(e.serverMetrics["requests"], prometheus.CounterValue, float64(s.Responses.OneXx), host, "1xx") 261 ch <- prometheus.MustNewConstMetric(e.serverMetrics["requests"], prometheus.CounterValue, float64(s.Responses.TwoXx), host, "2xx") 262 ch <- prometheus.MustNewConstMetric(e.serverMetrics["requests"], prometheus.CounterValue, float64(s.Responses.ThreeXx), host, "3xx") 263 ch <- prometheus.MustNewConstMetric(e.serverMetrics["requests"], prometheus.CounterValue, float64(s.Responses.FourXx), host, "4xx") 264 ch <- prometheus.MustNewConstMetric(e.serverMetrics["requests"], prometheus.CounterValue, float64(s.Responses.FiveXx), host, "5xx") 265 266 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Bypass), host, "bypass") 267 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Expired), host, "expired") 268 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Hit), host, "hit") 269 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Miss), host, "miss") 270 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Revalidated), host, "revalidated") 271 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Scarce), host, "scarce") 272 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Stale), host, "stale") 273 ch <- prometheus.MustNewConstMetric(e.serverMetrics["cache"], prometheus.CounterValue, float64(s.Responses.Updating), host, "updating") 274 275 ch <- prometheus.MustNewConstMetric(e.serverMetrics["bytes"], prometheus.CounterValue, float64(s.InBytes), host, "in") 276 ch <- prometheus.MustNewConstMetric(e.serverMetrics["bytes"], prometheus.CounterValue, float64(s.OutBytes), host, "out") 277 278 ch <- prometheus.MustNewConstMetric(e.serverMetrics["requestMsec"], prometheus.GaugeValue, float64(s.RequestMsec), host) 279 280 } 281 282 // UpstreamZones 283 for name, upstreamList := range nginxVtx.UpstreamZones { 284 for _, s := range upstreamList { 285 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["responseMsec"], prometheus.GaugeValue, float64(s.ResponseMsec), name, s.Server) 286 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["requestMsec"], prometheus.GaugeValue, float64(s.RequestMsec), name, s.Server) 287 288 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["requests"], prometheus.CounterValue, float64(s.RequestCounter), name, "total", s.Server) 289 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["requests"], prometheus.CounterValue, float64(s.Responses.OneXx), name, "1xx", s.Server) 290 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["requests"], prometheus.CounterValue, float64(s.Responses.TwoXx), name, "2xx", s.Server) 291 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["requests"], prometheus.CounterValue, float64(s.Responses.ThreeXx), name, "3xx", s.Server) 292 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["requests"], prometheus.CounterValue, float64(s.Responses.FourXx), name, "4xx", s.Server) 293 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["requests"], prometheus.CounterValue, float64(s.Responses.FiveXx), name, "5xx", s.Server) 294 295 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["bytes"], prometheus.CounterValue, float64(s.InBytes), name, "in", s.Server) 296 ch <- prometheus.MustNewConstMetric(e.upstreamMetrics["bytes"], prometheus.CounterValue, float64(s.OutBytes), name, "out", s.Server) 297 } 298 } 299 300 // FilterZones 301 for filter, values := range nginxVtx.FilterZones { 302 for name, stat := range values { 303 ch <- prometheus.MustNewConstMetric(e.filterMetrics["responseMsec"], prometheus.GaugeValue, float64(stat.ResponseMsec), filter, name) 304 ch <- prometheus.MustNewConstMetric(e.filterMetrics["requestMsec"], prometheus.GaugeValue, float64(stat.RequestMsec), filter, name) 305 ch <- prometheus.MustNewConstMetric(e.filterMetrics["requests"], prometheus.CounterValue, float64(stat.RequestCounter), filter, name, "total") 306 ch <- prometheus.MustNewConstMetric(e.filterMetrics["requests"], prometheus.CounterValue, float64(stat.Responses.OneXx), filter, name, "1xx") 307 ch <- prometheus.MustNewConstMetric(e.filterMetrics["requests"], prometheus.CounterValue, float64(stat.Responses.TwoXx), filter, name, "2xx") 308 ch <- prometheus.MustNewConstMetric(e.filterMetrics["requests"], prometheus.CounterValue, float64(stat.Responses.ThreeXx), filter, name, "3xx") 309 ch <- prometheus.MustNewConstMetric(e.filterMetrics["requests"], prometheus.CounterValue, float64(stat.Responses.FourXx), filter, name, "4xx") 310 ch <- prometheus.MustNewConstMetric(e.filterMetrics["requests"], prometheus.CounterValue, float64(stat.Responses.FiveXx), filter, name, "5xx") 311 312 ch <- prometheus.MustNewConstMetric(e.filterMetrics["bytes"], prometheus.CounterValue, float64(stat.InBytes), filter, name, "in") 313 ch <- prometheus.MustNewConstMetric(e.filterMetrics["bytes"], prometheus.CounterValue, float64(stat.OutBytes), filter, name, "out") 314 } 315 } 316 317 // CacheZones 318 for zone, s := range nginxVtx.CacheZones { 319 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Bypass), zone, "bypass") 320 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Expired), zone, "expired") 321 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Hit), zone, "hit") 322 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Miss), zone, "miss") 323 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Revalidated), zone, "revalidated") 324 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Scarce), zone, "scarce") 325 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Stale), zone, "stale") 326 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["requests"], prometheus.CounterValue, float64(s.Responses.Updating), zone, "updating") 327 328 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["bytes"], prometheus.CounterValue, float64(s.InBytes), zone, "in") 329 ch <- prometheus.MustNewConstMetric(e.cacheMetrics["bytes"], prometheus.CounterValue, float64(s.OutBytes), zone, "out") 330 } 331} 332 333func fetchHTTP(uri string, timeout time.Duration) func() (io.ReadCloser, error) { 334 http.DefaultClient.Timeout = timeout 335 http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: *insecure} 336 337 return func() (io.ReadCloser, error) { 338 resp, err := http.DefaultClient.Get(uri) 339 if err != nil { 340 return nil, err 341 } 342 if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { 343 resp.Body.Close() 344 return nil, fmt.Errorf("HTTP status %d", resp.StatusCode) 345 } 346 return resp.Body, nil 347 } 348} 349 350var ( 351 showVersion = flag.Bool("version", false, "Print version information.") 352 listenAddress = flag.String("telemetry.address", ":9913", "Address on which to expose metrics.") 353 metricsEndpoint = flag.String("telemetry.endpoint", "/metrics", "Path under which to expose metrics.") 354 metricsNamespace = flag.String("metrics.namespace", "nginx", "Prometheus metrics namespace.") 355 nginxScrapeURI = flag.String("nginx.scrape_uri", "http://localhost/status", "URI to nginx stub status page") 356 insecure = flag.Bool("insecure", true, "Ignore server certificate if using https") 357 nginxScrapeTimeout = flag.Int("nginx.scrape_timeout", 2, "The number of seconds to wait for an HTTP response from the nginx.scrape_uri") 358 goMetrics = flag.Bool("go.metrics", false, "Export process and go metrics.") 359) 360 361func init() { 362 prometheus.MustRegister(version.NewCollector("nginx_vts_exporter")) 363} 364 365func main() { 366 flag.Parse() 367 368 if *showVersion { 369 fmt.Fprintln(os.Stdout, version.Print("nginx_vts_exporter")) 370 os.Exit(0) 371 } 372 373 log.Printf("Starting nginx_vts_exporter %s", version.Info()) 374 log.Printf("Build context %s", version.BuildContext()) 375 376 exporter := NewExporter(*nginxScrapeURI) 377 prometheus.MustRegister(exporter) 378 379 if !(*goMetrics) { 380 prometheus.Unregister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{ 381 PidFn: func() (int, error) { 382 return os.Getpid(), nil 383 }, 384 })) 385 prometheus.Unregister(prometheus.NewGoCollector()) 386 } 387 388 http.Handle(*metricsEndpoint, promhttp.Handler()) 389 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 390 w.Write([]byte(`<html> 391 <head><title>Nginx Exporter</title></head> 392 <body> 393 <h1>Nginx Exporter</h1> 394 <p><a href="` + *metricsEndpoint + `">Metrics</a></p> 395 </body> 396 </html>`)) 397 }) 398 399 log.Printf("Starting Server at : %s", *listenAddress) 400 log.Printf("Metrics endpoint: %s", *metricsEndpoint) 401 log.Printf("Metrics namespace: %s", *metricsNamespace) 402 log.Printf("Scraping information from : %s", *nginxScrapeURI) 403 log.Fatal(http.ListenAndServe(*listenAddress, nil)) 404} 405