From 23c87c8f5c52bb9bbb631b8432aa99170cd851a3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 16 Jan 2026 22:52:18 +0100 Subject: [PATCH] internal/debug: update pyroscope code - move all code to a dedicated file - remove the debug RPC API handler - add prefix to log messages from pyroscope --- internal/debug/api.go | 77 ------------------------ internal/debug/flags.go | 71 +++++++--------------- internal/debug/pyroscope.go | 116 ++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 128 deletions(-) create mode 100644 internal/debug/pyroscope.go diff --git a/internal/debug/api.go b/internal/debug/api.go index 2a3dd5e7ce..5a2781cc77 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -23,7 +23,6 @@ package debug import ( "bytes" "errors" - "fmt" "io" "os" "os/user" @@ -38,7 +37,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/grafana/pyroscope-go" "github.com/hashicorp/go-bexpr" ) @@ -54,7 +52,6 @@ type HandlerT struct { cpuFile string traceW io.WriteCloser traceFile string - profiler *pyroscope.Profiler } // Verbosity sets the log verbosity ceiling. The verbosity of individual packages @@ -130,80 +127,6 @@ func (h *HandlerT) StopCPUProfile() error { return nil } -// Small wrapper for log.Logger to satisfy pyroscope.Logger interface -type pyroscopeLogger struct { - Logger log.Logger -} - -func (l *pyroscopeLogger) Infof(format string, v ...interface{}) { - l.Logger.Info(fmt.Sprintf(format, v...)) -} - -func (l *pyroscopeLogger) Debugf(format string, v ...interface{}) { - l.Logger.Debug(fmt.Sprintf(format, v...)) -} - -func (l *pyroscopeLogger) Errorf(format string, v ...interface{}) { - l.Logger.Error(fmt.Sprintf(format, v...)) -} - -// StartPyroscopeProfiler starts the Pyroscope profiler for later use -func (h *HandlerT) StartPyroscopeProfiler( - server string, - authUsername string, - authPassword string, - tags map[string]string, -) error { - h.mu.Lock() - defer h.mu.Unlock() - if h.profiler != nil { - log.Info("Pyroscope profiling already started") - return nil - } - profiler, err := pyroscope.Start( - pyroscope.Config{ - ApplicationName: "geth", - ServerAddress: server, - BasicAuthUser: authUsername, - BasicAuthPassword: authPassword, - Logger: &pyroscopeLogger{Logger: log.Root().With("module", "pyroscope")}, - Tags: tags, - ProfileTypes: []pyroscope.ProfileType{ - // Enabling all profile types - pyroscope.ProfileCPU, - pyroscope.ProfileAllocObjects, - pyroscope.ProfileAllocSpace, - pyroscope.ProfileInuseObjects, - pyroscope.ProfileInuseSpace, - pyroscope.ProfileGoroutines, - pyroscope.ProfileMutexCount, - pyroscope.ProfileMutexDuration, - pyroscope.ProfileBlockCount, - pyroscope.ProfileBlockDuration, - }, - }, - ) - if err != nil { - return err - } - h.profiler = profiler - log.Info("Pyroscope profiling started") - return nil -} - -// StopPyroscopeProfiler stops the Pyroscope profiler and returns the error -func (h *HandlerT) StopPyroscopeProfiler() error { - h.mu.Lock() - defer h.mu.Unlock() - if h.profiler != nil { - err := h.profiler.Stop() - h.profiler = nil - return err - } - log.Info("Pyroscope profiling stopped") - return nil -} - // GoTrace turns on tracing for nsec seconds and writes // trace data to file. func (h *HandlerT) GoTrace(file string, nsec uint) error { diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 75c3a34f6d..1c113ba1ac 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -26,7 +26,6 @@ import ( "os" "path/filepath" "runtime" - "strings" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" @@ -141,36 +140,6 @@ var ( Usage: "Write Go execution trace to the given file", Category: flags.LoggingCategory, } - pyroscopeFlag = &cli.BoolFlag{ - Name: "pyroscope", - Usage: "Enable Pyroscope profiling", - Value: false, - Category: flags.LoggingCategory, - } - pyroscopeServerFlag = &cli.StringFlag{ - Name: "pyroscope.server", - Usage: "Pyroscope server URL to push profiling data to", - Value: "http://localhost:4040", - Category: flags.LoggingCategory, - } - pyroscopeAuthUsernameFlag = &cli.StringFlag{ - Name: "pyroscope.username", - Usage: "Pyroscope basic authentication username", - Value: "", - Category: flags.LoggingCategory, - } - pyroscopeAuthPasswordFlag = &cli.StringFlag{ - Name: "pyroscope.password", - Usage: "Pyroscope basic authentication password", - Value: "", - Category: flags.LoggingCategory, - } - pyroscopeTagsFlag = &cli.StringFlag{ - Name: "pyroscope.tags", - Usage: "Comma separated list of key=value tags to add to profiling data", - Value: "", - Category: flags.LoggingCategory, - } ) // Flags holds all command-line flags required for debugging. @@ -337,26 +306,9 @@ func Setup(ctx *cli.Context) error { // Pyroscope profiling if ctx.Bool(pyroscopeFlag.Name) { - pyroscopeServer := ctx.String(pyroscopeServerFlag.Name) - pyroscopeAuthUsername := ctx.String(pyroscopeAuthUsernameFlag.Name) - pyroscopeAuthPassword := ctx.String(pyroscopeAuthPasswordFlag.Name) - - rawTags := ctx.String(pyroscopeTagsFlag.Name) - tags := make(map[string]string) - for _, tag := range strings.Split(rawTags, ",") { - kv := strings.Split(tag, "=") - // Ignore invalid tags - if len(kv) == 2 { - tags[kv[0]] = kv[1] - } + if err := startPyroscope(ctx); err != nil { + return err } - - Handler.StartPyroscopeProfiler( - pyroscopeServer, - pyroscopeAuthUsername, - pyroscopeAuthPassword, - tags, - ) } if len(logFile) > 0 || rotation { @@ -382,7 +334,7 @@ func StartPProf(address string, withMetrics bool) { // Exit stops all running profiles, flushing their output to the // respective file. func Exit() { - Handler.StopPyroscopeProfiler() + stopPyroscope() Handler.StopCPUProfile() Handler.StopGoTrace() if logOutputFile != nil { @@ -403,3 +355,20 @@ func validateLogLocation(path string) error { } return os.Remove(tmp) } + +// Small wrapper for log.Logger to satisfy pyroscope.Logger interface +type pyroscopeLogger struct { + Logger log.Logger +} + +func (l *pyroscopeLogger) Infof(format string, v ...any) { + l.Logger.Info(fmt.Sprintf("Pyroscope: "+format, v...)) +} + +func (l *pyroscopeLogger) Debugf(format string, v ...any) { + l.Logger.Debug(fmt.Sprintf("Pyroscope: "+format, v...)) +} + +func (l *pyroscopeLogger) Errorf(format string, v ...any) { + l.Logger.Error(fmt.Sprintf("Pyroscope: "+format, v...)) +} diff --git a/internal/debug/pyroscope.go b/internal/debug/pyroscope.go new file mode 100644 index 0000000000..e25580edbb --- /dev/null +++ b/internal/debug/pyroscope.go @@ -0,0 +1,116 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package debug + +import ( + "strings" + + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/grafana/pyroscope-go" + "github.com/urfave/cli/v2" +) + +var ( + pyroscopeFlag = &cli.BoolFlag{ + Name: "pyroscope", + Usage: "Enable Pyroscope profiling", + Value: false, + Category: flags.LoggingCategory, + } + pyroscopeServerFlag = &cli.StringFlag{ + Name: "pyroscope.server", + Usage: "Pyroscope server URL to push profiling data to", + Value: "http://localhost:4040", + Category: flags.LoggingCategory, + } + pyroscopeAuthUsernameFlag = &cli.StringFlag{ + Name: "pyroscope.username", + Usage: "Pyroscope basic authentication username", + Value: "", + Category: flags.LoggingCategory, + } + pyroscopeAuthPasswordFlag = &cli.StringFlag{ + Name: "pyroscope.password", + Usage: "Pyroscope basic authentication password", + Value: "", + Category: flags.LoggingCategory, + } + pyroscopeTagsFlag = &cli.StringFlag{ + Name: "pyroscope.tags", + Usage: "Comma separated list of key=value tags to add to profiling data", + Value: "", + Category: flags.LoggingCategory, + } +) + +// This holds the globally-configured Pyroscope instance. +var pyroscopeProfiler *pyroscope.Profiler + +func startPyroscope(ctx *cli.Context) error { + server := ctx.String(pyroscopeServerFlag.Name) + authUsername := ctx.String(pyroscopeAuthUsernameFlag.Name) + authPassword := ctx.String(pyroscopeAuthPasswordFlag.Name) + + rawTags := ctx.String(pyroscopeTagsFlag.Name) + tags := make(map[string]string) + for tag := range strings.SplitSeq(rawTags, ",") { + tag = strings.TrimSpace(tag) + if tag == "" { + continue + } + k, v, _ := strings.Cut(tag, "=") + tags[k] = v + } + + config := pyroscope.Config{ + ApplicationName: "geth", + ServerAddress: server, + BasicAuthUser: authUsername, + BasicAuthPassword: authPassword, + Logger: &pyroscopeLogger{Logger: log.Root()}, + Tags: tags, + ProfileTypes: []pyroscope.ProfileType{ + // Enabling all profile types + pyroscope.ProfileCPU, + pyroscope.ProfileAllocObjects, + pyroscope.ProfileAllocSpace, + pyroscope.ProfileInuseObjects, + pyroscope.ProfileInuseSpace, + pyroscope.ProfileGoroutines, + pyroscope.ProfileMutexCount, + pyroscope.ProfileMutexDuration, + pyroscope.ProfileBlockCount, + pyroscope.ProfileBlockDuration, + }, + } + + profiler, err := pyroscope.Start(config) + if err != nil { + return err + } + pyroscopeProfiler = profiler + log.Info("Enabled Pyroscope") + return nil +} + +func stopPyroscope() { + if pyroscopeProfiler != nil { + pyroscopeProfiler.Stop() + pyroscopeProfiler = nil + } +}