-
Notifications
You must be signed in to change notification settings - Fork 214
Add pprof to ledger service #8631
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,12 @@ import ( | |
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| "net/http/pprof" | ||
|
|
||
| "github.com/rs/zerolog" | ||
| "go.uber.org/atomic" | ||
|
|
||
| "github.com/onflow/flow-go/module/updatable_configs" | ||
| ) | ||
|
|
||
| // adminRequest represents the JSON request body for admin commands. | ||
|
|
@@ -29,19 +32,29 @@ type adminResponse struct { | |
| type adminHandler struct { | ||
| logger zerolog.Logger | ||
| triggerCheckpoint *atomic.Bool | ||
| configManager *updatable_configs.Manager | ||
| commands []string | ||
| } | ||
|
|
||
| // newAdminHandler creates a new admin HTTP handler. | ||
| func newAdminHandler(logger zerolog.Logger, triggerCheckpoint *atomic.Bool) http.Handler { | ||
| func newAdminHandler(logger zerolog.Logger, triggerCheckpoint *atomic.Bool, configManager *updatable_configs.Manager) http.Handler { | ||
| h := &adminHandler{ | ||
| logger: logger.With().Str("component", "admin").Logger(), | ||
| triggerCheckpoint: triggerCheckpoint, | ||
| commands: []string{"ping", "list-commands", "trigger-checkpoint"}, | ||
| configManager: configManager, | ||
| commands: []string{"ping", "list-commands", "trigger-checkpoint", "get-config", "set-config"}, | ||
| } | ||
|
|
||
| mux := http.NewServeMux() | ||
| mux.HandleFunc("/admin/run_command", h.handleCommand) | ||
|
|
||
| // Register pprof handlers for profiling (CPU, heap, goroutine, etc.) | ||
| mux.HandleFunc("/debug/pprof/", pprof.Index) | ||
| mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) | ||
| mux.HandleFunc("/debug/pprof/profile", pprof.Profile) | ||
| mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) | ||
| mux.HandleFunc("/debug/pprof/trace", pprof.Trace) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| return mux | ||
| } | ||
|
|
||
|
|
@@ -78,6 +91,50 @@ func (h *adminHandler) handleCommand(w http.ResponseWriter, r *http.Request) { | |
| result = "checkpoint already triggered" | ||
| } | ||
|
|
||
| case "get-config": | ||
| var configName string | ||
| if err := json.Unmarshal(req.Data, &configName); err != nil { | ||
| h.writeError(w, http.StatusBadRequest, fmt.Sprintf("get-config data must be a string config name: %v", err)) | ||
| return | ||
| } | ||
| field, ok := h.configManager.GetField(configName) | ||
| if !ok { | ||
| h.writeError(w, http.StatusBadRequest, fmt.Sprintf("unknown config field: %s", configName)) | ||
| return | ||
| } | ||
| result = field.Get() | ||
|
|
||
| case "set-config": | ||
| var data map[string]any | ||
| if err := json.Unmarshal(req.Data, &data); err != nil { | ||
| h.writeError(w, http.StatusBadRequest, fmt.Sprintf("set-config data must be a JSON object: %v", err)) | ||
| return | ||
| } | ||
| if len(data) != 1 { | ||
| h.writeError(w, http.StatusBadRequest, fmt.Sprintf("set-config data must have exactly one entry, got %d", len(data))) | ||
| return | ||
| } | ||
| var configName string | ||
| var configValue any | ||
| for k, v := range data { | ||
| configName = k | ||
| configValue = v | ||
| } | ||
| field, ok := h.configManager.GetField(configName) | ||
| if !ok { | ||
| h.writeError(w, http.StatusBadRequest, fmt.Sprintf("unknown config field: %s", configName)) | ||
| return | ||
| } | ||
| oldValue := field.Get() | ||
| if err := field.Set(configValue); err != nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| h.writeError(w, http.StatusBadRequest, fmt.Sprintf("failed to set config %s: %v", configName, err)) | ||
| return | ||
| } | ||
| result = map[string]any{ | ||
| "oldValue": oldValue, | ||
| "newValue": configValue, | ||
| } | ||
|
|
||
| default: | ||
| h.writeError(w, http.StatusBadRequest, fmt.Sprintf("unknown command: %s", req.CommandName)) | ||
| return | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import ( | |
| "os" | ||
| "os/signal" | ||
| "path/filepath" | ||
| "runtime" | ||
| "strings" | ||
| "syscall" | ||
| "time" | ||
|
|
@@ -23,6 +24,8 @@ import ( | |
| "github.com/onflow/flow-go/ledger/remote" | ||
| "github.com/onflow/flow-go/module/irrecoverable" | ||
| "github.com/onflow/flow-go/module/metrics" | ||
| "github.com/onflow/flow-go/module/profiler" | ||
| "github.com/onflow/flow-go/module/updatable_configs" | ||
| ) | ||
|
|
||
| var ( | ||
|
|
@@ -37,6 +40,12 @@ var ( | |
| logLevel = flag.String("loglevel", "info", "Log level (panic, fatal, error, warn, info, debug)") | ||
| maxRequestSize = flag.Uint("max-request-size", 1<<30, "Maximum request message size in bytes (default: 1 GiB)") | ||
| maxResponseSize = flag.Uint("max-response-size", 1<<30, "Maximum response message size in bytes (default: 1 GiB)") | ||
|
|
||
| profilerEnabled = flag.Bool("profiler-enabled", false, "Whether to enable the auto-profiler") | ||
| profilerDir = flag.String("profiler-dir", "profiler", "Directory to create auto-profiler profiles") | ||
| profilerInterval = flag.Duration("profiler-interval", 15*time.Minute, "Interval between auto-profiler runs") | ||
| profilerDuration = flag.Duration("profiler-duration", 10*time.Second, "Duration of each auto-profiler run") | ||
| profileUploaderEnabled = flag.Bool("profile-uploader-enabled", false, "Whether to upload profiles to a remote uploader (disabled for ledger service)") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this flag's only effect is a warning that it is ignored. |
||
| ) | ||
|
|
||
| func main() { | ||
|
|
@@ -60,6 +69,68 @@ func main() { | |
| Str("service", "ledger"). | ||
| Logger() | ||
|
|
||
| // Initialize updatable config manager and auto-profiler. | ||
| // The profiler is configured via admin get-config/set-config commands. | ||
| configManager := updatable_configs.NewManager() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| profilerConfig := profiler.ProfilerConfig{ | ||
| Enabled: *profilerEnabled, | ||
| UploaderEnabled: false, // ledger service does not support remote profile upload | ||
| Dir: *profilerDir, | ||
| Interval: *profilerInterval, | ||
| Duration: *profilerDuration, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| if *profileUploaderEnabled { | ||
| logger.Warn().Msg("profile-uploader-enabled is not supported by the ledger service, ignoring") | ||
| } | ||
|
|
||
| autoProfiler, err := profiler.New(logger, &profiler.NoopUploader{}, profilerConfig) | ||
| if err != nil { | ||
| logger.Fatal().Err(err).Msg("failed to create auto-profiler") | ||
| } | ||
|
|
||
| err = configManager.RegisterBoolConfig("profiler-enabled", autoProfiler.Enabled, autoProfiler.SetEnabled) | ||
| if err != nil { | ||
| logger.Fatal().Err(err).Msg("failed to register profiler-enabled config") | ||
| } | ||
| err = configManager.RegisterDurationConfig( | ||
| "profiler-trigger", | ||
| func() time.Duration { return profilerConfig.Duration }, | ||
| func(d time.Duration) error { return autoProfiler.TriggerRun(d) }, | ||
| ) | ||
| if err != nil { | ||
| logger.Fatal().Err(err).Msg("failed to register profiler-trigger config") | ||
| } | ||
| err = configManager.RegisterUintConfig( | ||
| "profiler-set-mem-profile-rate", | ||
| func() uint { return uint(runtime.MemProfileRate) }, | ||
| func(r uint) error { runtime.MemProfileRate = int(r); return nil }, | ||
| ) | ||
| if err != nil { | ||
| logger.Fatal().Err(err).Msg("failed to register profiler-set-mem-profile-rate config") | ||
| } | ||
| currentBlockRate := new(uint) | ||
| err = configManager.RegisterUintConfig( | ||
| "profiler-set-block-profile-rate", | ||
| func() uint { return *currentBlockRate }, | ||
| func(r uint) error { currentBlockRate = &r; runtime.SetBlockProfileRate(int(r)); return nil }, | ||
| ) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| logger.Fatal().Err(err).Msg("failed to register profiler-set-block-profile-rate config") | ||
| } | ||
| err = configManager.RegisterUintConfig( | ||
| "profiler-set-mutex-profile-fraction", | ||
| func() uint { return uint(runtime.SetMutexProfileFraction(-1)) }, | ||
| func(r uint) error { _ = runtime.SetMutexProfileFraction(int(r)); return nil }, | ||
| ) | ||
| if err != nil { | ||
| logger.Fatal().Err(err).Msg("failed to register profiler-set-mutex-profile-fraction config") | ||
| } | ||
|
|
||
| go func() { | ||
| <-autoProfiler.Ready() | ||
| logger.Info().Bool("enabled", autoProfiler.Enabled()).Msg("auto-profiler ready") | ||
| }() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
|
|
||
| // Validate that at least one address is provided | ||
| if *ledgerServiceTCP == "" && *ledgerServiceSocket == "" { | ||
| logger.Fatal().Msg("at least one of --ledger-service-tcp or --ledger-service-socket must be provided") | ||
|
|
@@ -72,6 +143,7 @@ func main() { | |
| Str("admin_addr", *adminAddr). | ||
| Uint("metrics_port", *metricsPort). | ||
| Int("mtrie_cache_size", *mtrieCacheSize). | ||
| Bool("profiler_enabled", *profilerEnabled). | ||
| Msg("starting ledger service") | ||
|
|
||
| // Create trigger for manual checkpointing (used by admin command) | ||
|
|
@@ -229,7 +301,7 @@ func main() { | |
| // This is a lightweight HTTP-only server (no gRPC proxy layer) | ||
| var adminServer *http.Server | ||
| if *adminAddr != "" { | ||
| adminHandler := newAdminHandler(logger, triggerCheckpointOnNextSegmentFinish) | ||
| adminHandler := newAdminHandler(logger, triggerCheckpointOnNextSegmentFinish, configManager) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| adminServer = &http.Server{ | ||
| Addr: *adminAddr, | ||
| Handler: adminHandler, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
cmd/ledger/README.mddocuments the admin commands and flags but was not updated with the new profiler flags and the get-config/set-config commands.