Skip to content

Commit e3d3aa8

Browse files
committed
Add WIP middlewares
1 parent 2a46816 commit e3d3aa8

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

prometheus/promhttp/middleware.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2016 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// Copyright (c) 2013, The Prometheus Authors
15+
// All rights reserved.
16+
//
17+
// Use of this source code is governed by a BSD-style license that can be found
18+
// in the LICENSE file.
19+
20+
package promhttp
21+
22+
import (
23+
"context"
24+
"net/http"
25+
"net/http/httptrace"
26+
"time"
27+
28+
"github.com/prometheus/client_golang/prometheus"
29+
)
30+
31+
// ClientTrace adds middleware providing a histogram of outgoing request
32+
// latencies, partitioned by http client, request host and httptrace event.
33+
func ClientTrace(httpClientName string) middlewareFunc {
34+
hist := prometheus.NewHistogramVec(prometheus.HistogramOpts{
35+
Namespace: "outgoing_httptrace",
36+
Name: "duration_seconds",
37+
ConstLabels: prometheus.Labels{"client": httpClientName},
38+
Help: "Histogram of outgoing request latencies.",
39+
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
40+
},
41+
[]string{"host", "event"},
42+
)
43+
44+
if err := prometheus.Register(hist); err != nil {
45+
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
46+
hist = are.ExistingCollector.(*prometheus.HistogramVec)
47+
} else {
48+
panic(err)
49+
}
50+
}
51+
52+
return func(req *http.Request, next Middleware) (*http.Response, error) {
53+
var (
54+
host = req.URL.Host
55+
start = time.Now()
56+
)
57+
58+
trace := &httptrace.ClientTrace{
59+
DNSStart: func(_ httptrace.DNSStartInfo) {
60+
hist.WithLabelValues(host, "DNSStart").Observe(time.Since(start).Seconds())
61+
},
62+
DNSDone: func(_ httptrace.DNSDoneInfo) {
63+
hist.WithLabelValues(host, "DNSDone").Observe(time.Since(start).Seconds())
64+
},
65+
ConnectStart: func(_, _ string) {
66+
hist.WithLabelValues(host, "ConnectStart").Observe(time.Since(start).Seconds())
67+
},
68+
ConnectDone: func(net, addr string, err error) {
69+
if err != nil {
70+
return
71+
}
72+
hist.WithLabelValues(host, "ConnectDone").Observe(time.Since(start).Seconds())
73+
},
74+
GotConn: func(_ httptrace.GotConnInfo) {
75+
hist.WithLabelValues(host, "GotConn").Observe(time.Since(start).Seconds())
76+
},
77+
GotFirstResponseByte: func() {
78+
hist.WithLabelValues(host, "GotFirstResponseByte").Observe(time.Since(start).Seconds())
79+
},
80+
}
81+
req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace))
82+
83+
return next(req)
84+
}
85+
}
86+
87+
// InFlight is middleware that instruments number of open requests partitioned
88+
// by http client and request host.
89+
var InFlight = func(httpClientName string) middlewareFunc {
90+
gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
91+
Namespace: "outgoing",
92+
Name: "open_requests",
93+
ConstLabels: prometheus.Labels{"client": httpClientName},
94+
Help: "Gauge of open outgoing requests.",
95+
},
96+
[]string{"host"},
97+
)
98+
99+
if err := prometheus.Register(gauge); err != nil {
100+
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
101+
gauge = are.ExistingCollector.(*prometheus.GaugeVec)
102+
} else {
103+
panic(err)
104+
}
105+
}
106+
107+
return func(req *http.Request, next Middleware) (*http.Response, error) {
108+
host := req.URL.Host
109+
gauge.WithLabelValues(host).Inc()
110+
111+
resp, err := next(req)
112+
113+
gauge.WithLabelValues(host).Dec()
114+
115+
return resp, err
116+
}
117+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2016 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// Copyright (c) 2013, The Prometheus Authors
15+
// All rights reserved.
16+
//
17+
// Use of this source code is governed by a BSD-style license that can be found
18+
// in the LICENSE file.
19+
20+
package promhttp
21+
22+
import (
23+
"log"
24+
"net/http"
25+
"testing"
26+
"time"
27+
28+
"github.com/prometheus/client_golang/prometheus"
29+
)
30+
31+
func TestClientTraceMiddleware(t *testing.T) {}
32+
33+
func ExampleMiddleware() {
34+
t := time.NewTicker(time.Second)
35+
defer t.Stop()
36+
37+
client := *http.DefaultClient
38+
client.Timeout = 500 * time.Millisecond
39+
40+
counter := prometheus.NewCounter(prometheus.CounterOpts{
41+
Name: "request_length_counter",
42+
Help: "Counter of request length.",
43+
})
44+
prometheus.MustRegister(counter)
45+
customMiddleware := func(req *http.Request, next Middleware) (*http.Response, error) {
46+
counter.Add(float64(req.ContentLength))
47+
48+
return next(req)
49+
}
50+
51+
promclient, err := NewClient(
52+
SetClient(client),
53+
SetMiddleware(ClientTrace("example_client"), customMiddleware, InFlight("example_client")),
54+
)
55+
if err != nil {
56+
log.Fatalln(err)
57+
}
58+
59+
go func() {
60+
if err := http.ListenAndServe(":3000", prometheus.Handler()); err != nil {
61+
log.Fatalln(err)
62+
}
63+
}()
64+
65+
for {
66+
select {
67+
case <-t.C:
68+
log.Println("sending GET")
69+
resp, err := promclient.Get("http://example.com")
70+
if err != nil {
71+
log.Fatalln(err)
72+
}
73+
resp.Body.Close()
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)