Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
Merge pull request #41 from prometheus/disable-server-metrics
Browse files Browse the repository at this point in the history
Disable server metrics
  • Loading branch information
grobie committed Dec 23, 2015
2 parents 3feed0a + 0e021d3 commit 1faf9dd
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 55 deletions.
15 changes: 7 additions & 8 deletions Makefile.COMMON
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,9 @@ SRC ?= $(shell find . -type f -name "*.go" ! -path "./.build/*")
GOOS ?= $(shell uname | tr A-Z a-z)
GOARCH ?= $(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m)))

ifeq ($(GOOS),darwin)
RELEASE_SUFFIX ?= -osx$(shell sw_vers -productVersion)
endif

GO_VERSION ?= 1.4.2
GO_VERSION ?= 1.5.2
GOURL ?= https://golang.org/dl
GOPKG ?= go$(GO_VERSION).$(GOOS)-$(GOARCH)$(RELEASE_SUFFIX).tar.gz
GOPKG ?= go$(GO_VERSION).$(GOOS)-$(GOARCH).tar.gz
GOPATH := $(CURDIR)/.build/gopath

# Check for the correct version of go in the path. If we find it, use it.
Expand Down Expand Up @@ -83,8 +79,11 @@ $(GOCC):
@echo Abort now if you want to manually install it system-wide instead.
@echo
@sleep 5
mkdir -p $(GOROOT)
curl -L $(GOURL)/$(GOPKG) | tar -C $(GOROOT) --strip 1 -xz
mkdir -p .build
# The archive contains a single directory called 'go/'.
curl -L $(GOURL)/$(GOPKG) | tar -C .build -xzf -
rm -rf $(GOROOT)
mv .build/go $(GOROOT)

$(SELFLINK):
mkdir -p $(dir $@)
Expand Down
113 changes: 72 additions & 41 deletions haproxy_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import (
"net"
"net/http"
_ "net/http/pprof"
"sort"
"strconv"
"strings"
"sync"
"time"

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

const (
Expand Down Expand Up @@ -71,34 +72,23 @@ func newServerMetric(metricName string, docString string, constLabels prometheus
)
}

// Exporter collects HAProxy stats from the given URI and exports them using
// the prometheus metrics package.
type Exporter struct {
URI string
mutex sync.RWMutex

up prometheus.Gauge
totalScrapes, csvParseFailures prometheus.Counter
frontendMetrics, backendMetrics, serverMetrics map[int]*prometheus.GaugeVec
client *http.Client
}

// NewExporter returns an initialized Exporter.
func NewExporter(uri string, haProxyServerMetricFields string, timeout time.Duration) *Exporter {
serverMetrics := map[int]*prometheus.GaugeVec{}
type metrics map[int]*prometheus.GaugeVec

serverMetricFields := make(map[int]bool)
if haProxyServerMetricFields != "" {
for _, f := range strings.Split(haProxyServerMetricFields, ",") {
field, err := strconv.Atoi(f)
if err != nil {
log.Fatalf("Invalid field number: %v", f)
}
serverMetricFields[field] = true
}
func (m metrics) String() string {
keys := make([]int, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
s := make([]string, len(keys))
for i, k := range keys {
s[i] = strconv.Itoa(k)
}
return strings.Join(s, ",")
}

for index, metric := range map[int]*prometheus.GaugeVec{
var (
serverMetrics = metrics{
4: newServerMetric("current_sessions", "Current number of active sessions.", nil),
5: newServerMetric("max_sessions", "Maximum observed number of active sessions.", nil),
7: newServerMetric("connections_total", "Total number of connections.", nil),
Expand All @@ -119,12 +109,23 @@ func NewExporter(uri string, haProxyServerMetricFields string, timeout time.Dura
42: newServerMetric("http_responses_total", "Total of HTTP responses.", prometheus.Labels{"code": "4xx"}),
43: newServerMetric("http_responses_total", "Total of HTTP responses.", prometheus.Labels{"code": "5xx"}),
44: newServerMetric("http_responses_total", "Total of HTTP responses.", prometheus.Labels{"code": "other"}),
} {
if len(serverMetricFields) == 0 || serverMetricFields[index] {
serverMetrics[index] = metric
}
}
)

// Exporter collects HAProxy stats from the given URI and exports them using
// the prometheus metrics package.
type Exporter struct {
URI string
mutex sync.RWMutex

up prometheus.Gauge
totalScrapes, csvParseFailures prometheus.Counter
frontendMetrics, backendMetrics, serverMetrics map[int]*prometheus.GaugeVec
client *http.Client
}

// NewExporter returns an initialized Exporter.
func NewExporter(uri string, selectedServerMetrics map[int]*prometheus.GaugeVec, timeout time.Duration) *Exporter {
return &Exporter{
URI: uri,
up: prometheus.NewGauge(prometheus.GaugeOpts{
Expand Down Expand Up @@ -186,7 +187,7 @@ func NewExporter(uri string, haProxyServerMetricFields string, timeout time.Dura
43: newBackendMetric("http_responses_total", "Total of HTTP responses.", prometheus.Labels{"code": "5xx"}),
44: newBackendMetric("http_responses_total", "Total of HTTP responses.", prometheus.Labels{"code": "other"}),
},
serverMetrics: serverMetrics,
serverMetrics: selectedServerMetrics,
client: &http.Client{
Transport: &http.Transport{
Dial: func(netw, addr string) (net.Conn, error) {
Expand Down Expand Up @@ -246,7 +247,7 @@ func (e *Exporter) scrape(csvRows chan<- []string) {
resp, err := e.client.Get(e.URI)
if err != nil {
e.up.Set(0)
log.Printf("Error while scraping HAProxy: %v", err)
log.Errorf("Can't scrape HAProxy: %v", err)
return
}
defer resp.Body.Close()
Expand All @@ -262,7 +263,7 @@ func (e *Exporter) scrape(csvRows chan<- []string) {
break
}
if err != nil {
log.Printf("Error while reading CSV: %v", err)
log.Errorf("Can't read CSV: %v", err)
e.csvParseFailures.Inc()
break
}
Expand Down Expand Up @@ -300,7 +301,7 @@ func (e *Exporter) collectMetrics(metrics chan<- prometheus.Metric) {
func (e *Exporter) setMetrics(csvRows <-chan []string) {
for csvRow := range csvRows {
if len(csvRow) < expectedCsvFieldCount {
log.Printf("Wrong CSV field count: %d vs. %d", len(csvRow), expectedCsvFieldCount)
log.Errorf("Wrong CSV field count: %d vs. %d", len(csvRow), expectedCsvFieldCount)
e.csvParseFailures.Inc()
continue
}
Expand Down Expand Up @@ -351,7 +352,7 @@ func (e *Exporter) exportCsvFields(metrics map[int]*prometheus.GaugeVec, csvRow
var err error
value, err = strconv.ParseInt(valueStr, 10, 64)
if err != nil {
log.Printf("Error while parsing CSV field value %s: %v", valueStr, err)
log.Errorf("Can't parse CSV field value %s: %v", valueStr, err)
e.csvParseFailures.Inc()
continue
}
Expand All @@ -360,37 +361,67 @@ func (e *Exporter) exportCsvFields(metrics map[int]*prometheus.GaugeVec, csvRow
}
}

// filterServerMetrics returns the set of server metrics specified by the comma
// separated filter.
func filterServerMetrics(filter string) (map[int]*prometheus.GaugeVec, error) {
metrics := map[int]*prometheus.GaugeVec{}
if len(filter) == 0 {
return metrics, nil
}

selected := map[int]struct{}{}
for _, f := range strings.Split(filter, ",") {
field, err := strconv.Atoi(f)
if err != nil {
return nil, fmt.Errorf("invalid server metric field number: %v", f)
}
selected[field] = struct{}{}
}

for field, metric := range serverMetrics {
if _, ok := selected[field]; ok {
metrics[field] = metric
}
}
return metrics, nil
}

func main() {
var (
listenAddress = flag.String("web.listen-address", ":9101", "Address to listen on for web interface and telemetry.")
metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
haProxyScrapeUri = flag.String("haproxy.scrape-uri", "http://localhost/;csv", "URI on which to scrape HAProxy.")
haProxyServerMetricFields = flag.String("haproxy.server-metric-fields", "", "If specified, only export the given csv fields. Comma-seperated list of numbers. See http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#9.1")
haProxyScrapeURI = flag.String("haproxy.scrape-uri", "http://localhost/;csv", "URI on which to scrape HAProxy.")
haProxyServerMetricFields = flag.String("haproxy.server-metric-fields", serverMetrics.String(), "Comma-seperated list of exported server metrics. See http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#9.1")
haProxyTimeout = flag.Duration("haproxy.timeout", 5*time.Second, "Timeout for trying to get stats from HAProxy.")
haProxyPidFile = flag.String("haproxy.pid-file", "", "Path to haproxy's pid file.")
)
flag.Parse()

exporter := NewExporter(*haProxyScrapeUri, *haProxyServerMetricFields, *haProxyTimeout)
selectedServerMetrics, err := filterServerMetrics(*haProxyServerMetricFields)
if err != nil {
log.Fatal(err)
}

exporter := NewExporter(*haProxyScrapeURI, selectedServerMetrics, *haProxyTimeout)
prometheus.MustRegister(exporter)

if *haProxyPidFile != "" {
procExporter := prometheus.NewProcessCollectorPIDFn(
func() (int, error) {
content, err := ioutil.ReadFile(*haProxyPidFile)
if err != nil {
return 0, fmt.Errorf("error reading pid file: %s", err)
return 0, fmt.Errorf("Can't read pid file: %s", err)
}
value, err := strconv.Atoi(strings.TrimSpace(string(content)))
if err != nil {
return 0, fmt.Errorf("error parsing pid file: %s", err)
return 0, fmt.Errorf("Can't parse pid file: %s", err)
}
return value, nil
}, namespace)
prometheus.MustRegister(procExporter)
}

log.Printf("Starting Server: %s", *listenAddress)
log.Infof("Starting Server: %s", *listenAddress)
http.Handle(*metricsPath, prometheus.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
Expand Down
39 changes: 33 additions & 6 deletions haproxy_exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"runtime"
"testing"
"time"
Expand Down Expand Up @@ -54,7 +55,7 @@ func TestInvalidConfig(t *testing.T) {
h := newHaproxy([]byte("not,enough,fields"))
defer h.Close()

e := NewExporter(h.URL, "", 5*time.Second)
e := NewExporter(h.URL, serverMetrics, 5*time.Second)
ch := make(chan prometheus.Metric)

go func() {
Expand Down Expand Up @@ -83,7 +84,7 @@ func TestServerWithoutChecks(t *testing.T) {
h := newHaproxy([]byte("test,127.0.0.1:8080,0,0,0,0,,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,1,1,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,"))
defer h.Close()

e := NewExporter(h.URL, "", 5*time.Second)
e := NewExporter(h.URL, serverMetrics, 5*time.Second)
ch := make(chan prometheus.Metric)

go func() {
Expand Down Expand Up @@ -112,7 +113,7 @@ func TestConfigChangeDetection(t *testing.T) {
h := newHaproxy([]byte(""))
defer h.Close()

e := NewExporter(h.URL, "", 5*time.Second)
e := NewExporter(h.URL, serverMetrics, 5*time.Second)
ch := make(chan prometheus.Metric)

go func() {
Expand All @@ -139,7 +140,7 @@ func TestDeadline(t *testing.T) {
s.Close()
}()

e := NewExporter(s.URL, "", 1*time.Second)
e := NewExporter(s.URL, serverMetrics, 1*time.Second)
ch := make(chan prometheus.Metric)
go func() {
defer close(ch)
Expand Down Expand Up @@ -182,7 +183,33 @@ func TestParseStatusField(t *testing.T) {

for _, tt := range tests {
if have := parseStatusField(tt.input); tt.want != have {
t.Errorf("want status value %d for input %s, have %s",
t.Errorf("want status value %d for input %s, have %d",
tt.want,
tt.input,
have,
)
}
}
}

func TestFilterServerMetrics(t *testing.T) {
tests := []struct {
input string
want map[int]*prometheus.GaugeVec
}{
{input: "", want: map[int]*prometheus.GaugeVec{}},
{input: "8", want: map[int]*prometheus.GaugeVec{8: serverMetrics[8]}},
{input: serverMetrics.String(), want: serverMetrics},
}

for _, tt := range tests {
have, err := filterServerMetrics(tt.input)
if err != nil {
t.Errorf("unexpected error for input %s: %s", tt.input, err)
continue
}
if !reflect.DeepEqual(tt.want, have) {
t.Errorf("want filtered metrics %s for input %s, have %s",
tt.want,
tt.input,
have,
Expand All @@ -200,7 +227,7 @@ func BenchmarkExtract(b *testing.B) {
h := newHaproxy(config)
defer h.Close()

e := NewExporter(h.URL, "", 5*time.Second)
e := NewExporter(h.URL, serverMetrics, 5*time.Second)

var before, after runtime.MemStats
runtime.GC()
Expand Down

0 comments on commit 1faf9dd

Please sign in to comment.