diff --git a/cmd/relayproxy/api/routes_monitoring.go b/cmd/relayproxy/api/routes_monitoring.go index 26e117c2900..b3bb136e13e 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,8 @@ 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..032f02c263d --- /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 TestPprofEndpointsStarts(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: 1032, + expectedStatusCode: http.StatusOK, + }, + { + name: "pprof not available ii debug not enabled", + Debug: false, + MonitoringPort: 1032, + 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: 1031, + 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..d5ee6ec646f --- /dev/null +++ b/cmd/relayproxy/modeldocs/pprofController.go @@ -0,0 +1,19 @@ +// nolint: lll +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. +} 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