├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── nginx_exporter.go └── nginx_exporter_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | vendor 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nginx_exporter 2 | vendor 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.9 2 | LABEL maintainer="@discordianfish" 3 | 4 | # Install dep tool 5 | ARG DEP_VERSION=v0.3.2 6 | ARG DEP_SHA256=322152b8b50b26e5e3a7f6ebaeb75d9c11a747e64bbfd0d8bb1f4d89a031c2b5 7 | RUN wget -q https://github.com/golang/dep/releases/download/${DEP_VERSION}/dep-linux-amd64 -O /usr/local/bin/dep \ 8 | && echo "${DEP_SHA256} /usr/local/bin/dep" | sha256sum -c - \ 9 | && chmod 755 /usr/local/bin/dep 10 | 11 | ENV GOOS=linux CGO_ENABLED=0 12 | WORKDIR /go/src/github.com/discordianfish/nginx_exporter 13 | COPY Gopkg.* *.go ./ 14 | RUN dep ensure --vendor-only \ 15 | && go install 16 | 17 | FROM quay.io/prometheus/busybox:glibc 18 | EXPOSE 9113 19 | COPY --from=0 /go/bin/nginx_exporter /usr/local/bin/ 20 | USER nobody 21 | ENTRYPOINT [ "/usr/local/bin/nginx_exporter" ] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Johannes 'fish' Ziemke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 0.1.0 2 | TAG = $(VERSION) 3 | PREFIX = nginx-exporter 4 | 5 | 6 | GIT_COMMIT = $(shell git rev-parse HEAD) 7 | DATE = $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") 8 | 9 | 10 | .PHONY: nginx-exporter 11 | nginx-exporter: 12 | CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(GIT_COMMIT) -X main.date=$(DATE)" -o nginx-exporter 13 | 14 | .PHONY: build-goreleaser 15 | build-goreleaser: ## Build all binaries using GoReleaser 16 | GOPATH=$(shell go env GOPATH) goreleaser build --rm-dist --snapshot 17 | 18 | .PHONY: deps 19 | deps: 20 | @go mod tidy && go mod verify && go mod download 21 | 22 | .PHONY: clean 23 | clean: 24 | -rm -r dist 25 | -rm nginx-exporter 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nginx Exporter for Prometheus 2 | 3 | This Prometheus Exporter retrieves nginx stats and exports them via HTTP for Prometheus 4 | consumption. 5 | 6 | To run it: 7 | 8 | ```bash 9 | ./nginx_exporter [flags] 10 | ``` 11 | 12 | Help on flags: 13 | ```bash 14 | ./nginx_exporter --help 15 | ``` 16 | 17 | ## Using Docker 18 | 19 | ``` 20 | docker pull fish/nginx-exporter 21 | 22 | docker run -d -p 9113:9113 fish/nginx-exporter \ 23 | -nginx.scrape_uri=http://172.17.42.1/nginx_status 24 | ``` 25 | In production you should use a tagged release: 26 | https://hub.docker.com/r/fish/nginx-exporter/tags/ 27 | 28 | ## Alternatives 29 | While nginx natively only provides the small set of metrics this exporter 30 | provides, [nginx-module-vts](https://github.com/vozlt/nginx-module-vts) 31 | adds extensive metrics that can be consumed by: 32 | 33 | - Standalone Prometheus Exporter: https://github.com/hnlq715/nginx-vts-exporter 34 | - Kubernetes NGINX Ingres controller: https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/customization/custom-vts-metrics-prometheus/README.md 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/discordianfish/nginx_exporter 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/prometheus/client_golang v1.0.0 7 | github.com/prometheus/common v0.6.0 8 | ) 9 | 10 | replace ( 11 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0 => github.com/golang/protobuf v1.4.0 12 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 => github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f 13 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc => google.golang.org/protobuf v1.24.0 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= 2 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= 4 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 5 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 6 | github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= 7 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 12 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 13 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 14 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 15 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 16 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 17 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 18 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 19 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 20 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 21 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 22 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 23 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 24 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 25 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 27 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 28 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 29 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 30 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 33 | github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= 34 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 35 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 36 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 37 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 38 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 39 | github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= 40 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= 41 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 42 | github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= 43 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 44 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 45 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 47 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 48 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 49 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 50 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 51 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 52 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 53 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 54 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 55 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 56 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 57 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 58 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 59 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 60 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 61 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 62 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 63 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 64 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 65 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 66 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 67 | -------------------------------------------------------------------------------- /nginx_exporter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | "sync" 12 | 13 | "github.com/prometheus/client_golang/prometheus" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | "github.com/prometheus/common/log" 16 | ) 17 | 18 | const ( 19 | namespace = "nginx" // For Prometheus metrics. 20 | ) 21 | 22 | var ( 23 | listeningAddress = flag.String("telemetry.address", ":9113", "Address on which to expose metrics.") 24 | metricsEndpoint = flag.String("telemetry.endpoint", "/metrics", "Path under which to expose metrics.") 25 | nginxScrapeURI = flag.String("nginx.scrape_uri", "http://localhost/nginx_status", "URI to nginx stub status page") 26 | insecure = flag.Bool("insecure", true, "Ignore server certificate if using https") 27 | ) 28 | 29 | // Exporter collects nginx stats from the given URI and exports them using 30 | // the prometheus metrics package. 31 | type Exporter struct { 32 | URI string 33 | mutex sync.RWMutex 34 | client *http.Client 35 | 36 | scrapeFailures prometheus.Counter 37 | processedConnections *prometheus.Desc 38 | currentConnections *prometheus.GaugeVec 39 | nginxUp prometheus.Gauge 40 | } 41 | 42 | // NewExporter returns an initialized Exporter. 43 | func NewExporter(uri string) *Exporter { 44 | return &Exporter{ 45 | URI: uri, 46 | scrapeFailures: prometheus.NewCounter(prometheus.CounterOpts{ 47 | Namespace: namespace, 48 | Name: "exporter_scrape_failures_total", 49 | Help: "Number of errors while scraping nginx.", 50 | }), 51 | processedConnections: prometheus.NewDesc( 52 | prometheus.BuildFQName(namespace, "", "connections_processed_total"), 53 | "Number of connections processed by nginx", 54 | []string{"stage"}, 55 | nil, 56 | ), 57 | currentConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 58 | Namespace: namespace, 59 | Name: "connections_current", 60 | Help: "Number of connections currently processed by nginx", 61 | }, 62 | []string{"state"}, 63 | ), 64 | nginxUp: prometheus.NewGauge(prometheus.GaugeOpts{ 65 | Namespace: namespace, 66 | Name: "up", 67 | Help: "Whether the nginx is up.", 68 | }), 69 | client: &http.Client{ 70 | Transport: &http.Transport{ 71 | TLSClientConfig: &tls.Config{InsecureSkipVerify: *insecure}, 72 | }, 73 | }, 74 | } 75 | } 76 | 77 | // Describe describes all the metrics ever exported by the nginx exporter. It 78 | // implements prometheus.Collector. 79 | func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { 80 | ch <- e.processedConnections 81 | e.currentConnections.Describe(ch) 82 | e.nginxUp.Describe(ch) 83 | e.scrapeFailures.Describe(ch) 84 | } 85 | 86 | func (e *Exporter) collect(ch chan<- prometheus.Metric) error { 87 | resp, err := e.client.Get(e.URI) 88 | if err != nil { 89 | e.nginxUp.Set(0) 90 | return fmt.Errorf("Error scraping nginx: %v", err) 91 | } 92 | e.nginxUp.Set(1) 93 | 94 | data, err := ioutil.ReadAll(resp.Body) 95 | resp.Body.Close() 96 | if resp.StatusCode < 200 || resp.StatusCode >= 400 { 97 | if err != nil { 98 | data = []byte(err.Error()) 99 | } 100 | return fmt.Errorf("Status %s (%d): %s", resp.Status, resp.StatusCode, data) 101 | } 102 | 103 | // Parsing results 104 | lines := strings.Split(string(data), "\n") 105 | if len(lines) != 5 { 106 | return fmt.Errorf("Unexpected number of lines in status: %v", lines) 107 | } 108 | 109 | // active connections 110 | parts := strings.Split(lines[0], ":") 111 | if len(parts) != 2 { 112 | return fmt.Errorf("Unexpected first line: %s", lines[0]) 113 | } 114 | v, err := strconv.Atoi(strings.TrimSpace(parts[1])) 115 | if err != nil { 116 | return err 117 | } 118 | e.currentConnections.WithLabelValues("active").Set(float64(v)) 119 | 120 | // processed connections 121 | parts = strings.Fields(lines[2]) 122 | if len(parts) != 3 { 123 | return fmt.Errorf("Unexpected third line: %s", lines[2]) 124 | } 125 | v, err = strconv.Atoi(strings.TrimSpace(parts[0])) 126 | if err != nil { 127 | return err 128 | } 129 | ch <- prometheus.MustNewConstMetric(e.processedConnections, prometheus.CounterValue, float64(v), "accepted") 130 | v, err = strconv.Atoi(strings.TrimSpace(parts[1])) 131 | if err != nil { 132 | return err 133 | } 134 | ch <- prometheus.MustNewConstMetric(e.processedConnections, prometheus.CounterValue, float64(v), "handled") 135 | v, err = strconv.Atoi(strings.TrimSpace(parts[2])) 136 | if err != nil { 137 | return err 138 | } 139 | ch <- prometheus.MustNewConstMetric(e.processedConnections, prometheus.CounterValue, float64(v), "any") 140 | 141 | // current connections 142 | parts = strings.Fields(lines[3]) 143 | if len(parts) != 6 { 144 | return fmt.Errorf("Unexpected fourth line: %s", lines[3]) 145 | } 146 | v, err = strconv.Atoi(strings.TrimSpace(parts[1])) 147 | if err != nil { 148 | return err 149 | } 150 | e.currentConnections.WithLabelValues("reading").Set(float64(v)) 151 | v, err = strconv.Atoi(strings.TrimSpace(parts[3])) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | e.currentConnections.WithLabelValues("writing").Set(float64(v)) 157 | v, err = strconv.Atoi(strings.TrimSpace(parts[5])) 158 | if err != nil { 159 | return err 160 | } 161 | e.currentConnections.WithLabelValues("waiting").Set(float64(v)) 162 | return nil 163 | } 164 | 165 | // Collect fetches the stats from configured nginx location and delivers them 166 | // as Prometheus metrics. It implements prometheus.Collector. 167 | func (e *Exporter) Collect(ch chan<- prometheus.Metric) { 168 | e.mutex.Lock() // To protect metrics from concurrent collects. 169 | defer e.mutex.Unlock() 170 | if err := e.collect(ch); err != nil { 171 | log.Errorf("Error scraping nginx: %s", err) 172 | e.scrapeFailures.Inc() 173 | e.scrapeFailures.Collect(ch) 174 | } 175 | e.currentConnections.Collect(ch) 176 | e.nginxUp.Collect(ch) 177 | return 178 | } 179 | 180 | func main() { 181 | flag.Parse() 182 | 183 | exporter := NewExporter(*nginxScrapeURI) 184 | prometheus.MustRegister(exporter) 185 | 186 | log.Infof("Starting Server: %s", *listeningAddress) 187 | http.Handle(*metricsEndpoint, promhttp.Handler()) 188 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 189 | w.Write([]byte(` 190 | Nginx Exporter 191 | 192 |

Nginx Exporter

193 |

Metrics

194 | 195 | `)) 196 | }) 197 | http.HandleFunc("/-/healthy", func(w http.ResponseWriter, r *http.Request) { 198 | w.WriteHeader(200) 199 | w.Write([]byte("ok")) 200 | }) 201 | log.Fatal(http.ListenAndServe(*listeningAddress, nil)) 202 | } 203 | -------------------------------------------------------------------------------- /nginx_exporter_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | const ( 12 | nginxStatus = `Active connections: 91 13 | server accepts handled requests 14 | 145249 145249 151557 15 | Reading: 0 Writing: 24 Waiting: 66 16 | ` 17 | metricCount = 8 18 | ) 19 | 20 | func TestNginxStatus(t *testing.T) { 21 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 | w.Write([]byte(nginxStatus)) 23 | }) 24 | server := httptest.NewServer(handler) 25 | 26 | e := NewExporter(server.URL) 27 | ch := make(chan prometheus.Metric) 28 | 29 | go func() { 30 | defer close(ch) 31 | e.Collect(ch) 32 | }() 33 | 34 | for i := 1; i <= metricCount; i++ { 35 | m := <-ch 36 | if m == nil { 37 | t.Error("expected metric but got nil") 38 | } 39 | } 40 | if <-ch != nil { 41 | t.Error("expected closed channel") 42 | } 43 | } 44 | --------------------------------------------------------------------------------