@@ -2,13 +2,15 @@ package main
2
2
3
3
import (
4
4
"encoding/csv"
5
+ "errors"
5
6
"flag"
6
7
"fmt"
7
8
"io"
8
9
"io/ioutil"
9
10
"net"
10
11
"net/http"
11
12
_ "net/http/pprof"
13
+ "net/url"
12
14
"os"
13
15
"sort"
14
16
"strconv"
@@ -123,17 +125,33 @@ var (
123
125
type Exporter struct {
124
126
URI string
125
127
mutex sync.RWMutex
128
+ fetch func () (io.ReadCloser , error )
126
129
127
130
up prometheus.Gauge
128
131
totalScrapes , csvParseFailures prometheus.Counter
129
132
frontendMetrics , backendMetrics , serverMetrics map [int ]* prometheus.GaugeVec
130
- client * http.Client
131
133
}
132
134
133
135
// 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
+
135
152
return & Exporter {
136
- URI : uri ,
153
+ URI : uri ,
154
+ fetch : fetch ,
137
155
up : prometheus .NewGauge (prometheus.GaugeOpts {
138
156
Namespace : namespace ,
139
157
Name : "up" ,
@@ -194,21 +212,7 @@ func NewExporter(uri string, selectedServerMetrics map[int]*prometheus.GaugeVec,
194
212
44 : newBackendMetric ("http_responses_total" , "Total of HTTP responses." , prometheus.Labels {"code" : "other" }),
195
213
},
196
214
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
212
216
}
213
217
214
218
// Describe describes all the metrics ever exported by the HAProxy exporter. It
@@ -243,24 +247,72 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
243
247
e .collectMetrics (ch )
244
248
}
245
249
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
+
246
303
func (e * Exporter ) scrape () {
247
304
e .totalScrapes .Inc ()
248
305
249
- resp , err := e .client . Get ( e . URI )
306
+ body , err := e .fetch ( )
250
307
if err != nil {
251
308
e .up .Set (0 )
252
309
log .Errorf ("Can't scrape HAProxy: %v" , err )
253
310
return
254
311
}
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 ()
261
313
e .up .Set (1 )
262
314
263
- reader := csv .NewReader (resp . Body )
315
+ reader := csv .NewReader (body )
264
316
reader .TrailingComma = true
265
317
reader .Comment = '#'
266
318
@@ -277,7 +329,7 @@ loop:
277
329
continue loop
278
330
default :
279
331
log .Errorf ("Unexpected error while reading CSV: %v" , err )
280
- e .csvParseFailures . Inc ( )
332
+ e .up . Set ( 0 )
281
333
break loop
282
334
}
283
335
e .parseRow (row )
@@ -418,7 +470,10 @@ func main() {
418
470
log .Infoln ("Starting haproxy_exporter" , version .Info ())
419
471
log .Infoln ("Build context" , version .BuildContext ())
420
472
421
- exporter := NewExporter (* haProxyScrapeURI , selectedServerMetrics , * haProxyTimeout )
473
+ exporter , err := NewExporter (* haProxyScrapeURI , selectedServerMetrics , * haProxyTimeout )
474
+ if err != nil {
475
+ log .Fatal (err )
476
+ }
422
477
prometheus .MustRegister (exporter )
423
478
prometheus .MustRegister (version .NewCollector ("haproxy_exporter" ))
424
479
0 commit comments