feat(debug): enable pprof memory profiling in dev/debug mode (#518)
This commit is contained in:
+10
-4
@@ -25,7 +25,7 @@ BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || powershell -c
|
||||
|
||||
LDFLAGS += -X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.BuildTime=$(BUILD_TIME)
|
||||
|
||||
.PHONY: all build build-all build-agentctl build-agentctl-linux build-mock-agent clean run dev test test-e2e test-sprites-e2e lint fmt vet help
|
||||
.PHONY: all build build-all build-agentctl build-agentctl-linux build-mock-agent clean run dev start-debug test test-e2e test-sprites-e2e lint fmt vet help
|
||||
|
||||
## Default target
|
||||
all: build
|
||||
@@ -71,10 +71,15 @@ run: build
|
||||
@echo "Running $(BINARY_NAME)..."
|
||||
./$(BUILD_DIR)/$(BINARY_NAME)
|
||||
|
||||
## Run in development mode
|
||||
## Run in development mode (with pprof enabled)
|
||||
dev: build
|
||||
@echo "Running $(BINARY_NAME) in development mode..."
|
||||
@exec ./$(BUILD_DIR)/$(BINARY_NAME)
|
||||
@KANDEV_DEBUG_PPROF_ENABLED=true exec ./$(BUILD_DIR)/$(BINARY_NAME)
|
||||
|
||||
## Run in debug mode (pprof + debug logging)
|
||||
start-debug: build
|
||||
@echo "Running $(BINARY_NAME) in debug mode (pprof + debug logging)..."
|
||||
@KANDEV_DEBUG_PPROF_ENABLED=true KANDEV_LOG_LEVEL=debug exec ./$(BUILD_DIR)/$(BINARY_NAME)
|
||||
|
||||
## Run tests
|
||||
test:
|
||||
@@ -149,7 +154,8 @@ help:
|
||||
@echo " build-mock-agent Build the mock-agent binary for testing"
|
||||
@echo " clean Remove build artifacts"
|
||||
@echo " run Build and run the application"
|
||||
@echo " dev Run in development mode with a local database"
|
||||
@echo " dev Run in development mode (pprof enabled)"
|
||||
@echo " start-debug Run in debug mode (pprof + debug logging)"
|
||||
@echo " test Run tests"
|
||||
@echo " test-e2e Run E2E adapter tests (real agents, costs money)"
|
||||
@echo " test-coverage Run tests with coverage report"
|
||||
|
||||
@@ -431,6 +431,7 @@ type routeParams struct {
|
||||
secretStore secrets.SecretStore
|
||||
mcpConfigSvc *mcpconfig.Service
|
||||
webInternalURL string
|
||||
pprofEnabled bool
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
@@ -590,6 +591,11 @@ func registerMCPAndDebugRoutes(
|
||||
|
||||
debughandlers.RegisterRoutes(p.router, p.log)
|
||||
p.log.Debug("Registered Debug handlers (HTTP)")
|
||||
|
||||
if p.pprofEnabled {
|
||||
debughandlers.RegisterPprofRoutes(p.router, p.log)
|
||||
debughandlers.RegisterMemoryRoute(p.router, p.log)
|
||||
}
|
||||
}
|
||||
|
||||
// runGracefulShutdown gracefully stops all services and runs cleanups.
|
||||
|
||||
@@ -446,6 +446,7 @@ func buildHTTPServer(
|
||||
secretStore: repos.Secrets,
|
||||
mcpConfigSvc: mcpconfig.NewService(repos.AgentSettings),
|
||||
webInternalURL: cfg.Server.WebInternalURL,
|
||||
pprofEnabled: cfg.Debug.PprofEnabled,
|
||||
log: log,
|
||||
})
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ type Config struct {
|
||||
RepositoryDiscovery RepositoryDiscoveryConfig `mapstructure:"repositoryDiscovery"`
|
||||
Worktree WorktreeConfig `mapstructure:"worktree"`
|
||||
RepoClone RepoCloneConfig `mapstructure:"repoClone"`
|
||||
Debug DebugConfig `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
// ResolvedHomeDir returns the Kandev home directory (~/.kandev for local dev).
|
||||
@@ -153,6 +154,13 @@ type RepoCloneConfig struct {
|
||||
BasePath string `mapstructure:"basePath"` // Base directory for cloned repos (default: ~/.kandev/repos)
|
||||
}
|
||||
|
||||
// DebugConfig holds debug/profiling configuration.
|
||||
type DebugConfig struct {
|
||||
// PprofEnabled enables pprof endpoints at /debug/pprof/ and /api/v1/debug/memory.
|
||||
// Controlled via KANDEV_DEBUG_PPROF_ENABLED env var. Default: false.
|
||||
PprofEnabled bool `mapstructure:"pprofEnabled"`
|
||||
}
|
||||
|
||||
// AgentConfig holds agent runtime configuration.
|
||||
// Note: Runtime selection is now per-task based on executor type, not global.
|
||||
// The Standalone runtime (agentctl) always runs as a core service.
|
||||
@@ -282,6 +290,9 @@ func setDefaults(v *viper.Viper) {
|
||||
|
||||
// RepoClone defaults
|
||||
v.SetDefault("repoClone.basePath", "")
|
||||
|
||||
// Debug defaults
|
||||
v.SetDefault("debug.pprofEnabled", false)
|
||||
}
|
||||
|
||||
// DefaultDockerHost returns the platform-appropriate Docker socket path.
|
||||
@@ -338,6 +349,7 @@ func LoadWithPath(configPath string) (*Config, error) {
|
||||
_ = v.BindEnv("dataDir", "KANDEV_DATA_DIR")
|
||||
_ = v.BindEnv("logging.level", "KANDEV_LOG_LEVEL")
|
||||
_ = v.BindEnv("events.namespace", "KANDEV_EVENTS_NAMESPACE")
|
||||
_ = v.BindEnv("debug.pprofEnabled", "KANDEV_DEBUG_PPROF_ENABLED")
|
||||
|
||||
// Configure config file
|
||||
v.SetConfigName("config")
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"runtime"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kandev/kandev/internal/common/logger"
|
||||
)
|
||||
|
||||
// RegisterPprofRoutes mounts the standard net/http/pprof handlers on the Gin router
|
||||
// at /debug/pprof/. These endpoints are only registered when debug.pprofEnabled is true.
|
||||
func RegisterPprofRoutes(router *gin.Engine, log *logger.Logger) {
|
||||
g := router.Group("/debug/pprof")
|
||||
g.GET("/", gin.WrapF(pprof.Index))
|
||||
g.GET("/cmdline", gin.WrapF(pprof.Cmdline))
|
||||
g.GET("/profile", gin.WrapF(pprof.Profile))
|
||||
g.GET("/symbol", gin.WrapF(pprof.Symbol))
|
||||
g.POST("/symbol", gin.WrapF(pprof.Symbol))
|
||||
g.GET("/trace", gin.WrapF(pprof.Trace))
|
||||
|
||||
for _, name := range []string{"allocs", "block", "goroutine", "heap", "mutex", "threadcreate"} {
|
||||
g.GET("/"+name, gin.WrapH(pprof.Handler(name)))
|
||||
}
|
||||
|
||||
log.Info("pprof endpoints registered at /debug/pprof/")
|
||||
}
|
||||
|
||||
// RegisterMemoryRoute registers GET /api/v1/debug/memory which returns a JSON summary
|
||||
// of runtime memory statistics. Useful for quick health checks without pulling a full
|
||||
// pprof heap profile.
|
||||
func RegisterMemoryRoute(router *gin.Engine, log *logger.Logger) {
|
||||
router.GET("/api/v1/debug/memory", handleMemoryStats())
|
||||
log.Info("Memory stats endpoint registered at /api/v1/debug/memory")
|
||||
}
|
||||
|
||||
func handleMemoryStats() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"heap_alloc_mb": float64(m.HeapAlloc) / (1024 * 1024),
|
||||
"heap_inuse_mb": float64(m.HeapInuse) / (1024 * 1024),
|
||||
"heap_sys_mb": float64(m.HeapSys) / (1024 * 1024),
|
||||
"heap_objects": m.HeapObjects,
|
||||
"goroutines": runtime.NumGoroutine(),
|
||||
"num_gc": m.NumGC,
|
||||
"sys_mb": float64(m.Sys) / (1024 * 1024),
|
||||
"stack_inuse_mb": float64(m.StackInuse) / (1024 * 1024),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export async function runDev({ repoRoot, backendPort, webPort }: DevOptions): Pr
|
||||
const extra: Record<string, string> = {
|
||||
KANDEV_DATABASE_PATH: dbPath,
|
||||
KANDEV_MOCK_AGENT: process.env.KANDEV_MOCK_AGENT || "true",
|
||||
KANDEV_DEBUG_PPROF_ENABLED: "true",
|
||||
};
|
||||
const backendEnv = buildBackendEnv({ ports, extra });
|
||||
const webEnv = buildWebEnv({ ports, debug: true });
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ async function prepareReleaseBundle({
|
||||
KANDEV_AGENT_STANDALONE_PORT: String(agentctlPort),
|
||||
KANDEV_DATABASE_PATH: dbPath,
|
||||
KANDEV_LOG_LEVEL: logLevel,
|
||||
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true" } : {}),
|
||||
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
|
||||
};
|
||||
|
||||
const webEnv: NodeJS.ProcessEnv = {
|
||||
|
||||
@@ -118,7 +118,7 @@ export async function runStart({
|
||||
logLevel,
|
||||
extra: {
|
||||
KANDEV_DATABASE_PATH: dbPath,
|
||||
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true" } : {}),
|
||||
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
|
||||
},
|
||||
});
|
||||
const webEnv = buildWebEnv({ ports, production: true, debug });
|
||||
|
||||
Reference in New Issue
Block a user