From 7958ed9e1fb99b90cfe414bb11860dca4ea8323a Mon Sep 17 00:00:00 2001 From: Neha Sherpa Date: Wed, 25 Feb 2026 22:57:49 -0800 Subject: [PATCH 1/2] feat: add pprof debug server for CPU and memory profiling --- cmd/cachewd/main.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cmd/cachewd/main.go b/cmd/cachewd/main.go index 99187b3..d23aead 100644 --- a/cmd/cachewd/main.go +++ b/cmd/cachewd/main.go @@ -6,6 +6,7 @@ import ( "log/slog" "net" "net/http" + _ "net/http/pprof" //nolint:gosec "os" "strings" "time" @@ -36,6 +37,7 @@ type GlobalConfig struct { State string `hcl:"state" default:"./state" help:"Base directory for all state (git mirrors, cache, etc.)."` Bind string `hcl:"bind" default:"127.0.0.1:8080" help:"Bind address for the server."` URL string `hcl:"url" default:"http://127.0.0.1:8080/" help:"Base URL for cachewd."` + PprofBind string `hcl:"pprof-bind" default:":6060" help:"Bind address for the pprof debug server (CPU/memory profiling). Empty string to disable."` SchedulerConfig jobscheduler.Config `hcl:"scheduler,block"` LoggingConfig logging.Config `hcl:"log,block"` MetricsConfig metrics.Config `hcl:"metrics,block"` @@ -99,6 +101,8 @@ func main() { kctx.FatalIfErrorf(err, "failed to start metrics server") } + servePprof(ctx, globalConfig.PprofBind) + logger.InfoContext(ctx, "Starting cachewd", slog.String("bind", globalConfig.Bind)) server := newServer(ctx, mux, globalConfig.Bind, globalConfig.MetricsConfig) @@ -172,6 +176,33 @@ func newMux(ctx context.Context, cr *cache.Registry, sr *strategy.Registry, prov return mux, nil } +func servePprof(ctx context.Context, bind string) { + if bind == "" { + return + } + logger := logging.FromContext(ctx) + server := &http.Server{ + Addr: bind, + Handler: http.DefaultServeMux, + ReadTimeout: 2 * time.Minute, + ReadHeaderTimeout: 5 * time.Second, + } + go func() { + logger.InfoContext(ctx, "Starting pprof server", slog.String("bind", bind)) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logger.ErrorContext(ctx, "pprof server error", "error", err) + } + }() + go func() { + <-ctx.Done() + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := server.Shutdown(shutdownCtx); err != nil { + logger.ErrorContext(shutdownCtx, "pprof server shutdown error", "error", err) + } + }() +} + // extractPathPrefix extracts the strategy name, path prefix from a request path. // Examples: /git/... -> "git", /gomod/... -> "gomod", /api/v1/... -> "api". func extractPathPrefix(path string) string { From 048e9a59a96f9c918f7e2fd47a487e16e6c49d35 Mon Sep 17 00:00:00 2001 From: Neha Sherpa Date: Thu, 26 Feb 2026 10:29:28 -0800 Subject: [PATCH 2/2] fix: resolve merge conflicts --- cmd/cachewd/main.go | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/cmd/cachewd/main.go b/cmd/cachewd/main.go index d23aead..18151e3 100644 --- a/cmd/cachewd/main.go +++ b/cmd/cachewd/main.go @@ -37,7 +37,6 @@ type GlobalConfig struct { State string `hcl:"state" default:"./state" help:"Base directory for all state (git mirrors, cache, etc.)."` Bind string `hcl:"bind" default:"127.0.0.1:8080" help:"Bind address for the server."` URL string `hcl:"url" default:"http://127.0.0.1:8080/" help:"Base URL for cachewd."` - PprofBind string `hcl:"pprof-bind" default:":6060" help:"Bind address for the pprof debug server (CPU/memory profiling). Empty string to disable."` SchedulerConfig jobscheduler.Config `hcl:"scheduler,block"` LoggingConfig logging.Config `hcl:"log,block"` MetricsConfig metrics.Config `hcl:"metrics,block"` @@ -101,8 +100,6 @@ func main() { kctx.FatalIfErrorf(err, "failed to start metrics server") } - servePprof(ctx, globalConfig.PprofBind) - logger.InfoContext(ctx, "Starting cachewd", slog.String("bind", globalConfig.Bind)) server := newServer(ctx, mux, globalConfig.Bind, globalConfig.MetricsConfig) @@ -169,6 +166,11 @@ func newMux(ctx context.Context, cr *cache.Registry, sr *strategy.Registry, prov _, _ = fmt.Fprintln(w, logging.GetLevel().String()) }) + mux.Handle("/admin/pprof/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.URL.Path = "/debug/pprof/" + r.URL.Path[len("/admin/pprof/"):] + http.DefaultServeMux.ServeHTTP(w, r) + })) + if err := config.Load(ctx, cr, sr, providersConfigHCL, mux, vars); err != nil { return nil, errors.Errorf("load config: %w", err) } @@ -176,33 +178,6 @@ func newMux(ctx context.Context, cr *cache.Registry, sr *strategy.Registry, prov return mux, nil } -func servePprof(ctx context.Context, bind string) { - if bind == "" { - return - } - logger := logging.FromContext(ctx) - server := &http.Server{ - Addr: bind, - Handler: http.DefaultServeMux, - ReadTimeout: 2 * time.Minute, - ReadHeaderTimeout: 5 * time.Second, - } - go func() { - logger.InfoContext(ctx, "Starting pprof server", slog.String("bind", bind)) - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logger.ErrorContext(ctx, "pprof server error", "error", err) - } - }() - go func() { - <-ctx.Done() - shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := server.Shutdown(shutdownCtx); err != nil { - logger.ErrorContext(shutdownCtx, "pprof server shutdown error", "error", err) - } - }() -} - // extractPathPrefix extracts the strategy name, path prefix from a request path. // Examples: /git/... -> "git", /gomod/... -> "gomod", /api/v1/... -> "api". func extractPathPrefix(path string) string {