From 375b42974932d2f51a817c402514fedc512908b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20L=C3=B3pez?= Date: Fri, 9 May 2025 13:20:24 -0300 Subject: [PATCH] feat: add HTTP pprof endpoints (#5363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Leandro López (inkel) --- cmd/server.go | 5 +++++ cmd/server_test.go | 1 + runatlantis.io/docs/api-endpoints.md | 4 ++++ runatlantis.io/docs/server-configuration.md | 10 ++++++++++ server/server.go | 15 +++++++++++++++ server/user_config.go | 1 + 6 files changed, 36 insertions(+) diff --git a/cmd/server.go b/cmd/server.go index 9c6ade100..6d8a12e01 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -86,6 +86,7 @@ const ( EnableDiffMarkdownFormat = "enable-diff-markdown-format" EnablePolicyChecksFlag = "enable-policy-checks" EnableRegExpCmdFlag = "enable-regexp-cmd" + EnableProfilingAPI = "enable-profiling-api" ExecutableName = "executable-name" FailOnPreWorkflowHookError = "fail-on-pre-workflow-hook-error" HideUnchangedPlanComments = "hide-unchanged-plan-comments" @@ -529,6 +530,10 @@ var boolFlags = map[string]boolFlag{ description: "Enable Atlantis to use regular expressions on plan/apply commands when \"-p\" flag is passed with it.", defaultValue: false, }, + EnableProfilingAPI: { + description: "Enable net/http/pprof routes in server for continuous profiling.", + defaultValue: false, + }, EnableDiffMarkdownFormat: { description: "Enable Atlantis to format Terraform plan output into a markdown-diff friendly format for color-coding purposes.", defaultValue: false, diff --git a/cmd/server_test.go b/cmd/server_test.go index ea73de290..daef8709b 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -164,6 +164,7 @@ var testFlags = map[string]interface{}{ EnablePolicyChecksFlag: false, EnableRegExpCmdFlag: false, EnableDiffMarkdownFormat: false, + EnableProfilingAPI: false, } func TestExecute_Defaults(t *testing.T) { diff --git a/runatlantis.io/docs/api-endpoints.md b/runatlantis.io/docs/api-endpoints.md index 91b55a2dc..cd104ea6d 100644 --- a/runatlantis.io/docs/api-endpoints.md +++ b/runatlantis.io/docs/api-endpoints.md @@ -234,3 +234,7 @@ curl --request GET 'https:///healthz' "status": "ok" } ``` + +### GET /debug/pprof + +If `--enable-profiling-api` is set to true, it adds endpoints under this path to expose server's profiling data. See [profiling Go programs](https://go.dev/blog/pprof) for more information. diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md index 88fc25fa8..7c74d9a6a 100644 --- a/runatlantis.io/docs/server-configuration.md +++ b/runatlantis.io/docs/server-configuration.md @@ -532,6 +532,16 @@ and set `--autoplan-modules` to `false`. Enables atlantis to run server side policies on the result of a terraform plan. Policies are defined in [server side repo config](server-side-repo-config.md#reference). +### `--enable-profiling-api` + + ```bash + atlantis server --enable-profiling-api + # or + ATLANTIS_ENABLE_PROFILING_API=true + ``` + + Enable [`net/http/pprof`](https://pkg.go.dev/net/http/pprof) endpoints for [continuous profiling](https://grafana.com/docs/pyroscope/latest/introduction/continuous-profiling/) of resources used by the server. See [profiling Go programs](https://go.dev/blog/pprof) for more information. + ### `--enable-regexp-cmd` ```bash diff --git a/server/server.go b/server/server.go index 3028c5152..1d372eeea 100644 --- a/server/server.go +++ b/server/server.go @@ -24,6 +24,7 @@ import ( "io" "log" "net/http" + "net/http/pprof" "net/url" "os" "os/signal" @@ -126,6 +127,7 @@ type Server struct { ProjectCmdOutputHandler jobs.ProjectCommandOutputHandler ScheduledExecutorService *scheduled.ExecutorService DisableGlobalApplyLock bool + EnableProfilingAPI bool } // Config holds config for server that isn't passed in by the user. @@ -1027,6 +1029,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { WebUsername: userConfig.WebUsername, WebPassword: userConfig.WebPassword, ScheduledExecutorService: scheduledExecutorService, + EnableProfilingAPI: userConfig.EnableProfilingAPI, } validate := validator.New(validator.WithRequiredStructEnabled()) @@ -1068,6 +1071,18 @@ func (s *Server) Start() error { s.Router.HandleFunc("/apply/unlock", s.LocksController.UnlockApply).Methods("DELETE").Queries() } + if s.EnableProfilingAPI { + for p, h := range map[string]http.HandlerFunc{ + "/": pprof.Index, + "/cmdline": pprof.Cmdline, + "/profile": pprof.Profile, + "/symbol": pprof.Symbol, + "/trace": pprof.Trace, + } { + s.Router.HandleFunc("/debug/pprof"+p, h).Methods("GET") + } + } + n := negroni.New(&negroni.Recovery{ Logger: log.New(os.Stdout, "", log.LstdFlags), PrintStack: false, diff --git a/server/user_config.go b/server/user_config.go index 787c6a49e..e981dbeb1 100644 --- a/server/user_config.go +++ b/server/user_config.go @@ -45,6 +45,7 @@ type UserConfig struct { EmojiReaction string `mapstructure:"emoji-reaction"` EnablePolicyChecksFlag bool `mapstructure:"enable-policy-checks"` EnableRegExpCmd bool `mapstructure:"enable-regexp-cmd"` + EnableProfilingAPI bool `mapstructure:"enable-profiling-api"` EnableDiffMarkdownFormat bool `mapstructure:"enable-diff-markdown-format"` ExecutableName string `mapstructure:"executable-name"` // Fail and do not run the Atlantis command request if any of the pre workflow hooks error.