Skip to content

Commit 30b969a

Browse files
committed
instrumentation: add internal metrics server
This commit adds an internal server to the prom-label-proxy that exposes HTTP metrics about all of the routes registered with the prom-label-proxy. The internal server also exposes pprof profiles. The internal server is only activated if the --internal-listen-address flag is provided. Signed-off-by: Lucas Servén Marín <[email protected]>
1 parent 9ab1868 commit 30b969a

File tree

4 files changed

+126
-40
lines changed

4 files changed

+126
-40
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ require (
66
github.com/efficientgo/tools/core v0.0.0-20220225185207-fe763185946b
77
github.com/go-openapi/runtime v0.24.0
88
github.com/go-openapi/strfmt v0.21.2
9+
github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a
10+
github.com/oklog/run v1.1.0
911
github.com/pkg/errors v0.9.1
1012
github.com/prometheus/alertmanager v0.24.0
1113
github.com/prometheus/client_golang v1.12.1

go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
7777
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
7878
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
7979
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
80+
github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM=
81+
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
8082
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
8183
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
8284
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
@@ -754,6 +756,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
754756
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
755757
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
756758
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
759+
github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a h1:0usWxe5SGXKQovz3p+BiQ81Jy845xSMu2CWKuXsXuUM=
760+
github.com/metalmatze/signal v0.0.0-20210307161603-1c9aa721a97a/go.mod h1:3OETvrxfELvGsU2RoGGWercfeZ4bCL3+SOwzIWtJH/Q=
757761
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
758762
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
759763
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -805,6 +809,7 @@ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/
805809
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
806810
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
807811
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
812+
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
808813
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
809814
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
810815
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@@ -887,6 +892,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
887892
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
888893
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
889894
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
895+
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
890896
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
891897
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
892898
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=

injectproxy/routes.go

+46-12
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ import (
2424
"strings"
2525

2626
"github.com/efficientgo/tools/core/pkg/merrors"
27+
"github.com/metalmatze/signal/server/signalhttp"
2728
"github.com/pkg/errors"
28-
"github.com/prometheus/client_golang/prometheus/promhttp"
29+
"github.com/prometheus/client_golang/prometheus"
2930
"github.com/prometheus/prometheus/model/labels"
3031
"github.com/prometheus/prometheus/promql/parser"
3132
)
@@ -41,7 +42,7 @@ type routes struct {
4142
label string
4243
labelValue string
4344

44-
mux *http.ServeMux
45+
mux http.Handler
4546
modifiers map[string]func(*http.Response) error
4647
errorOnReplace bool
4748
}
@@ -51,6 +52,7 @@ type options struct {
5152
enableLabelAPIs bool
5253
passthroughPaths []string
5354
errorOnReplace bool
55+
registerer prometheus.Registerer
5456
}
5557

5658
type Option interface {
@@ -63,6 +65,13 @@ func (f optionFunc) apply(o *options) {
6365
f(o)
6466
}
6567

68+
// WithPrometheusRegistry configures the proxy to use the given registerer.
69+
func WithPrometheusRegistry(reg prometheus.Registerer) Option {
70+
return optionFunc(func(o *options) {
71+
o.registerer = reg
72+
})
73+
}
74+
6675
// WithEnabledLabelsAPI enables proxying to labels API. If false, "501 Not implemented" will be return for those.
6776
func WithEnabledLabelsAPI() Option {
6877
return optionFunc(func(o *options) {
@@ -95,17 +104,22 @@ func WithLabelValue(value string) Option {
95104
})
96105
}
97106

107+
// mux abstracts away the behavior we expect from the http.ServeMux type in this package.
108+
type mux interface {
109+
http.Handler
110+
Handle(string, http.Handler)
111+
}
112+
98113
// strictMux is a mux that wraps standard HTTP handler with safer handler that allows safe user provided handler registrations.
99114
type strictMux struct {
115+
mux
100116
seen map[string]struct{}
101-
102-
m *http.ServeMux
103117
}
104118

105-
func newStrictMux() *strictMux {
119+
func newStrictMux(m mux) *strictMux {
106120
return &strictMux{
107-
seen: map[string]struct{}{},
108-
m: http.NewServeMux(),
121+
m,
122+
map[string]struct{}{},
109123
}
110124

111125
}
@@ -131,18 +145,39 @@ func (s *strictMux) Handle(pattern string, handler http.Handler) error {
131145
}
132146
}
133147

134-
s.m.Handle(sanitized, handler)
135-
s.m.Handle(sanitized+"/", handler)
148+
s.mux.Handle(sanitized, handler)
149+
s.mux.Handle(sanitized+"/", handler)
136150
s.seen[sanitized] = struct{}{}
137151

138152
return nil
139153
}
140154

155+
// instrumentedMux wraps a mux and instruments it.
156+
type instrumentedMux struct {
157+
mux
158+
i signalhttp.HandlerInstrumenter
159+
}
160+
161+
func newInstrumentedMux(m mux, r prometheus.Registerer) *instrumentedMux {
162+
return &instrumentedMux{
163+
m,
164+
signalhttp.NewHandlerInstrumenter(r, []string{"handler"}),
165+
}
166+
}
167+
168+
// Handle implements the mux interface.
169+
func (i *instrumentedMux) Handle(pattern string, handler http.Handler) {
170+
i.mux.Handle(pattern, i.i.NewHandler(prometheus.Labels{"handler": pattern}, handler))
171+
}
172+
141173
func NewRoutes(upstream *url.URL, label string, opts ...Option) (*routes, error) {
142174
opt := options{}
143175
for _, o := range opts {
144176
o.apply(&opt)
145177
}
178+
if opt.registerer == nil {
179+
opt.registerer = prometheus.NewRegistry()
180+
}
146181

147182
proxy := httputil.NewSingleHostReverseProxy(upstream)
148183

@@ -153,7 +188,7 @@ func NewRoutes(upstream *url.URL, label string, opts ...Option) (*routes, error)
153188
labelValue: opt.labelValue,
154189
errorOnReplace: opt.errorOnReplace,
155190
}
156-
mux := newStrictMux()
191+
mux := newStrictMux(newInstrumentedMux(http.NewServeMux(), opt.registerer))
157192

158193
errs := merrors.New(
159194
mux.Handle("/federate", r.enforceLabel(enforceMethods(r.matcher, "GET"))),
@@ -185,7 +220,6 @@ func NewRoutes(upstream *url.URL, label string, opts ...Option) (*routes, error)
185220
mux.Handle("/healthz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
186221
_ = json.NewEncoder(w).Encode(map[string]bool{"ok": true})
187222
})),
188-
mux.Handle("/metrics", promhttp.Handler()),
189223
)
190224

191225
if err := errs.Err(); err != nil {
@@ -213,7 +247,7 @@ func NewRoutes(upstream *url.URL, label string, opts ...Option) (*routes, error)
213247
}
214248
}
215249

216-
r.mux = mux.m
250+
r.mux = mux
217251
r.modifiers = map[string]func(*http.Response) error{
218252
"/api/v1/rules": modifyAPIResponse(r.filterRules),
219253
"/api/v1/alerts": modifyAPIResponse(r.filterAlerts),

main.go

+72-28
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,29 @@
1414
package main
1515

1616
import (
17+
"context"
18+
"errors"
1719
"flag"
1820
"log"
1921
"net"
2022
"net/http"
2123
"net/url"
2224
"os"
23-
"os/signal"
2425
"strings"
2526
"syscall"
2627

28+
"github.com/metalmatze/signal/internalserver"
29+
"github.com/oklog/run"
30+
"github.com/prometheus/client_golang/prometheus"
31+
"github.com/prometheus/client_golang/prometheus/collectors"
32+
2733
"github.com/prometheus-community/prom-label-proxy/injectproxy"
2834
)
2935

3036
func main() {
3137
var (
3238
insecureListenAddress string
39+
internalListenAddress string
3340
upstream string
3441
label string
3542
labelValue string
@@ -40,6 +47,7 @@ func main() {
4047

4148
flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
4249
flagset.StringVar(&insecureListenAddress, "insecure-listen-address", "", "The address the prom-label-proxy HTTP server should listen on.")
50+
flagset.StringVar(&internalListenAddress, "internal-listen-address", "", "The address the internal prom-label-proxy HTTP server should listen on to expose metrics about itself.")
4351
flagset.StringVar(&upstream, "upstream", "", "The upstream URL to proxy to.")
4452
flagset.StringVar(&label, "label", "", "The label to enforce in all proxied PromQL queries. "+
4553
"This label will be also required as the URL parameter to get the value to be injected. For example: -label=tenant will"+
@@ -69,7 +77,13 @@ func main() {
6977
log.Fatalf("Invalid scheme for upstream URL %q, only 'http' and 'https' are supported", upstream)
7078
}
7179

72-
var opts []injectproxy.Option
80+
reg := prometheus.NewRegistry()
81+
reg.MustRegister(
82+
collectors.NewGoCollector(),
83+
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
84+
)
85+
86+
opts := []injectproxy.Option{injectproxy.WithPrometheusRegistry(reg)}
7387
if enableLabelAPIs {
7488
opts = append(opts, injectproxy.WithEnabledLabelsAPI())
7589
}
@@ -83,38 +97,68 @@ func main() {
8397
opts = append(opts, injectproxy.WithLabelValue(labelValue))
8498
}
8599

86-
routes, err := injectproxy.NewRoutes(upstreamURL, label, opts...)
87-
if err != nil {
88-
log.Fatalf("Failed to create injectproxy Routes: %v", err)
89-
}
100+
var g run.Group
101+
102+
{
103+
// Run the insecure HTTP server.
104+
routes, err := injectproxy.NewRoutes(upstreamURL, label, opts...)
105+
if err != nil {
106+
log.Fatalf("Failed to create injectproxy Routes: %v", err)
107+
}
90108

91-
mux := http.NewServeMux()
92-
mux.Handle("/", routes)
109+
mux := http.NewServeMux()
110+
mux.Handle("/", routes)
93111

94-
srv := &http.Server{Handler: mux}
112+
srv := &http.Server{Handler: mux}
95113

96-
l, err := net.Listen("tcp", insecureListenAddress)
97-
if err != nil {
98-
log.Fatalf("Failed to listen on insecure address: %v", err)
114+
l, err := net.Listen("tcp", insecureListenAddress)
115+
if err != nil {
116+
log.Fatalf("Failed to listen on insecure address: %v", err)
117+
}
118+
119+
g.Add(func() error {
120+
log.Printf("Listening insecurely on %v", l.Addr())
121+
if err := srv.Serve(l); err != nil && err != http.ErrServerClosed {
122+
log.Printf("Server stopped with %v", err)
123+
return err
124+
}
125+
return nil
126+
}, func(error) {
127+
l.Close()
128+
})
99129
}
100130

101-
errCh := make(chan error)
102-
go func() {
103-
log.Printf("Listening insecurely on %v", l.Addr())
104-
errCh <- srv.Serve(l)
105-
}()
106-
107-
term := make(chan os.Signal, 1)
108-
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
109-
110-
select {
111-
case <-term:
112-
log.Print("Received SIGTERM, exiting gracefully...")
113-
srv.Close()
114-
case err := <-errCh:
115-
if err != http.ErrServerClosed {
131+
if internalListenAddress != "" {
132+
// Run the internal HTTP server.
133+
h := internalserver.NewHandler(
134+
internalserver.WithName("Internal prom-label-proxy API"),
135+
internalserver.WithPrometheusRegistry(reg),
136+
internalserver.WithPProf(),
137+
)
138+
// Run the HTTP server.
139+
l, err := net.Listen("tcp", internalListenAddress)
140+
if err != nil {
141+
log.Fatalf("Failed to listen on internal address: %v", err)
142+
}
143+
144+
g.Add(func() error {
145+
if err := http.Serve(l, h); err != nil && err != http.ErrServerClosed {
146+
log.Printf("Internal server stopped with %v", err)
147+
return err
148+
}
149+
return nil
150+
}, func(error) {
151+
l.Close()
152+
})
153+
}
154+
155+
g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM))
156+
157+
if err := g.Run(); err != nil {
158+
if !errors.Is(err, run.SignalError{}) {
116159
log.Printf("Server stopped with %v", err)
160+
os.Exit(1)
117161
}
118-
os.Exit(1)
162+
log.Print("Caught signal; exiting gracefully...")
119163
}
120164
}

0 commit comments

Comments
 (0)