Skip to content

Commit b5a3eb4

Browse files
committed
Add custom metrics
Custom metrics are added for HTTP traffic and exposed by default on port 8081. This has been tested more extensively in the classic watchdog and copied / duplicated into the of-watchdog. Local e2e testing was done on MacOS outside of a container and the new http_* fields and metrics were reported as expected. Signed-off-by: Alex Ellis <[email protected]>
1 parent ac8051c commit b5a3eb4

File tree

124 files changed

+33036
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+33036
-5
lines changed

Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
FROM golang:1.9.4
1+
FROM golang:1.10
22

33
RUN mkdir -p /go/src/github.com/openfaas-incubator/of-watchdog
44
WORKDIR /go/src/github.com/openfaas-incubator/of-watchdog
55

6-
COPY main.go .
6+
COPY vendor vendor
77
COPY config config
88
COPY executor executor
9+
COPY metrics metrics
10+
COPY main.go .
911

1012
# Run a gofmt and exclude all vendored code.
1113
RUN test -z "$(gofmt -l $(find . -type f -name '*.go' -not -path "./vendor/*"))"

Gopkg.lock

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Gopkg.toml example
2+
#
3+
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
4+
# for detailed Gopkg.toml documentation.
5+
#
6+
# required = ["github.com/user/thing/cmd/thing"]
7+
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
8+
#
9+
# [[constraint]]
10+
# name = "github.com/user/project"
11+
# version = "1.0.0"
12+
#
13+
# [[constraint]]
14+
# name = "github.com/user/project2"
15+
# branch = "dev"
16+
# source = "github.com/myfork/project2"
17+
#
18+
# [[override]]
19+
# name = "github.com/x/y"
20+
# version = "2.4.0"
21+
#
22+
# [prune]
23+
# non-go = false
24+
# go-tests = true
25+
# unused-packages = true
26+
27+
28+
[[constraint]]
29+
name = "github.com/prometheus/client_golang"
30+
version = "0.9.2"
31+
32+
[prune]
33+
go-tests = true
34+
unused-packages = true

config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ type WatchdogConfig struct {
2525
// to prevent transfer type of chunked encoding
2626
// which some servers do not support.
2727
BufferHTTPBody bool
28+
29+
// MetricsPort TCP port on which to serve HTTP Prometheus metrics
30+
MetricsPort int
2831
}
2932

3033
// Process returns a string for the process and a slice for the arguments from the FunctionProcess.
@@ -77,6 +80,7 @@ func New(env []string) (WatchdogConfig, error) {
7780
SuppressLock: getBool(envMap, "suppress_lock"),
7881
UpstreamURL: upstreamURL,
7982
BufferHTTPBody: getBool(envMap, "buffer_http"),
83+
MetricsPort: 8081,
8084
}
8185

8286
if val := envMap["mode"]; len(val) > 0 {

main.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"syscall"
1616
"time"
1717

18+
"github.com/openfaas-incubator/of-watchdog/metrics"
19+
1820
"github.com/openfaas-incubator/of-watchdog/config"
1921
"github.com/openfaas-incubator/of-watchdog/executor"
2022
)
@@ -41,11 +43,19 @@ func main() {
4143

4244
log.Printf("OperationalMode: %s\n", config.WatchdogMode(watchdogConfig.OperationalMode))
4345

44-
http.HandleFunc("/", requestHandler)
46+
httpMetrics := metrics.NewHttp()
47+
http.HandleFunc("/", metrics.InstrumentHandler(requestHandler, httpMetrics))
48+
4549
http.HandleFunc("/_/health", makeHealthHandler())
4650

47-
shutdownTimeout := watchdogConfig.HTTPWriteTimeout
51+
metricsServer := metrics.MetricsServer{}
52+
metricsServer.Register(watchdogConfig.MetricsPort)
53+
54+
cancel := make(chan bool)
4855

56+
go metricsServer.Serve(cancel)
57+
58+
shutdownTimeout := watchdogConfig.HTTPWriteTimeout
4959
s := &http.Server{
5060
Addr: fmt.Sprintf(":%d", watchdogConfig.TCPPort),
5161
ReadTimeout: watchdogConfig.HTTPReadTimeout,
@@ -54,7 +64,6 @@ func main() {
5464
}
5565

5666
listenUntilShutdown(shutdownTimeout, s, watchdogConfig.SuppressLock)
57-
5867
}
5968

6069
func markUnhealthy() error {

metrics/http.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package metrics
2+
3+
import (
4+
"github.com/prometheus/client_golang/prometheus"
5+
"github.com/prometheus/client_golang/prometheus/promauto"
6+
)
7+
8+
type Http struct {
9+
RequestsTotal *prometheus.CounterVec
10+
RequestDurationHistogram *prometheus.HistogramVec
11+
}
12+
13+
func NewHttp() Http {
14+
return Http{
15+
RequestsTotal: promauto.NewCounterVec(prometheus.CounterOpts{
16+
Subsystem: "http",
17+
Name: "requests_total",
18+
Help: "total HTTP requests processed",
19+
}, []string{"code", "method"}),
20+
RequestDurationHistogram: promauto.NewHistogramVec(prometheus.HistogramOpts{
21+
Subsystem: "http",
22+
Name: "request_duration_seconds",
23+
Help: "Seconds spent serving HTTP requests.",
24+
Buckets: prometheus.DefBuckets,
25+
}, []string{"code", "method"}),
26+
}
27+
}

metrics/metrics.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package metrics
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
"time"
9+
10+
"github.com/prometheus/client_golang/prometheus/promhttp"
11+
)
12+
13+
// MetricsServer provides instrumentation for HTTP calls
14+
type MetricsServer struct {
15+
s *http.Server
16+
port int
17+
}
18+
19+
// Register binds a HTTP server to expose Prometheus metrics
20+
func (m *MetricsServer) Register(metricsPort int) {
21+
22+
m.port = metricsPort
23+
24+
readTimeout := time.Millisecond * 500
25+
writeTimeout := time.Millisecond * 500
26+
27+
metricsMux := http.NewServeMux()
28+
metricsMux.Handle("/metrics", promhttp.Handler())
29+
30+
m.s = &http.Server{
31+
Addr: fmt.Sprintf(":%d", metricsPort),
32+
ReadTimeout: readTimeout,
33+
WriteTimeout: writeTimeout,
34+
MaxHeaderBytes: 1 << 20, // Max header of 1MB
35+
Handler: metricsMux,
36+
}
37+
38+
}
39+
40+
// Serve http traffic in go routine, non-blocking
41+
func (m *MetricsServer) Serve(cancel chan bool) {
42+
log.Printf("Metrics server. Port: %d\n", m.port)
43+
44+
go func() {
45+
if err := m.s.ListenAndServe(); err != http.ErrServerClosed {
46+
panic(fmt.Sprintf("metrics error ListenAndServe: %v\n", err))
47+
}
48+
}()
49+
50+
go func() {
51+
select {
52+
case <-cancel:
53+
log.Printf("metrics server shutdown\n")
54+
55+
m.s.Shutdown(context.Background())
56+
}
57+
}()
58+
}
59+
60+
// InstrumentHandler returns a handler which records HTTP requests
61+
// as they are made
62+
func InstrumentHandler(next http.HandlerFunc, _http Http) http.HandlerFunc {
63+
return promhttp.InstrumentHandlerCounter(_http.RequestsTotal,
64+
promhttp.InstrumentHandlerDuration(_http.RequestDurationHistogram, next))
65+
}

metrics/metrics_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package metrics
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"testing"
7+
"time"
8+
)
9+
10+
func Test_Register_ProvidesBytes(t *testing.T) {
11+
12+
metricsPort := 31111
13+
14+
metricsServer := MetricsServer{}
15+
metricsServer.Register(metricsPort)
16+
17+
cancel := make(chan bool)
18+
go metricsServer.Serve(cancel)
19+
20+
defer func() {
21+
cancel <- true
22+
}()
23+
24+
retries := 10
25+
26+
for i := 0; i < retries; i++ {
27+
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://127.0.0.1:%d/metrics", metricsPort), nil)
28+
29+
res, err := http.DefaultClient.Do(req)
30+
31+
if err != nil {
32+
t.Logf("cannot get metrics, or not ready: %s", err.Error())
33+
34+
time.Sleep(time.Millisecond * 100)
35+
continue
36+
}
37+
38+
wantStatus := http.StatusOK
39+
if res.StatusCode != wantStatus {
40+
t.Errorf("metrics gave wrong status, want: %d, got: %d", wantStatus, res.StatusCode)
41+
t.Fail()
42+
return
43+
}
44+
45+
if res.Body == nil {
46+
t.Errorf("metrics response should have a body")
47+
t.Fail()
48+
return
49+
}
50+
defer res.Body.Close()
51+
52+
return
53+
}
54+
55+
t.Errorf("unable to get expected response from metrics server")
56+
t.Fail()
57+
}

vendor/github.com/beorn7/perks/LICENSE

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)