forked from smartcontractkit/chainlink
-
Notifications
You must be signed in to change notification settings - Fork 0
/
prom.go
124 lines (101 loc) · 2.84 KB
/
prom.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package plugins
import (
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/net/context"
"github.com/smartcontractkit/chainlink-relay/pkg/logger"
)
type PromServer struct {
port int
srvrDone chan struct{} // closed when the http server is done
srvr *http.Server
tcpListener *net.TCPListener
lggr logger.Logger
handler http.Handler
}
type PromServerOpt func(*PromServer)
func WithHandler(h http.Handler) PromServerOpt {
return func(s *PromServer) {
s.handler = h
}
}
func NewPromServer(port int, lggr logger.Logger, opts ...PromServerOpt) *PromServer {
s := &PromServer{
port: port,
lggr: lggr,
srvrDone: make(chan struct{}),
srvr: &http.Server{
// reasonable default based on typical prom poll interval of 15s.
ReadTimeout: 5 * time.Second,
},
handler: promhttp.HandlerFor(
prometheus.DefaultGatherer,
promhttp.HandlerOpts{
EnableOpenMetrics: true,
},
),
}
for _, opt := range opts {
opt(s)
}
return s
}
// Start start HTTP server on specified port to handle metrics requests
func (p *PromServer) Start() error {
p.lggr.Debugf("Starting prom server on port %d", p.port)
err := p.setupListener()
if err != nil {
return err
}
http.Handle("/metrics", p.handler)
go func() {
defer close(p.srvrDone)
err := p.srvr.Serve(p.tcpListener)
if errors.Is(err, net.ErrClosed) {
// ErrClose is expected on gracefully shutdown
p.lggr.Warnf("%s closed", p.Name())
} else {
p.lggr.Errorf("%s: %s", p.Name(), err)
}
}()
return nil
}
// Close shutdowns down the underlying HTTP server. See [http.Server.Close] for details
func (p *PromServer) Close() error {
err := p.srvr.Shutdown(context.Background())
<-p.srvrDone
return err
}
// Name of the server
func (p *PromServer) Name() string {
return fmt.Sprintf("%s-prom-server", p.lggr.Name())
}
// Port is the resolved port and is only known after Start().
// returns -1 before it is resolved or if there was an error during resolution.
func (p *PromServer) Port() int {
if p.tcpListener == nil {
return -1
}
// always safe to cast because we explicitly have a tcp listener
// there is direct access to Port without the addr casting
// Note: addr `:0` is not resolved to non-zero port until ListenTCP is called
// net.ResolveTCPAddr sounds promising, but doesn't work in practice
return p.tcpListener.Addr().(*net.TCPAddr).Port
}
// setupListener creates explicit listener so that we can resolve `:0` port, which is needed for testing
// if we didn't need the resolved addr, or could pick a static port we could use p.srvr.ListenAndServer
func (p *PromServer) setupListener() error {
l, err := net.ListenTCP("tcp", &net.TCPAddr{
Port: p.port,
})
if err != nil {
return err
}
p.tcpListener = l
return nil
}