Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"SCAFFOLD_DOCKER_IMAGE": "cortex-axon-agent:local",
"PORT" : "7399",
"BUILTIN_PLUGIN_DIR": "${workspaceFolder}/agent/server/snykbroker/plugins",
"ENABLE_PPROF": "true"
}
},

Expand Down
10 changes: 10 additions & 0 deletions agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type AgentConfig struct {
WebhookServerPort int
SnykBrokerPort int
EnableApiProxy bool
EnablePprof bool
FailWaitTime time.Duration
AutoRegisterFrequency time.Duration
VerboseOutput bool
Expand Down Expand Up @@ -109,6 +110,9 @@ func (ac AgentConfig) Print() {
} else {
fmt.Println("\tAPI Proxy: Disabled")
}
if ac.EnablePprof {
fmt.Println("\tpprof: Enabled at /pprof")
}

fmt.Printf("\tFast fail time: %v\n", ac.FailWaitTime)
}
Expand Down Expand Up @@ -180,6 +184,11 @@ func NewAgentEnvConfig() AgentConfig {
dryRun = dryRunEnv == "true" || dryRunEnv == "1"
}

enablePprof := false
if pprofEnv := os.Getenv("ENABLE_PPROF"); pprofEnv != "" {
enablePprof = pprofEnv == "true" || pprofEnv == "1"
}

dequeueWaitTime := 1 * time.Second
if dequeueWaitTimeEnv := os.Getenv("DEQUEUE_WAIT_TIME"); dequeueWaitTimeEnv != "" {
dwt, err := time.ParseDuration(dequeueWaitTimeEnv)
Expand Down Expand Up @@ -243,6 +252,7 @@ func NewAgentEnvConfig() AgentConfig {
WebhookServerPort: WebhookServerPort,
SnykBrokerPort: snykBrokerPort,
EnableApiProxy: true,
EnablePprof: enablePprof,
FailWaitTime: time.Second * 2,
PluginDirs: []string{"./plugins"},
AutoRegisterFrequency: reregisterFrequency,
Expand Down
51 changes: 51 additions & 0 deletions agent/server/http/pprof_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package http

import (
"io"
"net/http"
"net/http/pprof"

"github.com/cortexapps/axon/config"
"github.com/gorilla/mux"
"go.uber.org/zap"
)

const PprofPathRoot = "/pprof"

type pprofHandler struct {
io.Closer
config config.AgentConfig
logger *zap.Logger
}

func NewPprofHandler(config config.AgentConfig, logger *zap.Logger) RegisterableHandler {
return &pprofHandler{
config: config,
logger: logger,
}
}

func (h *pprofHandler) RegisterRoutes(m *mux.Router) error {
sub := m.PathPrefix(PprofPathRoot).Subrouter()

// The index page generates relative links to the named profiles, so they
// resolve under /pprof/. Individual profiles must be registered explicitly
// since pprof.Index only auto-dispatches under the hardcoded /debug/pprof/.
sub.HandleFunc("/", pprof.Index)
sub.HandleFunc("/cmdline", pprof.Cmdline)
sub.HandleFunc("/profile", pprof.Profile)
sub.HandleFunc("/symbol", pprof.Symbol)
sub.HandleFunc("/trace", pprof.Trace)

for _, name := range []string{"heap", "goroutine", "allocs", "block", "mutex", "threadcreate"} {
sub.Handle("/"+name, pprof.Handler(name))
}

h.logger.Info("pprof profiling endpoint enabled", zap.String("path", PprofPathRoot))

return nil
}

func (h *pprofHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
panic("ServeHTTP should not be called directly")
}
49 changes: 49 additions & 0 deletions agent/server/http/pprof_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package http

import (
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/cortexapps/axon/config"
"github.com/gorilla/mux"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)

func newPprofTestServer(t *testing.T) *httptest.Server {
t.Helper()
handler := NewPprofHandler(config.AgentConfig{}, zap.NewNop())
router := mux.NewRouter()
require.NoError(t, handler.RegisterRoutes(router))
ts := httptest.NewServer(router)
t.Cleanup(ts.Close)
return ts
}

func TestPprofIndex(t *testing.T) {
ts := newPprofTestServer(t)

resp, err := http.Get(ts.URL + "/pprof/")
require.NoError(t, err)
defer resp.Body.Close()

require.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(body), "goroutine")
}

func TestPprofHeapProfile(t *testing.T) {
ts := newPprofTestServer(t)

resp, err := http.Get(ts.URL + "/pprof/heap")
require.NoError(t, err)
defer resp.Body.Close()

require.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NotEmpty(t, body)
}
5 changes: 5 additions & 0 deletions agent/server/main_http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,10 @@ func NewMainHttpServer(p MainHttpServerParams) cortexHttp.Server {
httpServer.RegisterHandler(metricsHandler)
}

if config.EnablePprof {
pprofHandler := cortexHttp.NewPprofHandler(config, p.Logger)
httpServer.RegisterHandler(pprofHandler)
}

return httpServer
}
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ WORKDIR /agent
# when the scheduled Trivy scan flags OS-package CVEs whose fixes are already
# in the archive — the cache is just serving a stale layer. Leaves the rest of
# the build (Go, npm, snyk-broker clone) hitting cache as normal.
ARG APT_CACHE_BUST=2026-05-27
ARG APT_CACHE_BUST=2026-05-29
RUN echo "apt cache bust: $APT_CACHE_BUST" \
&& apt-get update && apt-get upgrade -y && apt-get install -y \
protobuf-compiler git python3 python3-venv wget build-essential openssl jq
Expand Down
Loading