Skip to content

Commit c83ee1d

Browse files
committed
pkg/loop: replace PromServer with webServer, including pprof support
1 parent c188380 commit c83ee1d

File tree

4 files changed

+109
-5
lines changed

4 files changed

+109
-5
lines changed

pkg/loop/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ type EnvConfig struct {
115115
MercuryTransmitterReaperMaxAge time.Duration
116116
MercuryVerboseLogging bool
117117

118-
PrometheusPort int
118+
PrometheusPort int //TODO more than just prom
119119

120120
TracingEnabled bool
121121
TracingCollectorTarget string

pkg/loop/prom.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/smartcontractkit/chainlink-common/pkg/logger"
1515
)
1616

17+
// Deprecated: Use WebServer which includes pprof routes
1718
type PromServer struct {
1819
port int
1920
srvrDone chan struct{} // closed when the http server is done
@@ -24,10 +25,12 @@ type PromServer struct {
2425
handler http.Handler
2526
}
2627

28+
// Deprecated: Use WebServerOpts which includes pprof routes
2729
type PromServerOpts struct {
2830
Handler http.Handler
2931
}
3032

33+
// Deprecated: Use NewWebServer which includes pprof routes
3134
func NewPromServer(port int, lggr logger.Logger) *PromServer {
3235
return PromServerOpts{}.New(port, lggr)
3336
}

pkg/loop/server.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ type Server struct {
9191
db *sqlx.DB // optional
9292
dbStatsReporter *pg.StatsReporter // optional
9393
DataSource sqlutil.DataSource // optional
94-
promServer *PromServer
94+
webServer *webServer
9595
checker *services.HealthChecker
9696
LimitsFactory limits.Factory
9797
}
@@ -221,8 +221,8 @@ func (s *Server) start(opts ...ServerOpt) error {
221221
}
222222
}
223223

224-
s.promServer = NewPromServer(s.EnvConfig.PrometheusPort, s.Logger)
225-
if err := s.promServer.Start(); err != nil {
224+
s.webServer = WebServerOpts{}.New(s.Logger, s.EnvConfig.PrometheusPort)
225+
if err := s.webServer.Start(ctx); err != nil {
226226
return fmt.Errorf("error starting prometheus server: %w", err)
227227
}
228228

@@ -290,7 +290,7 @@ func (s *Server) Stop() {
290290
s.Logger.ErrorIfFn(s.db.Close, "Failed to close database connection")
291291
}
292292
s.Logger.ErrorIfFn(s.checker.Close, "Failed to close health checker")
293-
s.Logger.ErrorIfFn(s.promServer.Close, "Failed to close prometheus server")
293+
s.Logger.ErrorIfFn(s.webServer.Close, "Failed to close web server")
294294
if err := s.Logger.Sync(); err != nil {
295295
fmt.Println("Failed to sync logger:", err)
296296
}

pkg/loop/web.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package loop
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net"
7+
"net/http"
8+
_ "net/http/pprof"
9+
"time"
10+
11+
"github.com/prometheus/client_golang/prometheus"
12+
"github.com/prometheus/client_golang/prometheus/promhttp"
13+
14+
"github.com/smartcontractkit/chainlink-common/pkg/logger"
15+
)
16+
17+
// webServer serves web routes including
18+
// - /metrics prometheus metrics
19+
// - /debug/pprof/ debug profiling
20+
type webServer struct {
21+
lggr logger.Logger
22+
port int
23+
handler http.Handler
24+
25+
srvr *http.Server
26+
tcpListener *net.TCPListener
27+
28+
done chan struct{}
29+
}
30+
31+
type WebServerOpts struct {
32+
Handler http.Handler
33+
}
34+
35+
func (o WebServerOpts) New(lggr logger.Logger, port int) *webServer {
36+
s := &webServer{
37+
lggr: logger.Named(lggr, "WebServer"),
38+
port: port,
39+
handler: o.Handler,
40+
srvr: &http.Server{
41+
// reasonable default based on typical prom poll interval of 15s.
42+
ReadTimeout: 5 * time.Second,
43+
},
44+
done: make(chan struct{}),
45+
}
46+
if s.handler == nil {
47+
s.handler = promhttp.HandlerFor(
48+
prometheus.DefaultGatherer,
49+
promhttp.HandlerOpts{
50+
EnableOpenMetrics: true,
51+
},
52+
)
53+
}
54+
return s
55+
}
56+
57+
// setupListener creates an explicit listener so that we can resolve `:0` port, which is needed for testing
58+
// if we didn't need the resolved addr, or could pick a static port we could use p.srvr.ListenAndServer
59+
func (w *webServer) setupListener() error {
60+
l, err := net.ListenTCP("tcp", &net.TCPAddr{
61+
Port: w.port,
62+
})
63+
if err != nil {
64+
return err
65+
}
66+
67+
w.tcpListener = l
68+
return nil
69+
}
70+
71+
func (w *webServer) Start(ctx context.Context) error {
72+
err := w.setupListener()
73+
if err != nil {
74+
return err
75+
}
76+
77+
http.Handle("/metrics", w.handler)
78+
79+
// pprof handler registered via import side effects
80+
81+
go func() {
82+
defer close(w.done)
83+
err := w.srvr.Serve(w.tcpListener)
84+
if !errors.Is(err, http.ErrServerClosed) {
85+
w.lggr.Errorw("Unexpected server error", "err", err)
86+
}
87+
}()
88+
return nil
89+
}
90+
91+
func (w *webServer) Close() error {
92+
err := w.srvr.Shutdown(context.Background())
93+
<-w.done
94+
return err
95+
}
96+
97+
func (w *webServer) Ready() error { return nil }
98+
99+
func (w *webServer) HealthReport() map[string]error { return map[string]error{w.Name(): nil} }
100+
101+
func (w *webServer) Name() string { return w.lggr.Name() }

0 commit comments

Comments
 (0)