Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
discordianfish committed Aug 15, 2014
0 parents commit 2a4d6b4
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nginx_exporter
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Nginx Exporter for Prometheus

This is a simple server that periodically scrapes nginx stats and exports them via HTTP for Prometheus
consumption.

To run it:

```bash
go run nginx_exporter [flags]
```

Help on flags:
```bash
go run nginx_exporter --help
```

# Getting Started
* All of the core developers are accessible via the [Prometheus Developers Mailinglist](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers).
180 changes: 180 additions & 0 deletions nginx_exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package main

import (
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"strconv"
"strings"
"sync"

"github.com/prometheus/client_golang/prometheus"
)

const (
namespace = "nginx" // For Prometheus metrics.
)

var (
listeningAddress = flag.String("telemetry.address", ":8080", "Address on which to expose metrics.")
metricsEndpoint = flag.String("telemetry.endpoint", "/metrics", "Path under which to expose metrics.")
nginxScrapeURI = flag.String("nginx.scrape_uri", "https://localhost/nginx_status", "URI to nginx stub status page")
insecure = flag.Bool("insecure", true, "Ignore server certificate if using https")
)

// Exporter collects nginx stats from the given URI and exports them using
// the prometheus metrics package.
type Exporter struct {
URI string
mutex sync.RWMutex
client *http.Client

scrapeFailures prometheus.Counter
processedConnections *prometheus.CounterVec
currentConnections *prometheus.GaugeVec
}

// NewExporter returns an initialized Exporter.
func NewExporter(uri string) *Exporter {
return &Exporter{
URI: uri,
scrapeFailures: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Name: "exporter_scrape_failures",
Help: "Number of errors while scraping nginx.",
}),
processedConnections: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Name: "connections_processed",
Help: "Number of connections processed by nginx",
},
[]string{"stage"},
),
currentConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "connections_current",
Help: "Number of connections currently processed by nginx",
},
[]string{"state"},
),
client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: *insecure},
},
},
}
}

// Describe describes all the metrics ever exported by the nginx exporter. It
// implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
e.processedConnections.Describe(ch)
e.currentConnections.Describe(ch)
e.scrapeFailures.Describe(ch)
}

func (e *Exporter) collect(ch chan<- prometheus.Metric) error {
resp, err := e.client.Get(e.URI)
if err != nil {
return fmt.Errorf("Error scraping nginx: %v", err)
}

data, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
if err != nil {
data = []byte(err.Error())
}
return fmt.Errorf("Status %s (%d): %s", resp.Status, resp.StatusCode, data)
}

// Parsing results
lines := strings.Split(string(data), "\n")
if len(lines) != 5 {
return fmt.Errorf("Unexpected number of lines in status: %v", lines)
}

// active connections
parts := strings.Split(lines[0], ":")
if len(parts) != 2 {
return fmt.Errorf("Unexpected first line: %s", lines[0])
}
v, err := strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil {
return err
}
e.currentConnections.WithLabelValues("active").Set(float64(v))

// processed connections
parts = strings.Fields(lines[2])
if len(parts) != 3 {
return fmt.Errorf("Unexpected third line: %s", lines[2])
}
v, err = strconv.Atoi(strings.TrimSpace(parts[0]))
if err != nil {
return err
}
e.processedConnections.WithLabelValues("accepted").Set(float64(v))
v, err = strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil {
return err
}
e.processedConnections.WithLabelValues("handled").Set(float64(v))
v, err = strconv.Atoi(strings.TrimSpace(parts[2]))
if err != nil {
return err
}
e.processedConnections.WithLabelValues("any").Set(float64(v))

// current connections
parts = strings.Fields(lines[3])
if len(parts) != 6 {
return fmt.Errorf("Unexpected fourth line: %s", lines[3])
}
v, err = strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil {
return err
}
e.currentConnections.WithLabelValues("reading").Set(float64(v))
v, err = strconv.Atoi(strings.TrimSpace(parts[3]))
if err != nil {
return err
}

e.currentConnections.WithLabelValues("writing").Set(float64(v))
v, err = strconv.Atoi(strings.TrimSpace(parts[5]))
if err != nil {
return err
}
e.currentConnections.WithLabelValues("waiting").Set(float64(v))
return nil
}

// Collect fetches the stats from configured nginx location and delivers them
// as Prometheus metrics. It implements prometheus.Collector.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
e.mutex.Lock() // To protect metrics from concurrent collects.
defer e.mutex.Unlock()
if err := e.collect(ch); err != nil {
log.Printf("Error scraping nginx: %s", err)
e.scrapeFailures.Inc()
e.scrapeFailures.Collect(ch)
}
e.processedConnections.Collect(ch)
e.currentConnections.Collect(ch)
return
}

func main() {
flag.Parse()

exporter := NewExporter(*nginxScrapeURI)
prometheus.MustRegister(exporter)

log.Printf("Starting Server: %s", *listeningAddress)
http.Handle(*metricsEndpoint, prometheus.Handler())
log.Fatal(http.ListenAndServe(*listeningAddress, nil))
}
43 changes: 43 additions & 0 deletions nginx_exporter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/prometheus/client_golang/prometheus"
)

const (
nginxStatus = `Active connections: 91
server accepts handled requests
145249 145249 151557
Reading: 0 Writing: 24 Waiting: 66
`
metricCount = 7
)

func TestNginxStatus(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(nginxStatus))
})
server := httptest.NewServer(handler)

e := NewExporter(server.URL)
ch := make(chan prometheus.Metric)

go func() {
defer close(ch)
e.Collect(ch)
}()

for i := 1; i <= metricCount; i++ {
m := <-ch
if m == nil {
t.Error("expected metric but got nil")
}
}
if <-ch != nil {
t.Error("expected closed channel")
}
}

0 comments on commit 2a4d6b4

Please sign in to comment.