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

Commit 5166276

Browse files
committed
Merge pull request #58 from alicebob/unix
support for 'stats' unix socket
2 parents b70afc3 + 6952fbb commit 5166276

File tree

3 files changed

+289
-35
lines changed

3 files changed

+289
-35
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ If your stats port is protected by [basic auth](https://cbonte.github.io/haproxy
4747
haproxy_exporter -haproxy.scrape-uri="http://user:pass@haproxy.example.com/haproxy?stats;csv"
4848
```
4949

50+
## Unix Sockets
51+
52+
As alternative to localhost HTTP a stats socket can be used. Enable the stats
53+
socket in HAProxy with for example:
54+
```
55+
stats socket /run/haproxy/admin.sock mode 660 level admin
56+
```
57+
58+
The scrape URL uses the 'unix:' scheme:
59+
```bash
60+
haproxy_exporter -haproxy.scrape-uri=unix:/run/haproxy/admin.sock
61+
```
62+
5063
## Docker
5164

5265
To run the haproxy exporter as a Docker container, run:

haproxy_exporter.go

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package main
22

33
import (
44
"encoding/csv"
5+
"errors"
56
"flag"
67
"fmt"
78
"io"
89
"io/ioutil"
910
"net"
1011
"net/http"
1112
_ "net/http/pprof"
13+
"net/url"
1214
"os"
1315
"sort"
1416
"strconv"
@@ -123,17 +125,33 @@ var (
123125
type Exporter struct {
124126
URI string
125127
mutex sync.RWMutex
128+
fetch func() (io.ReadCloser, error)
126129

127130
up prometheus.Gauge
128131
totalScrapes, csvParseFailures prometheus.Counter
129132
frontendMetrics, backendMetrics, serverMetrics map[int]*prometheus.GaugeVec
130-
client *http.Client
131133
}
132134

133135
// NewExporter returns an initialized Exporter.
134-
func NewExporter(uri string, selectedServerMetrics map[int]*prometheus.GaugeVec, timeout time.Duration) *Exporter {
136+
func NewExporter(uri string, selectedServerMetrics map[int]*prometheus.GaugeVec, timeout time.Duration) (*Exporter, error) {
137+
u, err := url.Parse(uri)
138+
if err != nil {
139+
return nil, err
140+
}
141+
142+
var fetch func() (io.ReadCloser, error)
143+
switch u.Scheme {
144+
case "http", "https", "file":
145+
fetch = fetchHTTP(uri, timeout)
146+
case "unix":
147+
fetch = fetchUnix(u, timeout)
148+
default:
149+
return nil, fmt.Errorf("unsupported scheme: %q", u.Scheme)
150+
}
151+
135152
return &Exporter{
136-
URI: uri,
153+
URI: uri,
154+
fetch: fetch,
137155
up: prometheus.NewGauge(prometheus.GaugeOpts{
138156
Namespace: namespace,
139157
Name: "up",
@@ -194,21 +212,7 @@ func NewExporter(uri string, selectedServerMetrics map[int]*prometheus.GaugeVec,
194212
44: newBackendMetric("http_responses_total", "Total of HTTP responses.", prometheus.Labels{"code": "other"}),
195213
},
196214
serverMetrics: selectedServerMetrics,
197-
client: &http.Client{
198-
Transport: &http.Transport{
199-
Dial: func(netw, addr string) (net.Conn, error) {
200-
c, err := net.DialTimeout(netw, addr, timeout)
201-
if err != nil {
202-
return nil, err
203-
}
204-
if err := c.SetDeadline(time.Now().Add(timeout)); err != nil {
205-
return nil, err
206-
}
207-
return c, nil
208-
},
209-
},
210-
},
211-
}
215+
}, nil
212216
}
213217

214218
// Describe describes all the metrics ever exported by the HAProxy exporter. It
@@ -243,24 +247,72 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
243247
e.collectMetrics(ch)
244248
}
245249

250+
func fetchHTTP(uri string, timeout time.Duration) func() (io.ReadCloser, error) {
251+
client := http.Client{
252+
Transport: &http.Transport{
253+
Dial: func(netw, addr string) (net.Conn, error) {
254+
c, err := net.DialTimeout(netw, addr, timeout)
255+
if err != nil {
256+
return nil, err
257+
}
258+
if err := c.SetDeadline(time.Now().Add(timeout)); err != nil {
259+
return nil, err
260+
}
261+
return c, nil
262+
},
263+
},
264+
}
265+
266+
return func() (io.ReadCloser, error) {
267+
resp, err := client.Get(uri)
268+
if err != nil {
269+
return nil, err
270+
}
271+
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
272+
resp.Body.Close()
273+
return nil, fmt.Errorf("HTTP status %d", resp.StatusCode)
274+
}
275+
return resp.Body, nil
276+
}
277+
}
278+
279+
func fetchUnix(u *url.URL, timeout time.Duration) func() (io.ReadCloser, error) {
280+
return func() (io.ReadCloser, error) {
281+
f, err := net.DialTimeout("unix", u.Path, timeout)
282+
if err != nil {
283+
return nil, err
284+
}
285+
if err := f.SetDeadline(time.Now().Add(timeout)); err != nil {
286+
f.Close()
287+
return nil, err
288+
}
289+
cmd := "show stat\n"
290+
n, err := io.WriteString(f, cmd)
291+
if err != nil {
292+
f.Close()
293+
return nil, err
294+
}
295+
if n != len(cmd) {
296+
f.Close()
297+
return nil, errors.New("write error")
298+
}
299+
return f, nil
300+
}
301+
}
302+
246303
func (e *Exporter) scrape() {
247304
e.totalScrapes.Inc()
248305

249-
resp, err := e.client.Get(e.URI)
306+
body, err := e.fetch()
250307
if err != nil {
251308
e.up.Set(0)
252309
log.Errorf("Can't scrape HAProxy: %v", err)
253310
return
254311
}
255-
defer resp.Body.Close()
256-
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
257-
e.up.Set(0)
258-
log.Errorf("Can't scrape HAProxy: status %d", resp.StatusCode)
259-
return
260-
}
312+
defer body.Close()
261313
e.up.Set(1)
262314

263-
reader := csv.NewReader(resp.Body)
315+
reader := csv.NewReader(body)
264316
reader.TrailingComma = true
265317
reader.Comment = '#'
266318

@@ -277,7 +329,7 @@ loop:
277329
continue loop
278330
default:
279331
log.Errorf("Unexpected error while reading CSV: %v", err)
280-
e.csvParseFailures.Inc()
332+
e.up.Set(0)
281333
break loop
282334
}
283335
e.parseRow(row)
@@ -418,7 +470,10 @@ func main() {
418470
log.Infoln("Starting haproxy_exporter", version.Info())
419471
log.Infoln("Build context", version.BuildContext())
420472

421-
exporter := NewExporter(*haProxyScrapeURI, selectedServerMetrics, *haProxyTimeout)
473+
exporter, err := NewExporter(*haProxyScrapeURI, selectedServerMetrics, *haProxyTimeout)
474+
if err != nil {
475+
log.Fatal(err)
476+
}
422477
prometheus.MustRegister(exporter)
423478
prometheus.MustRegister(version.NewCollector("haproxy_exporter"))
424479

0 commit comments

Comments
 (0)