From ff98ad710bb718b26a55e7862f2d7fb70b14a1f7 Mon Sep 17 00:00:00 2001 From: Bugagazavr Date: Mon, 22 Oct 2018 22:54:50 +0300 Subject: [PATCH] Add Nginx Plus API input (#4837) --- plugins/inputs/all/all.go | 1 + plugins/inputs/nginx_plus_api/README.md | 216 ++++ .../inputs/nginx_plus_api/nginx_plus_api.go | 115 ++ .../nginx_plus_api/nginx_plus_api_metrics.go | 454 +++++++ .../nginx_plus_api_metrics_test.go | 1075 +++++++++++++++++ .../nginx_plus_api/nginx_plus_api_types.go | 133 ++ 6 files changed, 1994 insertions(+) create mode 100644 plugins/inputs/nginx_plus_api/README.md create mode 100644 plugins/inputs/nginx_plus_api/nginx_plus_api.go create mode 100644 plugins/inputs/nginx_plus_api/nginx_plus_api_metrics.go create mode 100644 plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go create mode 100644 plugins/inputs/nginx_plus_api/nginx_plus_api_types.go diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 5840893e9..1d666947a 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -80,6 +80,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/net_response" _ "github.com/influxdata/telegraf/plugins/inputs/nginx" _ "github.com/influxdata/telegraf/plugins/inputs/nginx_plus" + _ "github.com/influxdata/telegraf/plugins/inputs/nginx_plus_api" _ "github.com/influxdata/telegraf/plugins/inputs/nsq" _ "github.com/influxdata/telegraf/plugins/inputs/nsq_consumer" _ "github.com/influxdata/telegraf/plugins/inputs/nstat" diff --git a/plugins/inputs/nginx_plus_api/README.md b/plugins/inputs/nginx_plus_api/README.md new file mode 100644 index 000000000..e90645e43 --- /dev/null +++ b/plugins/inputs/nginx_plus_api/README.md @@ -0,0 +1,216 @@ +# Telegraf Plugin: nginx_plus_api + +Nginx Plus is a commercial version of the open source web server Nginx. The use this plugin you will need a license. For more information about the differences between Nginx (F/OSS) and Nginx Plus, [click here](https://www.nginx.com/blog/whats-difference-nginx-foss-nginx-plus/). + +### Configuration: + +``` +# Read Nginx Plus API advanced status information +[[inputs.nginx_plus_api]] + ## An array of Nginx API URIs to gather stats. + urls = ["http://localhost/api"] + # Nginx API version, default: 3 + # api_version = 3 +``` + +### Migration from Nginx Plus (Status) input plugin + +| Nginx Plus | Nginx Plus API | +|---------------------------------|--------------------------------------| +| nginx_plus_processes | nginx_plus_api_processes | +| nginx_plus_connections | nginx_plus_api_connections | +| nginx_plus_ssl | nginx_plus_api_ssl | +| nginx_plus_requests | nginx_plus_api_http_requests | +| nginx_plus_zone | nginx_plus_api_http_server_zones | +| nginx_plus_upstream | nginx_plus_api_http_upstreams | +| nginx_plus_upstream_peer | nginx_plus_api_http_upstream_peers | +| nginx_plus_cache | nginx_plus_api_http_caches | +| nginx_plus_stream_upstream | nginx_plus_api_stream_upstreams | +| nginx_plus_stream_upstream_peer | nginx_plus_api_stream_upstream_peers | +| nginx.stream.zone | nginx_plus_api_stream_server_zones | + +### Measurements & Fields: + +- nginx_plus_api_processes + - respawned +- nginx_plus_api_connections + - accepted + - dropped + - active + - idle +- nginx_plus_api_ssl + - handshakes + - handshakes_failed + - session_reuses +- nginx_plus_api_http_requests + - total + - current +- nginx_plus_api_http_server_zones + - processing + - requests + - responses_1xx + - responses_2xx + - responses_3xx + - responses_4xx + - responses_5xx + - responses_total + - received + - sent + - discarded +- nginx_plus_api_http_upstreams + - keepalive + - zombies +- nginx_plus_api_http_upstream_peers + - requests + - unavail + - healthchecks_checks + - header_time + - state + - response_time + - active + - healthchecks_last_passed + - weight + - responses_1xx + - responses_2xx + - responses_3xx + - responses_4xx + - responses_5xx + - received + - healthchecks_fails + - healthchecks_unhealthy + - backup + - responses_total + - sent + - fails + - downtime +- nginx_plus_api_http_caches + - size + - max_size + - cold + - hit_responses + - hit_bytes + - stale_responses + - stale_bytes + - updating_responses + - updating_bytes + - revalidated_responses + - revalidated_bytes + - miss_responses + - miss_bytes + - miss_responses_written + - miss_bytes_written + - expired_responses + - expired_bytes + - expired_responses_written + - expired_bytes_written + - bypass_responses + - bypass_bytes + - bypass_responses_written + - bypass_bytes_written +- nginx_plus_api_stream_upstreams + - zombies +- nginx_plus_api_stream_upstream_peers + - unavail + - healthchecks_checks + - healthchecks_fails + - healthchecks_unhealthy + - healthchecks_last_passed + - response_time + - state + - active + - weight + - received + - backup + - sent + - fails + - downtime +- nginx_plus_api_stream_server_zones + - processing + - connections + - received + - sent + + +### Tags: + +- nginx_plus_api_processes, nginx_plus_api_connections, nginx_plus_api_ssl, nginx_plus_api_http_requests + - source + - port + +- nginx_plus_api_http_upstreams, nginx_plus_api_stream_upstreams + - upstream + - source + - port + +- nginx_plus_api_http_server_zones, nginx_plus_api_upstream_server_zones + - source + - port + - zone + +- nginx_plus_api_upstream_peers, nginx_plus_api_stream_upstream_peers + - id + - upstream + - source + - port + - upstream_address + +- nginx_plus_api_http_caches + - source + - port + +### Example Output: + +Using this configuration: +``` +[[inputs.nginx_plus_api]] + ## An array of Nginx Plus API URIs to gather stats. + urls = ["http://localhost/api"] +``` + +When run with: +``` +./telegraf -config telegraf.conf -input-filter nginx_plus_api -test +``` + +It produces: +``` +> nginx_plus_api_processes,host=localhost,port=80,source=localhost respawned=0i 1539163505000000000 +> nginx_plus_api_connections,host=localhost,port=80,source=localhost accepted=120890747i,active=6i,dropped=0i,idle=67i 1539163505000000000 +> nginx_plus_api_ssl,host=localhost,port=80,source=localhost handshakes=2983938i,handshakes_failed=54350i,session_reuses=2485267i 1539163506000000000 +> nginx_plus_api_http_requests,host=localhost,port=80,source=localhost current=12i,total=175270198i 1539163506000000000 +> nginx_plus_api_http_server_zones,host=localhost,port=80,source=localhost,zone=hg.nginx.org discarded=45i,processing=0i,received=35723884i,requests=134102i,responses_1xx=0i,responses_2xx=96890i,responses_3xx=6892i,responses_4xx=30270i,responses_5xx=5i,responses_total=134057i,sent=3681826618i 1539163506000000000 +> nginx_plus_api_http_server_zones,host=localhost,port=80,source=localhost,zone=trac.nginx.org discarded=4034i,processing=9i,received=282399663i,requests=336129i,responses_1xx=0i,responses_2xx=101264i,responses_3xx=25454i,responses_4xx=68961i,responses_5xx=136407i,responses_total=332086i,sent=2346677493i 1539163506000000000 +> nginx_plus_api_http_server_zones,host=localhost,port=80,source=localhost,zone=lxr.nginx.org discarded=4i,processing=1i,received=7223569i,requests=29661i,responses_1xx=0i,responses_2xx=28584i,responses_3xx=73i,responses_4xx=390i,responses_5xx=609i,responses_total=29656i,sent=5811238975i 1539163506000000000 +> nginx_plus_api_http_upstreams,host=localhost,port=80,source=localhost,upstream=trac-backend keepalive=0i,zombies=0i 1539163506000000000 +> nginx_plus_api_http_upstream_peers,host=localhost,id=0,port=80,source=localhost,upstream=trac-backend,upstream_address=10.0.0.1:8080 active=0i,backup=false,downtime=53870i,fails=5i,header_time=421i,healthchecks_checks=17275i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=1885213684i,requests=88476i,response_time=423i,responses_1xx=0i,responses_2xx=50997i,responses_3xx=205i,responses_4xx=34344i,responses_5xx=2076i,responses_total=87622i,sent=189938404i,state="up",unavail=5i,weight=1i 1539163506000000000 +> nginx_plus_api_http_upstream_peers,host=localhost,id=1,port=80,source=localhost,upstream=trac-backend,upstream_address=10.0.0.1:8081 active=0i,backup=true,downtime=173957231i,fails=0i,healthchecks_checks=17394i,healthchecks_fails=17394i,healthchecks_last_passed=false,healthchecks_unhealthy=1i,received=0i,requests=0i,responses_1xx=0i,responses_2xx=0i,responses_3xx=0i,responses_4xx=0i,responses_5xx=0i,responses_total=0i,sent=0i,state="unhealthy",unavail=0i,weight=1i 1539163506000000000 +> nginx_plus_api_http_upstreams,host=localhost,port=80,source=localhost,upstream=hg-backend keepalive=0i,zombies=0i 1539163506000000000 +> nginx_plus_api_http_upstream_peers,host=localhost,id=0,port=80,source=localhost,upstream=hg-backend,upstream_address=10.0.0.1:8088 active=0i,backup=false,downtime=0i,fails=0i,header_time=22i,healthchecks_checks=17319i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=3724240605i,requests=89563i,response_time=44i,responses_1xx=0i,responses_2xx=81996i,responses_3xx=6886i,responses_4xx=639i,responses_5xx=5i,responses_total=89526i,sent=31597952i,state="up",unavail=0i,weight=5i 1539163506000000000 +> nginx_plus_api_http_upstream_peers,host=localhost,id=1,port=80,source=localhost,upstream=hg-backend,upstream_address=10.0.0.1:8089 active=0i,backup=true,downtime=173957231i,fails=0i,healthchecks_checks=17394i,healthchecks_fails=17394i,healthchecks_last_passed=false,healthchecks_unhealthy=1i,received=0i,requests=0i,responses_1xx=0i,responses_2xx=0i,responses_3xx=0i,responses_4xx=0i,responses_5xx=0i,responses_total=0i,sent=0i,state="unhealthy",unavail=0i,weight=1i 1539163506000000000 +> nginx_plus_api_http_upstreams,host=localhost,port=80,source=localhost,upstream=lxr-backend keepalive=0i,zombies=0i 1539163506000000000 +> nginx_plus_api_http_upstream_peers,host=localhost,id=0,port=80,source=localhost,upstream=lxr-backend,upstream_address=unix:/tmp/cgi.sock active=0i,backup=false,downtime=0i,fails=609i,header_time=111i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,received=6220215064i,requests=28278i,response_time=172i,responses_1xx=0i,responses_2xx=27665i,responses_3xx=0i,responses_4xx=0i,responses_5xx=0i,responses_total=27665i,sent=21337016i,state="up",unavail=0i,weight=1i 1539163506000000000 +> nginx_plus_api_http_upstream_peers,host=localhost,id=1,port=80,source=localhost,upstream=lxr-backend,upstream_address=unix:/tmp/cgib.sock active=0i,backup=true,downtime=0i,fails=0i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,max_conns=42i,received=0i,requests=0i,responses_1xx=0i,responses_2xx=0i,responses_3xx=0i,responses_4xx=0i,responses_5xx=0i,responses_total=0i,sent=0i,state="up",unavail=0i,weight=1i 1539163506000000000 +> nginx_plus_api_http_upstreams,host=localhost,port=80,source=localhost,upstream=demo-backend keepalive=0i,zombies=0i 1539163506000000000 +> nginx_plus_api_http_upstream_peers,host=localhost,id=0,port=80,source=localhost,upstream=demo-backend,upstream_address=10.0.0.2:15431 active=0i,backup=false,downtime=0i,fails=0i,healthchecks_checks=173640i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=0i,requests=0i,responses_1xx=0i,responses_2xx=0i,responses_3xx=0i,responses_4xx=0i,responses_5xx=0i,responses_total=0i,sent=0i,state="up",unavail=0i,weight=1i 1539163506000000000 +> nginx_plus_api_http_caches,cache=http_cache,host=localhost,port=80,source=localhost bypass_bytes=0i,bypass_bytes_written=0i,bypass_responses=0i,bypass_responses_written=0i,cold=false,expired_bytes=133671410i,expired_bytes_written=129210272i,expired_responses=15721i,expired_responses_written=15213i,hit_bytes=2459840828i,hit_responses=231195i,max_size=536870912i,miss_bytes=18742246i,miss_bytes_written=85199i,miss_responses=2816i,miss_responses_written=69i,revalidated_bytes=0i,revalidated_responses=0i,size=774144i,stale_bytes=0i,stale_responses=0i,updating_bytes=0i,updating_responses=0i 1539163506000000000 +> nginx_plus_api_stream_server_zones,host=localhost,port=80,source=localhost,zone=postgresql_loadbalancer connections=173639i,processing=0i,received=17884817i,sent=33685966i 1539163506000000000 +> nginx_plus_api_stream_server_zones,host=localhost,port=80,source=localhost,zone=dns_loadbalancer connections=97255i,processing=0i,received=2699082i,sent=16566552i 1539163506000000000 +> nginx_plus_api_stream_upstreams,host=localhost,port=80,source=localhost,upstream=postgresql_backends zombies=0i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=0,port=80,source=localhost,upstream=postgresql_backends,upstream_address=10.0.0.2:15432 active=0i,backup=false,connect_time=4i,connections=57880i,downtime=0i,fails=0i,first_byte_time=10i,healthchecks_checks=34781i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=11228720i,response_time=10i,sent=5961640i,state="up",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=1,port=80,source=localhost,upstream=postgresql_backends,upstream_address=10.0.0.2:15433 active=0i,backup=false,connect_time=3i,connections=57880i,downtime=0i,fails=0i,first_byte_time=9i,healthchecks_checks=34781i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=11228720i,response_time=10i,sent=5961640i,state="up",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=2,port=80,source=localhost,upstream=postgresql_backends,upstream_address=10.0.0.2:15434 active=0i,backup=false,connect_time=2i,connections=57879i,downtime=0i,fails=0i,first_byte_time=9i,healthchecks_checks=34781i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=11228526i,response_time=9i,sent=5961537i,state="up",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=3,port=80,source=localhost,upstream=postgresql_backends,upstream_address=10.0.0.2:15435 active=0i,backup=false,connections=0i,downtime=0i,fails=0i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,received=0i,sent=0i,state="down",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstreams,host=localhost,port=80,source=localhost,upstream=dns_udp_backends zombies=0i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=0,port=80,source=localhost,upstream=dns_udp_backends,upstream_address=10.0.0.5:53 active=0i,backup=false,connect_time=0i,connections=64837i,downtime=0i,fails=0i,first_byte_time=17i,healthchecks_checks=34761i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=10996616i,response_time=17i,sent=1791693i,state="up",unavail=0i,weight=2i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=1,port=80,source=localhost,upstream=dns_udp_backends,upstream_address=10.0.0.2:53 active=0i,backup=false,connect_time=0i,connections=32418i,downtime=0i,fails=0i,first_byte_time=17i,healthchecks_checks=34761i,healthchecks_fails=0i,healthchecks_last_passed=true,healthchecks_unhealthy=0i,received=5569936i,response_time=17i,sent=907389i,state="up",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=2,port=80,source=localhost,upstream=dns_udp_backends,upstream_address=10.0.0.7:53 active=0i,backup=false,connections=0i,downtime=0i,fails=0i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,received=0i,sent=0i,state="down",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstreams,host=localhost,port=80,source=localhost,upstream=unused_tcp_backends zombies=0i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=1,port=80,source=localhost,upstream=unused_tcp_backends,upstream_address=95.211.80.227:80 active=0i,backup=false,connections=0i,downtime=0i,fails=0i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,received=0i,sent=0i,state="down",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=2,port=80,source=localhost,upstream=unused_tcp_backends,upstream_address=206.251.255.63:80 active=0i,backup=false,connections=0i,downtime=0i,fails=0i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,received=0i,sent=0i,state="down",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=3,port=80,source=localhost,upstream=unused_tcp_backends,upstream_address=[2001:1af8:4060:a004:21::e3]:80 active=0i,backup=false,connections=0i,downtime=0i,fails=0i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,received=0i,sent=0i,state="down",unavail=0i,weight=1i 1539163507000000000 +> nginx_plus_api_stream_upstream_peers,host=localhost,id=4,port=80,source=localhost,upstream=unused_tcp_backends,upstream_address=[2606:7100:1:69::3f]:80 active=0i,backup=false,connections=0i,downtime=0i,fails=0i,healthchecks_checks=0i,healthchecks_fails=0i,healthchecks_unhealthy=0i,received=0i,sent=0i,state="down",unavail=0i,weight=1i 1539163507000000000 +``` + +### Reference material + +[api documentation](http://demo.nginx.com/swagger-ui/#/) diff --git a/plugins/inputs/nginx_plus_api/nginx_plus_api.go b/plugins/inputs/nginx_plus_api/nginx_plus_api.go new file mode 100644 index 000000000..d44f793f1 --- /dev/null +++ b/plugins/inputs/nginx_plus_api/nginx_plus_api.go @@ -0,0 +1,115 @@ +package nginx_plus_api + +import ( + "fmt" + "net/http" + "net/url" + "sync" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/plugins/inputs" +) + +type NginxPlusApi struct { + Urls []string + + ApiVersion int64 + + client *http.Client + + ResponseTimeout internal.Duration +} + +const ( + // Default settings + defaultApiVersion = 3 + + // Paths + processesPath = "processes" + connectionsPath = "connections" + sslPath = "ssl" + + httpRequestsPath = "http/requests" + httpServerZonesPath = "http/server_zones" + httpUpstreamsPath = "http/upstreams" + httpCachesPath = "http/caches" + + streamServerZonesPath = "stream/server_zones" + streamUpstreamsPath = "stream/upstreams" +) + +var sampleConfig = ` + ## An array of API URI to gather stats. + urls = ["http://localhost/api"] + + # Nginx API version, default: 3 + # api_version = 3 + + # HTTP response timeout (default: 5s) + response_timeout = "5s" +` + +func (n *NginxPlusApi) SampleConfig() string { + return sampleConfig +} + +func (n *NginxPlusApi) Description() string { + return "Read Nginx Plus Api documentation" +} + +func (n *NginxPlusApi) Gather(acc telegraf.Accumulator) error { + var wg sync.WaitGroup + + // Create an HTTP client that is re-used for each + // collection interval + + if n.ApiVersion == 0 { + n.ApiVersion = defaultApiVersion + } + + if n.client == nil { + client, err := n.createHttpClient() + if err != nil { + return err + } + n.client = client + } + + for _, u := range n.Urls { + addr, err := url.Parse(u) + if err != nil { + acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err)) + continue + } + + wg.Add(1) + go func(addr *url.URL) { + defer wg.Done() + n.gatherMetrics(addr, acc) + }(addr) + } + + wg.Wait() + return nil +} + +func (n *NginxPlusApi) createHttpClient() (*http.Client, error) { + if n.ResponseTimeout.Duration < time.Second { + n.ResponseTimeout.Duration = time.Second * 5 + } + + client := &http.Client{ + Transport: &http.Transport{}, + Timeout: n.ResponseTimeout.Duration, + } + + return client, nil +} + +func init() { + inputs.Add("nginx_plus_api", func() telegraf.Input { + return &NginxPlusApi{} + }) +} diff --git a/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics.go b/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics.go new file mode 100644 index 000000000..5583670e4 --- /dev/null +++ b/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics.go @@ -0,0 +1,454 @@ +package nginx_plus_api + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/influxdata/telegraf" +) + +func (n *NginxPlusApi) gatherMetrics(addr *url.URL, acc telegraf.Accumulator) { + acc.AddError(n.gatherProcessesMetrics(addr, acc)) + acc.AddError(n.gatherConnectionsMetrics(addr, acc)) + acc.AddError(n.gatherSslMetrics(addr, acc)) + acc.AddError(n.gatherHttpRequestsMetrics(addr, acc)) + acc.AddError(n.gatherHttpServerZonesMetrics(addr, acc)) + acc.AddError(n.gatherHttpUpstreamsMetrics(addr, acc)) + acc.AddError(n.gatherHttpCachesMetrics(addr, acc)) + acc.AddError(n.gatherStreamServerZonesMetrics(addr, acc)) + acc.AddError(n.gatherStreamUpstreamsMetrics(addr, acc)) +} + +func (n *NginxPlusApi) gatherUrl(addr *url.URL, path string) ([]byte, error) { + url := fmt.Sprintf("%s/%d/%s", addr.String(), n.ApiVersion, path) + resp, err := n.client.Get(url) + + if err != nil { + return nil, fmt.Errorf("error making HTTP request to %s: %s", addr.String(), err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s returned HTTP status %s", addr.String(), resp.Status) + } + contentType := strings.Split(resp.Header.Get("Content-Type"), ";")[0] + switch contentType { + case "application/json": + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return body, nil + default: + return nil, fmt.Errorf("%s returned unexpected content type %s", url, contentType) + } +} + +func (n *NginxPlusApi) gatherProcessesMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, processesPath) + if err != nil { + return err + } + + var processes = &Processes{} + + if err := json.Unmarshal(body, processes); err != nil { + return err + } + + acc.AddFields( + "nginx_plus_api_processes", + map[string]interface{}{ + "respawned": processes.Respawned, + }, + getTags(addr), + ) + + return nil +} + +func (n *NginxPlusApi) gatherConnectionsMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, connectionsPath) + if err != nil { + return err + } + + var connections = &Connections{} + + if err := json.Unmarshal(body, connections); err != nil { + return err + } + + acc.AddFields( + "nginx_plus_api_connections", + map[string]interface{}{ + "accepted": connections.Accepted, + "dropped": connections.Dropped, + "active": connections.Active, + "idle": connections.Idle, + }, + getTags(addr), + ) + + return nil +} + +func (n *NginxPlusApi) gatherSslMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, sslPath) + if err != nil { + return err + } + + var ssl = &Ssl{} + + if err := json.Unmarshal(body, ssl); err != nil { + return err + } + + acc.AddFields( + "nginx_plus_api_ssl", + map[string]interface{}{ + "handshakes": ssl.Handshakes, + "handshakes_failed": ssl.HandshakesFailed, + "session_reuses": ssl.SessionReuses, + }, + getTags(addr), + ) + + return nil +} + +func (n *NginxPlusApi) gatherHttpRequestsMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, httpRequestsPath) + if err != nil { + return err + } + + var httpRequests = &HttpRequests{} + + if err := json.Unmarshal(body, httpRequests); err != nil { + return err + } + + acc.AddFields( + "nginx_plus_api_http_requests", + map[string]interface{}{ + "total": httpRequests.Total, + "current": httpRequests.Current, + }, + getTags(addr), + ) + + return nil +} + +func (n *NginxPlusApi) gatherHttpServerZonesMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, httpServerZonesPath) + if err != nil { + return err + } + + var httpServerZones HttpServerZones + + if err := json.Unmarshal(body, &httpServerZones); err != nil { + return err + } + + tags := getTags(addr) + + for zoneName, zone := range httpServerZones { + zoneTags := map[string]string{} + for k, v := range tags { + zoneTags[k] = v + } + zoneTags["zone"] = zoneName + acc.AddFields( + "nginx_plus_api_http_server_zones", + func() map[string]interface{} { + result := map[string]interface{}{ + "processing": zone.Processing, + "requests": zone.Requests, + "responses_1xx": zone.Responses.Responses1xx, + "responses_2xx": zone.Responses.Responses2xx, + "responses_3xx": zone.Responses.Responses3xx, + "responses_4xx": zone.Responses.Responses4xx, + "responses_5xx": zone.Responses.Responses5xx, + "responses_total": zone.Responses.Total, + "received": zone.Received, + "sent": zone.Sent, + } + if zone.Discarded != nil { + result["discarded"] = *zone.Discarded + } + return result + }(), + zoneTags, + ) + } + + return nil +} + +func (n *NginxPlusApi) gatherHttpUpstreamsMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, httpUpstreamsPath) + if err != nil { + return err + } + + var httpUpstreams HttpUpstreams + + if err := json.Unmarshal(body, &httpUpstreams); err != nil { + return err + } + + tags := getTags(addr) + + for upstreamName, upstream := range httpUpstreams { + upstreamTags := map[string]string{} + for k, v := range tags { + upstreamTags[k] = v + } + upstreamTags["upstream"] = upstreamName + upstreamFields := map[string]interface{}{ + "keepalive": upstream.Keepalive, + "zombies": upstream.Zombies, + } + if upstream.Queue != nil { + upstreamFields["queue_size"] = upstream.Queue.Size + upstreamFields["queue_max_size"] = upstream.Queue.MaxSize + upstreamFields["queue_overflows"] = upstream.Queue.Overflows + } + acc.AddFields( + "nginx_plus_api_http_upstreams", + upstreamFields, + upstreamTags, + ) + for _, peer := range upstream.Peers { + peerFields := map[string]interface{}{ + "backup": peer.Backup, + "weight": peer.Weight, + "state": peer.State, + "active": peer.Active, + "requests": peer.Requests, + "responses_1xx": peer.Responses.Responses1xx, + "responses_2xx": peer.Responses.Responses2xx, + "responses_3xx": peer.Responses.Responses3xx, + "responses_4xx": peer.Responses.Responses4xx, + "responses_5xx": peer.Responses.Responses5xx, + "responses_total": peer.Responses.Total, + "sent": peer.Sent, + "received": peer.Received, + "fails": peer.Fails, + "unavail": peer.Unavail, + "healthchecks_checks": peer.HealthChecks.Checks, + "healthchecks_fails": peer.HealthChecks.Fails, + "healthchecks_unhealthy": peer.HealthChecks.Unhealthy, + "downtime": peer.Downtime, + //"selected": peer.Selected.toInt64, + //"downstart": peer.Downstart.toInt64, + } + if peer.HealthChecks.LastPassed != nil { + peerFields["healthchecks_last_passed"] = *peer.HealthChecks.LastPassed + } + if peer.HeaderTime != nil { + peerFields["header_time"] = *peer.HeaderTime + } + if peer.ResponseTime != nil { + peerFields["response_time"] = *peer.ResponseTime + } + if peer.MaxConns != nil { + peerFields["max_conns"] = *peer.MaxConns + } + peerTags := map[string]string{} + for k, v := range upstreamTags { + peerTags[k] = v + } + peerTags["upstream_address"] = peer.Server + if peer.ID != nil { + peerTags["id"] = strconv.Itoa(*peer.ID) + } + acc.AddFields("nginx_plus_api_http_upstream_peers", peerFields, peerTags) + } + } + return nil +} + +func (n *NginxPlusApi) gatherHttpCachesMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, httpCachesPath) + if err != nil { + return err + } + + var httpCaches HttpCaches + + if err := json.Unmarshal(body, &httpCaches); err != nil { + return err + } + + tags := getTags(addr) + + for cacheName, cache := range httpCaches { + cacheTags := map[string]string{} + for k, v := range tags { + cacheTags[k] = v + } + cacheTags["cache"] = cacheName + acc.AddFields( + "nginx_plus_api_http_caches", + map[string]interface{}{ + "size": cache.Size, + "max_size": cache.MaxSize, + "cold": cache.Cold, + "hit_responses": cache.Hit.Responses, + "hit_bytes": cache.Hit.Bytes, + "stale_responses": cache.Stale.Responses, + "stale_bytes": cache.Stale.Bytes, + "updating_responses": cache.Updating.Responses, + "updating_bytes": cache.Updating.Bytes, + "revalidated_responses": cache.Revalidated.Responses, + "revalidated_bytes": cache.Revalidated.Bytes, + "miss_responses": cache.Miss.Responses, + "miss_bytes": cache.Miss.Bytes, + "miss_responses_written": cache.Miss.ResponsesWritten, + "miss_bytes_written": cache.Miss.BytesWritten, + "expired_responses": cache.Expired.Responses, + "expired_bytes": cache.Expired.Bytes, + "expired_responses_written": cache.Expired.ResponsesWritten, + "expired_bytes_written": cache.Expired.BytesWritten, + "bypass_responses": cache.Bypass.Responses, + "bypass_bytes": cache.Bypass.Bytes, + "bypass_responses_written": cache.Bypass.ResponsesWritten, + "bypass_bytes_written": cache.Bypass.BytesWritten, + }, + cacheTags, + ) + } + + return nil +} + +func (n *NginxPlusApi) gatherStreamServerZonesMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, streamServerZonesPath) + if err != nil { + return err + } + + var streamServerZones StreamServerZones + + if err := json.Unmarshal(body, &streamServerZones); err != nil { + return err + } + + tags := getTags(addr) + + for zoneName, zone := range streamServerZones { + zoneTags := map[string]string{} + for k, v := range tags { + zoneTags[k] = v + } + zoneTags["zone"] = zoneName + acc.AddFields( + "nginx_plus_api_stream_server_zones", + map[string]interface{}{ + "processing": zone.Processing, + "connections": zone.Connections, + "received": zone.Received, + "sent": zone.Sent, + }, + zoneTags, + ) + } + + return nil +} + +func (n *NginxPlusApi) gatherStreamUpstreamsMetrics(addr *url.URL, acc telegraf.Accumulator) error { + body, err := n.gatherUrl(addr, streamUpstreamsPath) + if err != nil { + return err + } + + var streamUpstreams StreamUpstreams + + if err := json.Unmarshal(body, &streamUpstreams); err != nil { + return err + } + + tags := getTags(addr) + + for upstreamName, upstream := range streamUpstreams { + upstreamTags := map[string]string{} + for k, v := range tags { + upstreamTags[k] = v + } + upstreamTags["upstream"] = upstreamName + acc.AddFields( + "nginx_plus_api_stream_upstreams", + map[string]interface{}{ + "zombies": upstream.Zombies, + }, + upstreamTags, + ) + for _, peer := range upstream.Peers { + peerFields := map[string]interface{}{ + "backup": peer.Backup, + "weight": peer.Weight, + "state": peer.State, + "active": peer.Active, + "connections": peer.Connections, + "sent": peer.Sent, + "received": peer.Received, + "fails": peer.Fails, + "unavail": peer.Unavail, + "healthchecks_checks": peer.HealthChecks.Checks, + "healthchecks_fails": peer.HealthChecks.Fails, + "healthchecks_unhealthy": peer.HealthChecks.Unhealthy, + "downtime": peer.Downtime, + } + if peer.HealthChecks.LastPassed != nil { + peerFields["healthchecks_last_passed"] = *peer.HealthChecks.LastPassed + } + if peer.ConnectTime != nil { + peerFields["connect_time"] = *peer.ConnectTime + } + if peer.FirstByteTime != nil { + peerFields["first_byte_time"] = *peer.FirstByteTime + } + if peer.ResponseTime != nil { + peerFields["response_time"] = *peer.ResponseTime + } + peerTags := map[string]string{} + for k, v := range upstreamTags { + peerTags[k] = v + } + peerTags["upstream_address"] = peer.Server + peerTags["id"] = strconv.Itoa(peer.ID) + + acc.AddFields("nginx_plus_api_stream_upstream_peers", peerFields, peerTags) + } + } + + return nil +} + +func getTags(addr *url.URL) map[string]string { + h := addr.Host + host, port, err := net.SplitHostPort(h) + if err != nil { + host = addr.Host + if addr.Scheme == "http" { + port = "80" + } else if addr.Scheme == "https" { + port = "443" + } else { + port = "" + } + } + return map[string]string{"source": host, "port": port} +} diff --git a/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go b/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go new file mode 100644 index 000000000..a7516dee5 --- /dev/null +++ b/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics_test.go @@ -0,0 +1,1075 @@ +package nginx_plus_api + +import ( + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +const processesPayload = ` +{ + "respawned": 0 +} +` + +const connectionsPayload = ` +{ + "accepted": 1234567890000, + "dropped": 2345678900000, + "active": 345, + "idle": 567 +} +` + +const sslPayload = ` +{ + "handshakes": 79572, + "handshakes_failed": 21025, + "session_reuses": 15762 +} +` + +const httpRequestsPayload = ` +{ + "total": 10624511, + "current": 4 +} +` + +const httpServerZonesPayload = ` +{ + "site1": { + "processing": 2, + "requests": 736395, + "responses": { + "1xx": 0, + "2xx": 727290, + "3xx": 4614, + "4xx": 934, + "5xx": 1535, + "total": 734373 + }, + "discarded": 2020, + "received": 180157219, + "sent": 20183175459 + }, + "site2": { + "processing": 1, + "requests": 185307, + "responses": { + "1xx": 0, + "2xx": 112674, + "3xx": 45383, + "4xx": 2504, + "5xx": 4419, + "total": 164980 + }, + "discarded": 20326, + "received": 51575327, + "sent": 2983241510 + } +} +` + +const httpUpstreamsPayload = ` +{ + "trac-backend": { + "peers": [ + { + "id": 0, + "server": "10.0.0.1:8088", + "name": "10.0.0.1:8088", + "backup": false, + "weight": 5, + "state": "up", + "active": 0, + "requests": 667231, + "header_time": 20, + "response_time": 36, + "responses": { + "1xx": 0, + "2xx": 666310, + "3xx": 0, + "4xx": 915, + "5xx": 6, + "total": 667231 + }, + "sent": 251946292, + "received": 19222475454, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26214, + "fails": 0, + "unhealthy": 0, + "last_passed": true + }, + "downtime": 0, + "downstart": {}, + "selected": {} + }, + { + "id": 1, + "server": "10.0.0.1:8089", + "name": "10.0.0.1:8089", + "backup": true, + "weight": 1, + "state": "unhealthy", + "active": 0, + "requests": 0, + "responses": { + "1xx": 0, + "2xx": 0, + "3xx": 0, + "4xx": 0, + "5xx": 0, + "total": 0 + }, + "sent": 0, + "received": 0, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26284, + "fails": 26284, + "unhealthy": 1, + "last_passed": false + }, + "downtime": 262925617, + "downstart": {}, + "selected": {} + } + ], + "keepalive": 0, + "zombies": 0, + "zone": "trac-backend" + }, + "hg-backend": { + "peers": [ + { + "id": 0, + "server": "10.0.0.1:8088", + "name": "10.0.0.1:8088", + "backup": false, + "weight": 5, + "state": "up", + "active": 0, + "requests": 667231, + "header_time": 20, + "response_time": 36, + "responses": { + "1xx": 0, + "2xx": 666310, + "3xx": 0, + "4xx": 915, + "5xx": 6, + "total": 667231 + }, + "sent": 251946292, + "received": 19222475454, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26214, + "fails": 0, + "unhealthy": 0, + "last_passed": true + }, + "downtime": 0, + "downstart": {}, + "selected": {} + }, + { + "id": 1, + "server": "10.0.0.1:8089", + "name": "10.0.0.1:8089", + "backup": true, + "weight": 1, + "state": "unhealthy", + "active": 0, + "requests": 0, + "responses": { + "1xx": 0, + "2xx": 0, + "3xx": 0, + "4xx": 0, + "5xx": 0, + "total": 0 + }, + "sent": 0, + "received": 0, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26284, + "fails": 26284, + "unhealthy": 1, + "last_passed": false + }, + "downtime": 262925617, + "downstart": {}, + "selected": {} + } + ], + "keepalive": 0, + "zombies": 0, + "zone": "hg-backend" + } +} +` + +const httpCachesPayload = ` +{ + "http-cache": { + "size": 530915328, + "max_size": 536870912, + "cold": false, + "hit": { + "responses": 254032, + "bytes": 6685627875 + }, + "stale": { + "responses": 0, + "bytes": 0 + }, + "updating": { + "responses": 0, + "bytes": 0 + }, + "revalidated": { + "responses": 0, + "bytes": 0 + }, + "miss": { + "responses": 1619201, + "bytes": 53841943822 + }, + "expired": { + "responses": 45859, + "bytes": 1656847080, + "responses_written": 44992, + "bytes_written": 1641825173 + }, + "bypass": { + "responses": 200187, + "bytes": 5510647548, + "responses_written": 200173, + "bytes_written": 44992 + } + }, + "frontend-cache": { + "size": 530915328, + "max_size": 536870912, + "cold": false, + "hit": { + "responses": 254032, + "bytes": 6685627875 + }, + "stale": { + "responses": 0, + "bytes": 0 + }, + "updating": { + "responses": 0, + "bytes": 0 + }, + "revalidated": { + "responses": 0, + "bytes": 0 + }, + "miss": { + "responses": 1619201, + "bytes": 53841943822 + }, + "expired": { + "responses": 45859, + "bytes": 1656847080, + "responses_written": 44992, + "bytes_written": 1641825173 + }, + "bypass": { + "responses": 200187, + "bytes": 5510647548, + "responses_written": 200173, + "bytes_written": 44992 + } + } +} +` + +const streamUpstreamsPayload = ` +{ + "mysql_backends": { + "peers": [ + { + "id": 0, + "server": "10.0.0.1:12345", + "name": "10.0.0.1:12345", + "backup": false, + "weight": 5, + "state": "up", + "active": 0, + "max_conns": 30, + "connecions": 1231, + "sent": 251946292, + "received": 19222475454, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26214, + "fails": 0, + "unhealthy": 0, + "last_passed": true + }, + "downtime": 0, + "downstart": {}, + "selected": {} + }, + { + "id": 1, + "server": "10.0.0.1:12346", + "name": "10.0.0.1:12346", + "backup": true, + "weight": 1, + "state": "unhealthy", + "active": 0, + "max_conns": 30, + "connections": 0, + "sent": 0, + "received": 0, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26284, + "fails": 26284, + "unhealthy": 1, + "last_passed": false + }, + "downtime": 262925617, + "downstart": {}, + "selected": {} + } + ], + "zombies": 0, + "zone": "mysql_backends" + }, + "dns": { + "peers": [ + { + "id": 0, + "server": "10.0.0.1:12347", + "name": "10.0.0.1:12347", + "backup": false, + "weight": 5, + "state": "up", + "active": 0, + "max_conns": 30, + "connections": 667231, + "sent": 251946292, + "received": 19222475454, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26214, + "fails": 0, + "unhealthy": 0, + "last_passed": true + }, + "downtime": 0, + "downstart": {}, + "selected": {} + }, + { + "id": 1, + "server": "10.0.0.1:12348", + "name": "10.0.0.1:12348", + "backup": true, + "weight": 1, + "state": "unhealthy", + "active": 0, + "connections": 0, + "max_conns": 30, + "sent": 0, + "received": 0, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 26284, + "fails": 26284, + "unhealthy": 1, + "last_passed": false + }, + "downtime": 262925617, + "downstart": {}, + "selected": {} + } + ], + "zombies": 0, + "zone": "dns" + } +} +` + +const streamServerZonesPayload = ` +{ + "mysql-frontend": { + "processing": 2, + "connections": 270925, + "sessions": { + "2xx": 155564, + "4xx": 0, + "5xx": 0, + "total": 270925 + }, + "discarded": 0, + "received": 28988975, + "sent": 3879346317 + }, + "dns": { + "processing": 1, + "connections": 155569, + "sessions": { + "2xx": 155564, + "4xx": 0, + "5xx": 0, + "total": 155569 + }, + "discarded": 0, + "received": 4200363, + "sent": 20489184 + } +} +` + +func TestGatherProcessesMetrics(t *testing.T) { + ts, n := prepareEndpoint(processesPath, defaultApiVersion, processesPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherProcessesMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_processes", + map[string]interface{}{ + "respawned": int(0), + }, + map[string]string{ + "source": host, + "port": port, + }) +} + +func TestGatherConnectioinsMetrics(t *testing.T) { + ts, n := prepareEndpoint(connectionsPath, defaultApiVersion, connectionsPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherConnectionsMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_connections", + map[string]interface{}{ + "accepted": int64(1234567890000), + "dropped": int64(2345678900000), + "active": int64(345), + "idle": int64(567), + }, + map[string]string{ + "source": host, + "port": port, + }) +} + +func TestGatherSslMetrics(t *testing.T) { + ts, n := prepareEndpoint(sslPath, defaultApiVersion, sslPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherSslMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_ssl", + map[string]interface{}{ + "handshakes": int64(79572), + "handshakes_failed": int64(21025), + "session_reuses": int64(15762), + }, + map[string]string{ + "source": host, + "port": port, + }) +} + +func TestGatherHttpRequestsMetrics(t *testing.T) { + ts, n := prepareEndpoint(httpRequestsPath, defaultApiVersion, httpRequestsPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherHttpRequestsMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_requests", + map[string]interface{}{ + "total": int64(10624511), + "current": int64(4), + }, + map[string]string{ + "source": host, + "port": port, + }) +} + +func TestGatherHttpServerZonesMetrics(t *testing.T) { + ts, n := prepareEndpoint(httpServerZonesPath, defaultApiVersion, httpServerZonesPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherHttpServerZonesMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_server_zones", + map[string]interface{}{ + "discarded": int64(2020), + "processing": int(2), + "received": int64(180157219), + "requests": int64(736395), + "responses_1xx": int64(0), + "responses_2xx": int64(727290), + "responses_3xx": int64(4614), + "responses_4xx": int64(934), + "responses_5xx": int64(1535), + "responses_total": int64(734373), + "sent": int64(20183175459), + }, + map[string]string{ + "source": host, + "port": port, + "zone": "site1", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_server_zones", + map[string]interface{}{ + "discarded": int64(20326), + "processing": int(1), + "received": int64(51575327), + "requests": int64(185307), + "responses_1xx": int64(0), + "responses_2xx": int64(112674), + "responses_3xx": int64(45383), + "responses_4xx": int64(2504), + "responses_5xx": int64(4419), + "responses_total": int64(164980), + "sent": int64(2983241510), + }, + map[string]string{ + "source": host, + "port": port, + "zone": "site2", + }) +} + +func TestHatherHttpUpstreamsMetrics(t *testing.T) { + ts, n := prepareEndpoint(httpUpstreamsPath, defaultApiVersion, httpUpstreamsPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherHttpUpstreamsMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_upstreams", + map[string]interface{}{ + "keepalive": int(0), + "zombies": int(0), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "trac-backend", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_upstreams", + map[string]interface{}{ + "keepalive": int(0), + "zombies": int(0), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "hg-backend", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": false, + "downtime": int64(0), + "fails": int64(0), + "header_time": int64(20), + "healthchecks_checks": int64(26214), + "healthchecks_fails": int64(0), + "healthchecks_last_passed": true, + "healthchecks_unhealthy": int64(0), + "received": int64(19222475454), + "requests": int64(667231), + "response_time": int64(36), + "responses_1xx": int64(0), + "responses_2xx": int64(666310), + "responses_3xx": int64(0), + "responses_4xx": int64(915), + "responses_5xx": int64(6), + "responses_total": int64(667231), + "sent": int64(251946292), + "state": "up", + "unavail": int64(0), + "weight": int(5), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "trac-backend", + "upstream_address": "10.0.0.1:8088", + "id": "0", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": true, + "downtime": int64(262925617), + "fails": int64(0), + "healthchecks_checks": int64(26284), + "healthchecks_fails": int64(26284), + "healthchecks_last_passed": false, + "healthchecks_unhealthy": int64(1), + "received": int64(0), + "requests": int64(0), + "responses_1xx": int64(0), + "responses_2xx": int64(0), + "responses_3xx": int64(0), + "responses_4xx": int64(0), + "responses_5xx": int64(0), + "responses_total": int64(0), + "sent": int64(0), + "state": "unhealthy", + "unavail": int64(0), + "weight": int(1), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "trac-backend", + "upstream_address": "10.0.0.1:8089", + "id": "1", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": false, + "downtime": int64(0), + "fails": int64(0), + "header_time": int64(20), + "healthchecks_checks": int64(26214), + "healthchecks_fails": int64(0), + "healthchecks_last_passed": true, + "healthchecks_unhealthy": int64(0), + "received": int64(19222475454), + "requests": int64(667231), + "response_time": int64(36), + "responses_1xx": int64(0), + "responses_2xx": int64(666310), + "responses_3xx": int64(0), + "responses_4xx": int64(915), + "responses_5xx": int64(6), + "responses_total": int64(667231), + "sent": int64(251946292), + "state": "up", + "unavail": int64(0), + "weight": int(5), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "hg-backend", + "upstream_address": "10.0.0.1:8088", + "id": "0", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": true, + "downtime": int64(262925617), + "fails": int64(0), + "healthchecks_checks": int64(26284), + "healthchecks_fails": int64(26284), + "healthchecks_last_passed": false, + "healthchecks_unhealthy": int64(1), + "received": int64(0), + "requests": int64(0), + "responses_1xx": int64(0), + "responses_2xx": int64(0), + "responses_3xx": int64(0), + "responses_4xx": int64(0), + "responses_5xx": int64(0), + "responses_total": int64(0), + "sent": int64(0), + "state": "unhealthy", + "unavail": int64(0), + "weight": int(1), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "hg-backend", + "upstream_address": "10.0.0.1:8089", + "id": "1", + }) +} + +func TestGatherHttpCachesMetrics(t *testing.T) { + ts, n := prepareEndpoint(httpCachesPath, defaultApiVersion, httpCachesPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherHttpCachesMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_caches", + map[string]interface{}{ + "bypass_bytes": int64(5510647548), + "bypass_bytes_written": int64(44992), + "bypass_responses": int64(200187), + "bypass_responses_written": int64(200173), + "cold": false, + "expired_bytes": int64(1656847080), + "expired_bytes_written": int64(1641825173), + "expired_responses": int64(45859), + "expired_responses_written": int64(44992), + "hit_bytes": int64(6685627875), + "hit_responses": int64(254032), + "max_size": int64(536870912), + "miss_bytes": int64(53841943822), + "miss_bytes_written": int64(0), + "miss_responses": int64(1619201), + "miss_responses_written": int64(0), + "revalidated_bytes": int64(0), + "revalidated_responses": int64(0), + "size": int64(530915328), + "stale_bytes": int64(0), + "stale_responses": int64(0), + "updating_bytes": int64(0), + "updating_responses": int64(0), + }, + map[string]string{ + "source": host, + "port": port, + "cache": "http-cache", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_http_caches", + map[string]interface{}{ + "bypass_bytes": int64(5510647548), + "bypass_bytes_written": int64(44992), + "bypass_responses": int64(200187), + "bypass_responses_written": int64(200173), + "cold": false, + "expired_bytes": int64(1656847080), + "expired_bytes_written": int64(1641825173), + "expired_responses": int64(45859), + "expired_responses_written": int64(44992), + "hit_bytes": int64(6685627875), + "hit_responses": int64(254032), + "max_size": int64(536870912), + "miss_bytes": int64(53841943822), + "miss_bytes_written": int64(0), + "miss_responses": int64(1619201), + "miss_responses_written": int64(0), + "revalidated_bytes": int64(0), + "revalidated_responses": int64(0), + "size": int64(530915328), + "stale_bytes": int64(0), + "stale_responses": int64(0), + "updating_bytes": int64(0), + "updating_responses": int64(0), + }, + map[string]string{ + "source": host, + "port": port, + "cache": "frontend-cache", + }) +} + +func TestGatherStreamUpstreams(t *testing.T) { + ts, n := prepareEndpoint(streamUpstreamsPath, defaultApiVersion, streamUpstreamsPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherStreamUpstreamsMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_upstreams", + map[string]interface{}{ + "zombies": int(0), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "mysql_backends", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_upstreams", + map[string]interface{}{ + "zombies": int(0), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "dns", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": false, + "connections": int64(0), + "downtime": int64(0), + "fails": int64(0), + "healthchecks_checks": int64(26214), + "healthchecks_fails": int64(0), + "healthchecks_last_passed": true, + "healthchecks_unhealthy": int64(0), + "received": int64(19222475454), + "sent": int64(251946292), + "state": "up", + "unavail": int64(0), + "weight": int(5), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "mysql_backends", + "upstream_address": "10.0.0.1:12345", + "id": "0", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": true, + "connections": int64(0), + "downtime": int64(262925617), + "fails": int64(0), + "healthchecks_checks": int64(26284), + "healthchecks_fails": int64(26284), + "healthchecks_last_passed": false, + "healthchecks_unhealthy": int64(1), + "received": int64(0), + "sent": int64(0), + "state": "unhealthy", + "unavail": int64(0), + "weight": int(1), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "mysql_backends", + "upstream_address": "10.0.0.1:12346", + "id": "1", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": false, + "connections": int64(667231), + "downtime": int64(0), + "fails": int64(0), + "healthchecks_checks": int64(26214), + "healthchecks_fails": int64(0), + "healthchecks_last_passed": true, + "healthchecks_unhealthy": int64(0), + "received": int64(19222475454), + "sent": int64(251946292), + "state": "up", + "unavail": int64(0), + "weight": int(5), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "dns", + "upstream_address": "10.0.0.1:12347", + "id": "0", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_upstream_peers", + map[string]interface{}{ + "active": int(0), + "backup": true, + "connections": int64(0), + "downtime": int64(262925617), + "fails": int64(0), + "healthchecks_checks": int64(26284), + "healthchecks_fails": int64(26284), + "healthchecks_last_passed": false, + "healthchecks_unhealthy": int64(1), + "received": int64(0), + "sent": int64(0), + "state": "unhealthy", + "unavail": int64(0), + "weight": int(1), + }, + map[string]string{ + "source": host, + "port": port, + "upstream": "dns", + "upstream_address": "10.0.0.1:12348", + "id": "1", + }) + +} + +func TestGatherStreamServerZonesMatrics(t *testing.T) { + ts, n := prepareEndpoint(streamServerZonesPath, defaultApiVersion, streamServerZonesPayload) + defer ts.Close() + + var acc testutil.Accumulator + addr, host, port := prepareAddr(ts) + + require.NoError(t, n.gatherStreamServerZonesMetrics(addr, &acc)) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_server_zones", + map[string]interface{}{ + "connections": int(270925), + "processing": int(2), + "received": int64(28988975), + "sent": int64(3879346317), + }, + map[string]string{ + "source": host, + "port": port, + "zone": "mysql-frontend", + }) + + acc.AssertContainsTaggedFields( + t, + "nginx_plus_api_stream_server_zones", + map[string]interface{}{ + "connections": int(155569), + "processing": int(1), + "received": int64(4200363), + "sent": int64(20489184), + }, + map[string]string{ + "source": host, + "port": port, + "zone": "dns", + }) +} + +func prepareAddr(ts *httptest.Server) (*url.URL, string, string) { + addr, err := url.Parse(fmt.Sprintf("%s/api", ts.URL)) + if err != nil { + panic(err) + } + + host, port, err := net.SplitHostPort(addr.Host) + + if err != nil { + host = addr.Host + if addr.Scheme == "http" { + port = "80" + } else if addr.Scheme == "https" { + port = "443" + } else { + port = "" + } + } + + return addr, host, port +} + +func prepareEndpoint(path string, apiVersion int64, payload string) (*httptest.Server, *NginxPlusApi) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var rsp string + + if r.URL.Path == fmt.Sprintf("/api/%d/%s", apiVersion, path) { + rsp = payload + w.Header()["Content-Type"] = []string{"application/json"} + } else { + panic("Cannot handle request") + } + + fmt.Fprintln(w, rsp) + })) + + n := &NginxPlusApi{ + Urls: []string{fmt.Sprintf("%s/api", ts.URL)}, + ApiVersion: apiVersion, + } + + client, err := n.createHttpClient() + if err != nil { + panic(err) + } + n.client = client + + return ts, n +} diff --git a/plugins/inputs/nginx_plus_api/nginx_plus_api_types.go b/plugins/inputs/nginx_plus_api/nginx_plus_api_types.go new file mode 100644 index 000000000..b8240f844 --- /dev/null +++ b/plugins/inputs/nginx_plus_api/nginx_plus_api_types.go @@ -0,0 +1,133 @@ +package nginx_plus_api + +type Processes struct { + Respawned int `json:"respawned"` +} + +type Connections struct { + Accepted int64 `json:"accepted"` + Dropped int64 `json:"dropped"` + Active int64 `json:"active"` + Idle int64 `json:"idle"` +} + +type Ssl struct { // added in version 6 + Handshakes int64 `json:"handshakes"` + HandshakesFailed int64 `json:"handshakes_failed"` + SessionReuses int64 `json:"session_reuses"` +} + +type HttpRequests struct { + Total int64 `json:"total"` + Current int64 `json:"current"` +} + +type ResponseStats struct { + Responses1xx int64 `json:"1xx"` + Responses2xx int64 `json:"2xx"` + Responses3xx int64 `json:"3xx"` + Responses4xx int64 `json:"4xx"` + Responses5xx int64 `json:"5xx"` + Total int64 `json:"total"` +} + +type HttpServerZones map[string]struct { + Processing int `json:"processing"` + Requests int64 `json:"requests"` + Responses ResponseStats `json:"responses"` + Discarded *int64 `json:"discarded"` // added in version 6 + Received int64 `json:"received"` + Sent int64 `json:"sent"` +} + +type HealthCheckStats struct { + Checks int64 `json:"checks"` + Fails int64 `json:"fails"` + Unhealthy int64 `json:"unhealthy"` + LastPassed *bool `json:"last_passed"` +} + +type HttpUpstreams map[string]struct { + Peers []struct { + ID *int `json:"id"` // added in version 3 + Server string `json:"server"` + Backup bool `json:"backup"` + Weight int `json:"weight"` + State string `json:"state"` + Active int `json:"active"` + Keepalive *int `json:"keepalive"` // removed in version 5 + MaxConns *int `json:"max_conns"` // added in version 3 + Requests int64 `json:"requests"` + Responses ResponseStats `json:"responses"` + Sent int64 `json:"sent"` + Received int64 `json:"received"` + Fails int64 `json:"fails"` + Unavail int64 `json:"unavail"` + HealthChecks HealthCheckStats `json:"health_checks"` + Downtime int64 `json:"downtime"` + HeaderTime *int64 `json:"header_time"` // added in version 5 + ResponseTime *int64 `json:"response_time"` // added in version 5 + } `json:"peers"` + Keepalive int `json:"keepalive"` + Zombies int `json:"zombies"` // added in version 6 + Queue *struct { // added in version 6 + Size int `json:"size"` + MaxSize int `json:"max_size"` + Overflows int64 `json:"overflows"` + } `json:"queue"` +} + +type StreamServerZones map[string]struct { + Processing int `json:"processing"` + Connections int `json:"connections"` + Sessions *ResponseStats `json:"sessions"` + Discarded *int64 `json:"discarded"` // added in version 7 + Received int64 `json:"received"` + Sent int64 `json:"sent"` +} + +type StreamUpstreams map[string]struct { + Peers []struct { + ID int `json:"id"` + Server string `json:"server"` + Backup bool `json:"backup"` + Weight int `json:"weight"` + State string `json:"state"` + Active int `json:"active"` + Connections int64 `json:"connections"` + ConnectTime *int `json:"connect_time"` + FirstByteTime *int `json:"first_byte_time"` + ResponseTime *int `json:"response_time"` + Sent int64 `json:"sent"` + Received int64 `json:"received"` + Fails int64 `json:"fails"` + Unavail int64 `json:"unavail"` + HealthChecks HealthCheckStats `json:"health_checks"` + Downtime int64 `json:"downtime"` + } `json:"peers"` + Zombies int `json:"zombies"` +} + +type BasicHitStats struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` +} + +type ExtendedHitStats struct { + BasicHitStats + ResponsesWritten int64 `json:"responses_written"` + BytesWritten int64 `json:"bytes_written"` +} + +type HttpCaches map[string]struct { // added in version 2 + Size int64 `json:"size"` + MaxSize int64 `json:"max_size"` + Cold bool `json:"cold"` + Hit BasicHitStats `json:"hit"` + Stale BasicHitStats `json:"stale"` + Updating BasicHitStats `json:"updating"` + Revalidated *BasicHitStats `json:"revalidated"` // added in version 3 + Miss ExtendedHitStats `json:"miss"` + Expired ExtendedHitStats `json:"expired"` + Bypass ExtendedHitStats `json:"bypass"` +}