Skip to content

Commit fc98703

Browse files
committed
[baseserver] Expose gRPC prometheus metrics
1 parent 0ab2a8d commit fc98703

File tree

3 files changed

+69
-14
lines changed

3 files changed

+69
-14
lines changed

components/common-go/baseserver/options.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ type config struct {
3434

3535
func defaultConfig() *config {
3636
return &config{
37-
logger: log.New(),
38-
hostname: "localhost",
39-
httpPort: 9000,
40-
grpcPort: 9001,
41-
closeTimeout: 5 * time.Second,
42-
healthHandler: healthcheck.NewHandler(),
37+
logger: log.New(),
38+
hostname: "localhost",
39+
httpPort: 9000,
40+
grpcPort: 9001,
41+
closeTimeout: 5 * time.Second,
42+
healthHandler: healthcheck.NewHandler(),
43+
metricsRegistry: prometheus.NewRegistry(),
4344
}
4445
}
4546

components/common-go/baseserver/server.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
gitpod_grpc "github.com/gitpod-io/gitpod/common-go/grpc"
1111
"github.com/gitpod-io/gitpod/common-go/pprof"
1212
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
13+
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
14+
"github.com/prometheus/client_golang/prometheus"
1315
"github.com/prometheus/client_golang/prometheus/promhttp"
1416
"github.com/sirupsen/logrus"
1517
"google.golang.org/grpc"
@@ -165,6 +167,10 @@ func (s *Server) GRPC() *grpc.Server {
165167
return s.grpc
166168
}
167169

170+
func (s *Server) MetricsRegistry() *prometheus.Registry {
171+
return s.cfg.metricsRegistry
172+
}
173+
168174
func (s *Server) close(ctx context.Context) error {
169175
if s.listening == nil {
170176
return fmt.Errorf("server is not running, invalid close operation")
@@ -221,14 +227,9 @@ func (s *Server) newHTTPMux() *http.ServeMux {
221227
mux.HandleFunc("/live", s.cfg.healthHandler.LiveEndpoint)
222228
s.Logger().WithField("protocol", "http").Debug("Serving liveliness handler on /live")
223229

224-
// Metrics endpoint
225-
metricsHandler := promhttp.Handler()
226-
if s.cfg.metricsRegistry != nil {
227-
metricsHandler = promhttp.InstrumentMetricHandler(
228-
s.cfg.metricsRegistry, promhttp.HandlerFor(s.cfg.metricsRegistry, promhttp.HandlerOpts{}),
229-
)
230-
}
231-
mux.Handle("/metrics", metricsHandler)
230+
mux.Handle("/metrics", promhttp.InstrumentMetricHandler(
231+
s.cfg.metricsRegistry, promhttp.HandlerFor(s.cfg.metricsRegistry, promhttp.HandlerOpts{}),
232+
))
232233
s.Logger().WithField("protocol", "http").Debug("Serving metrics on /metrics")
233234

234235
mux.Handle(pprof.Path, pprof.Handler())
@@ -240,11 +241,19 @@ func (s *Server) newHTTPMux() *http.ServeMux {
240241
func (s *Server) initializeGRPC() error {
241242
gitpod_grpc.SetupLogging()
242243

244+
grpcMetrics := grpc_prometheus.NewServerMetrics()
245+
grpcMetrics.EnableHandlingTimeHistogram()
246+
if err := s.MetricsRegistry().Register(grpcMetrics); err != nil {
247+
return fmt.Errorf("failed to register grpc metrics: %w", err)
248+
}
249+
243250
unary := []grpc.UnaryServerInterceptor{
244251
grpc_logrus.UnaryServerInterceptor(s.Logger()),
252+
grpcMetrics.UnaryServerInterceptor(),
245253
}
246254
stream := []grpc.StreamServerInterceptor{
247255
grpc_logrus.StreamServerInterceptor(s.Logger()),
256+
grpcMetrics.StreamServerInterceptor(),
248257
}
249258

250259
s.grpc = grpc.NewServer(gitpod_grpc.ServerOptionsWithInterceptors(stream, unary)...)

components/common-go/baseserver/server_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@
55
package baseserver_test
66

77
import (
8+
"context"
89
"fmt"
910
"github.com/gitpod-io/gitpod/common-go/baseserver"
1011
"github.com/gitpod-io/gitpod/common-go/pprof"
1112
"github.com/prometheus/client_golang/prometheus"
13+
"github.com/prometheus/client_golang/prometheus/testutil"
1214
"github.com/stretchr/testify/require"
15+
"google.golang.org/grpc"
16+
"google.golang.org/grpc/credentials/insecure"
17+
"google.golang.org/grpc/health/grpc_health_v1"
1318
"net/http"
1419
"testing"
1520
)
@@ -77,3 +82,43 @@ func TestServer_ServesPprof(t *testing.T) {
7782
require.NoError(t, err)
7883
require.Equalf(t, http.StatusOK, resp.StatusCode, "must serve pprof on %s", pprof.Path)
7984
}
85+
86+
func TestServer_Metrics_gRPC(t *testing.T) {
87+
ctx := context.Background()
88+
srv := baseserver.NewForTests(t)
89+
90+
// At this point, there must be metrics registry available for use
91+
require.NotNil(t, srv.MetricsRegistry())
92+
93+
// To actually get gRPC metrics, we need to invoke an RPC, let's use a built-in health service as a mock
94+
grpc_health_v1.RegisterHealthServer(srv.GRPC(), &HealthService{})
95+
96+
// Let's start our server up
97+
baseserver.StartServerForTests(t, srv)
98+
99+
// We need a client to be able to invoke the RPC, let's construct one
100+
conn, err := grpc.DialContext(ctx, srv.GRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
101+
require.NoError(t, err)
102+
client := grpc_health_v1.NewHealthClient(conn)
103+
104+
// Invoke the RPC
105+
_, err = client.Check(ctx, &grpc_health_v1.HealthCheckRequest{})
106+
require.NoError(t, err)
107+
108+
// Finally, we can assert that some metrics were produced.
109+
registry := srv.MetricsRegistry()
110+
// We expect at least the following. It's not the full set, but a good baseline to sanity check.
111+
expected := []string{"grpc_server_handled_total", "grpc_server_handling_seconds", "grpc_server_started_total"}
112+
113+
count, err := testutil.GatherAndCount(registry, expected...)
114+
require.NoError(t, err)
115+
require.Equal(t, len(expected)*1, count, "expected 1 count for each metric")
116+
}
117+
118+
type HealthService struct {
119+
grpc_health_v1.UnimplementedHealthServer
120+
}
121+
122+
func (h *HealthService) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
123+
return &grpc_health_v1.HealthCheckResponse{Status: grpc_health_v1.HealthCheckResponse_SERVING}, nil
124+
}

0 commit comments

Comments
 (0)