From f4194c2389c2e57f68f224b79eadf2a40703f49f Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Sat, 12 Oct 2024 10:57:06 +0200 Subject: [PATCH 1/4] feat: Expose pprof endpoints in debug mode --- cmd/relayproxy/api/routes_monitoring.go | 6 ++ cmd/relayproxy/api/routes_monitoring_test.go | 79 +++++++++++++++++++ cmd/relayproxy/config/config.go | 4 +- cmd/relayproxy/docs/docs.go | 20 +++++ cmd/relayproxy/docs/swagger.json | 20 +++++ cmd/relayproxy/docs/swagger.yaml | 17 ++++ cmd/relayproxy/modeldocs/metricsController.go | 2 +- cmd/relayproxy/modeldocs/pprofController.go | 18 +++++ 8 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 cmd/relayproxy/api/routes_monitoring_test.go create mode 100644 cmd/relayproxy/modeldocs/pprofController.go diff --git a/cmd/relayproxy/api/routes_monitoring.go b/cmd/relayproxy/api/routes_monitoring.go index 26e117c2900..2c632c21d5b 100644 --- a/cmd/relayproxy/api/routes_monitoring.go +++ b/cmd/relayproxy/api/routes_monitoring.go @@ -2,6 +2,7 @@ package api import ( "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo-contrib/pprof" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" custommiddleware "github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/api/middleware" @@ -39,4 +40,9 @@ func (s *Server) initMonitoringEndpoint(echoInstance *echo.Echo) { // health Routes echoInstance.GET("/health", cHealth.Handler) echoInstance.GET("/info", cInfo.Handler) + + if s.config.Debug { + pprof.Register(echoInstance) + } + } diff --git a/cmd/relayproxy/api/routes_monitoring_test.go b/cmd/relayproxy/api/routes_monitoring_test.go new file mode 100644 index 00000000000..e9d4d327b5d --- /dev/null +++ b/cmd/relayproxy/api/routes_monitoring_test.go @@ -0,0 +1,79 @@ +package api_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/api" + "github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/config" + "github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/metric" + "github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/service" + "github.com/thomaspoignant/go-feature-flag/notifier" + "go.uber.org/zap" +) + +func TestXXX(t *testing.T) { + type test struct { + name string + MonitoringPort int + Debug bool + expectedStatusCode int + } + tests := []test{ + { + name: "pprof available in proxy port", + Debug: true, + expectedStatusCode: http.StatusOK, + }, + { + name: "pprof available in monitoring port", + Debug: true, + MonitoringPort: 46000, + expectedStatusCode: http.StatusOK, + }, + { + name: "pprof not available ii debug not enabled", + Debug: false, + MonitoringPort: 46000, + expectedStatusCode: http.StatusNotFound, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z, err := zap.NewProduction() + require.NoError(t, err) + c := &config.Config{ + Retriever: &config.RetrieverConf{ + Kind: "file", + Path: "../../../testdata/flag-config.yaml", + }, + MonitoringPort: tt.MonitoringPort, + ListenPort: 46001, + Debug: tt.Debug, + } + + goff, err := service.NewGoFeatureFlagClient(c, z, []notifier.Notifier{}) + require.NoError(t, err) + apiServer := api.New(c, service.Services{ + MonitoringService: service.NewMonitoring(goff), + WebsocketService: service.NewWebsocketService(), + GOFeatureFlagService: goff, + Metrics: metric.Metrics{}, + }, z) + + portToCheck := c.ListenPort + if tt.MonitoringPort != 0 { + portToCheck = tt.MonitoringPort + } + + go apiServer.Start() + defer apiServer.Stop() + resp, err := http.Get(fmt.Sprintf("http://localhost:%d/debug/pprof/heap", portToCheck)) + require.NoError(t, err) + require.Equal(t, tt.expectedStatusCode, resp.StatusCode) + }) + } +} diff --git a/cmd/relayproxy/config/config.go b/cmd/relayproxy/config/config.go index 9ba30be9929..33ab485fcdc 100644 --- a/cmd/relayproxy/config/config.go +++ b/cmd/relayproxy/config/config.go @@ -146,7 +146,9 @@ type Config struct { // HideBanner (optional) if true, we don't display the go-feature-flag relay proxy banner HideBanner bool `mapstructure:"hideBanner" koanf:"hidebanner"` - // Debug (optional) if true, go-feature-flag relay proxy will run on debug mode, with more logs and custom responses + // Debug (optional) if true, go-feature-flag relay proxy will run on debug mode, with more logs and custom responses. + // It will also start the pprof endpoints on the same port as the monitoring. + // Default: false Debug bool `mapstructure:"debug" koanf:"debug"` // EnableSwagger (optional) to have access to the swagger diff --git a/cmd/relayproxy/docs/docs.go b/cmd/relayproxy/docs/docs.go index b1d48cc95f6..7e7adab7928 100644 --- a/cmd/relayproxy/docs/docs.go +++ b/cmd/relayproxy/docs/docs.go @@ -63,6 +63,26 @@ const docTemplate = `{ } } }, + "/debug/pprof/": { + "get": { + "description": "This endpoint is provided by the echo pprof middleware.\nTo know more please check this blogpost from the GO team https://go.dev/blog/pprof.\nVisit the page /debug/pprof/ to see the available endpoints, all endpoint are not in the swagger documentation because they are standard pprof endpoints.\nThis endpoint is only available in debug mode.", + "produces": [ + "text/plain" + ], + "tags": [ + "Profiling" + ], + "summary": "pprof endpoint", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, "/health": { "get": { "description": "Making a **GET** request to the URL path ` + "`" + `/health` + "`" + ` will tell you if the relay proxy is ready to serve\ntraffic.\n\nThis is useful especially for loadbalancer to know that they can send traffic to the service.", diff --git a/cmd/relayproxy/docs/swagger.json b/cmd/relayproxy/docs/swagger.json index e6dc3f1bafe..089c2c0fb69 100644 --- a/cmd/relayproxy/docs/swagger.json +++ b/cmd/relayproxy/docs/swagger.json @@ -55,6 +55,26 @@ } } }, + "/debug/pprof/": { + "get": { + "description": "This endpoint is provided by the echo pprof middleware.\nTo know more please check this blogpost from the GO team https://go.dev/blog/pprof.\nVisit the page /debug/pprof/ to see the available endpoints, all endpoint are not in the swagger documentation because they are standard pprof endpoints.\nThis endpoint is only available in debug mode.", + "produces": [ + "text/plain" + ], + "tags": [ + "Profiling" + ], + "summary": "pprof endpoint", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, "/health": { "get": { "description": "Making a **GET** request to the URL path `/health` will tell you if the relay proxy is ready to serve\ntraffic.\n\nThis is useful especially for loadbalancer to know that they can send traffic to the service.", diff --git a/cmd/relayproxy/docs/swagger.yaml b/cmd/relayproxy/docs/swagger.yaml index f5f77b92036..b0b56ba1551 100644 --- a/cmd/relayproxy/docs/swagger.yaml +++ b/cmd/relayproxy/docs/swagger.yaml @@ -444,6 +444,23 @@ paths: summary: This endpoint is used to force the refresh of the flags in the cache. tags: - Admin API to manage GO Feature Flag + /debug/pprof/: + get: + description: |- + This endpoint is provided by the echo pprof middleware. + To know more please check this blogpost from the GO team https://go.dev/blog/pprof. + Visit the page /debug/pprof/ to see the available endpoints, all endpoint are not in the swagger documentation because they are standard pprof endpoints. + This endpoint is only available in debug mode. + produces: + - text/plain + responses: + "200": + description: OK + schema: + type: string + summary: pprof endpoint + tags: + - Profiling /health: get: description: |- diff --git a/cmd/relayproxy/modeldocs/metricsController.go b/cmd/relayproxy/modeldocs/metricsController.go index 3e34b73d24e..c618a065968 100644 --- a/cmd/relayproxy/modeldocs/metricsController.go +++ b/cmd/relayproxy/modeldocs/metricsController.go @@ -2,7 +2,7 @@ package modeldocs import "github.com/labstack/echo/v4" -// FakeMetricsController is the entry point for the allFlags endpoint +// FakeMetricsController is a fake entry point for swagger documentation // // @Summary Prometheus endpoint // @Tags Monitoring diff --git a/cmd/relayproxy/modeldocs/pprofController.go b/cmd/relayproxy/modeldocs/pprofController.go new file mode 100644 index 00000000000..e34f94d75ac --- /dev/null +++ b/cmd/relayproxy/modeldocs/pprofController.go @@ -0,0 +1,18 @@ +package modeldocs + +import "github.com/labstack/echo/v4" + +// FakePprofController is a fake endpoint for swagger documentation of pprof endpoint +// +// @Summary pprof endpoint +// @Tags Profiling +// @Description This endpoint is provided by the echo pprof middleware. +// @Description To know more please check this blogpost from the GO team https://go.dev/blog/pprof. +// @Description Visit the page /debug/pprof/ to see the available endpoints, all endpoint are not in the swagger documentation because they are standard pprof endpoints. +// @Description This endpoint is only available in debug mode. +// @Produce plain +// @Success 200 {object} string +// @Router /debug/pprof/ [get] +func FakePprofController(_ echo.Context) { + // This is a fake controller, the real entry point is provided by the prometheus middleware. +} From 7a89d23f590f9f752b5811cbc29099c770b7030a Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Sat, 12 Oct 2024 11:08:43 +0200 Subject: [PATCH 2/4] Adding documentation --- website/docs/relay_proxy/profiling.md | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 website/docs/relay_proxy/profiling.md diff --git a/website/docs/relay_proxy/profiling.md b/website/docs/relay_proxy/profiling.md new file mode 100644 index 00000000000..80850e10413 --- /dev/null +++ b/website/docs/relay_proxy/profiling.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 81 +title: Profiling +description: Profiling of the relay proxy. +--- + +## Profiling + +The **relay proxy** is able to expose profiling information. +This is useful to understand the performance of the service and solve potential issues. + +The information are exposed on the `/debug/pprof` endpoint, and we are using the default `net/http/pprof` package +to expose the information. + +:::warning +By default the profiling endpoints are disabled. +You have to run the relay proxy in debug mode if you want to enable them. +::: + +List of endpoints exposed is available http://localhost:1031/debug/pprof/ + +### Enable profiling + +In your relay proxy configuration file you need to set the `debug` field to `true`. + +```yaml {5} +retriever: + kind: file + path: /goff/flags.yaml # Location of your feature flag files +# ... +debug: true +``` \ No newline at end of file From 9caf74ddf9698ff9c43f8fb67df14c11e24e3941 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Sat, 12 Oct 2024 11:15:25 +0200 Subject: [PATCH 3/4] fix linter issue --- cmd/relayproxy/api/routes_monitoring.go | 1 - cmd/relayproxy/modeldocs/pprofController.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/relayproxy/api/routes_monitoring.go b/cmd/relayproxy/api/routes_monitoring.go index 2c632c21d5b..b3bb136e13e 100644 --- a/cmd/relayproxy/api/routes_monitoring.go +++ b/cmd/relayproxy/api/routes_monitoring.go @@ -44,5 +44,4 @@ func (s *Server) initMonitoringEndpoint(echoInstance *echo.Echo) { if s.config.Debug { pprof.Register(echoInstance) } - } diff --git a/cmd/relayproxy/modeldocs/pprofController.go b/cmd/relayproxy/modeldocs/pprofController.go index e34f94d75ac..d5ee6ec646f 100644 --- a/cmd/relayproxy/modeldocs/pprofController.go +++ b/cmd/relayproxy/modeldocs/pprofController.go @@ -1,3 +1,4 @@ +// nolint: lll package modeldocs import "github.com/labstack/echo/v4" From 5a23a74af775bf698e8e0fb1793dc5296d1c0fa4 Mon Sep 17 00:00:00 2001 From: Thomas Poignant Date: Sat, 12 Oct 2024 12:09:06 +0200 Subject: [PATCH 4/4] Change port --- cmd/relayproxy/api/routes_monitoring_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/relayproxy/api/routes_monitoring_test.go b/cmd/relayproxy/api/routes_monitoring_test.go index e9d4d327b5d..032f02c263d 100644 --- a/cmd/relayproxy/api/routes_monitoring_test.go +++ b/cmd/relayproxy/api/routes_monitoring_test.go @@ -14,7 +14,7 @@ import ( "go.uber.org/zap" ) -func TestXXX(t *testing.T) { +func TestPprofEndpointsStarts(t *testing.T) { type test struct { name string MonitoringPort int @@ -30,13 +30,13 @@ func TestXXX(t *testing.T) { { name: "pprof available in monitoring port", Debug: true, - MonitoringPort: 46000, + MonitoringPort: 1032, expectedStatusCode: http.StatusOK, }, { name: "pprof not available ii debug not enabled", Debug: false, - MonitoringPort: 46000, + MonitoringPort: 1032, expectedStatusCode: http.StatusNotFound, }, } @@ -51,7 +51,7 @@ func TestXXX(t *testing.T) { Path: "../../../testdata/flag-config.yaml", }, MonitoringPort: tt.MonitoringPort, - ListenPort: 46001, + ListenPort: 1031, Debug: tt.Debug, }