From 448193b0fd091b5357f792f20d2152defe0ea27c Mon Sep 17 00:00:00 2001 From: ziggie Date: Wed, 25 Sep 2024 12:56:18 +0200 Subject: [PATCH] multi: separate profiler config --- config.go | 90 ++++++++++++++++++++++++------------------------- lncfg/error.go | 25 ++++++++++++++ lncfg/pprof.go | 68 +++++++++++++++++++++++++++++++++++++ lnd.go | 18 +++++----- sample-lnd.conf | 64 +++++++++++++++++++++++++++-------- 5 files changed, 196 insertions(+), 69 deletions(-) create mode 100644 lncfg/error.go create mode 100644 lncfg/pprof.go diff --git a/config.go b/config.go index 513c217e6..601bdfe1f 100644 --- a/config.go +++ b/config.go @@ -352,12 +352,12 @@ type Config struct { DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify ,=,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` - CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"` + CPUProfile string `long:"cpuprofile" description:"DEPRECATED: Use 'pprof.cpuprofile' option. Write CPU profile to the specified file" hidden:"true"` + Profile string `long:"profile" description:"DEPRECATED: Use 'pprof.profile' option. Enable HTTP profiling on either a port or host:port" hidden:"true"` + BlockingProfile int `long:"blockingprofile" description:"DEPRECATED: Use 'pprof.blockingprofile' option. Used to enable a blocking profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every blocking event, and 0 including no events." hidden:"true"` + MutexProfile int `long:"mutexprofile" description:"DEPRECATED: Use 'pprof.mutexprofile' option. Used to Enable a mutex profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every mutex event, and 0 including no events." hidden:"true"` - Profile string `long:"profile" description:"Enable HTTP profiling on either a port or host:port"` - - BlockingProfile int `long:"blockingprofile" description:"Used to enable a blocking profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every blocking event, and 0 including no events."` - MutexProfile int `long:"mutexprofile" description:"Used to Enable a mutex profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every mutex event, and 0 including no events."` + Pprof *lncfg.Pprof `group:"Pprof" namespace:"pprof"` UnsafeDisconnect bool `long:"unsafe-disconnect" description:"DEPRECATED: Allows the rpcserver to intentionally disconnect from peers with open channels. THIS FLAG WILL BE REMOVED IN 0.10.0" hidden:"true"` UnsafeReplay bool `long:"unsafe-replay" description:"Causes a link to replay the adds on its commitment txn after starting up, this enables testing of the sphinx replay logic."` @@ -821,7 +821,8 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { cleanCfg, err := ValidateConfig( cfg, interceptor, fileParser, flagParser, ) - if usageErr, ok := err.(*usageError); ok { + var usageErr *lncfg.UsageError + if errors.As(err, &usageErr) { // The logging system might not yet be initialized, so we also // write to stderr to make sure the error appears somewhere. _, _ = fmt.Fprintln(os.Stderr, usageMessage) @@ -830,9 +831,9 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { // The log subsystem might not yet be initialized. But we still // try to log the error there since some packaging solutions // might only look at the log and not stdout/stderr. - ltndLog.Warnf("Error validating config: %v", usageErr.err) + ltndLog.Warnf("Error validating config: %v", err) - return nil, usageErr.err + return nil, err } if err != nil { // The log subsystem might not yet be initialized. But we still @@ -856,18 +857,6 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { return cleanCfg, nil } -// usageError is an error type that signals a problem with the supplied flags. -type usageError struct { - err error -} - -// Error returns the error string. -// -// NOTE: This is part of the error interface. -func (u *usageError) Error() string { - return u.err.Error() -} - // ValidateConfig check the given configuration to be sane. This makes sure no // illegal values or combination of values are set. All file system paths are // normalized. The cleaned up config is returned on success. @@ -1347,31 +1336,6 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount) } - // Validate profile port or host:port. - if cfg.Profile != "" { - str := "%s: The profile port must be between 1024 and 65535" - - // Try to parse Profile as a host:port. - _, hostPort, err := net.SplitHostPort(cfg.Profile) - if err == nil { - // Determine if the port is valid. - profilePort, err := strconv.Atoi(hostPort) - if err != nil || profilePort < 1024 || profilePort > 65535 { - return nil, &usageError{mkErr(str)} - } - } else { - // Try to parse Profile as a port. - profilePort, err := strconv.Atoi(cfg.Profile) - if err != nil || profilePort < 1024 || profilePort > 65535 { - return nil, &usageError{mkErr(str)} - } - - // Since the user just set a port, we will serve debugging - // information over localhost. - cfg.Profile = net.JoinHostPort("127.0.0.1", cfg.Profile) - } - } - // We'll now construct the network directory which will be where we // store all the data specific to this chain/network. cfg.networkDir = filepath.Join( @@ -1469,7 +1433,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.LogWriter) if err != nil { str := "error parsing debug level: %v" - return nil, &usageError{mkErr(str, err)} + return nil, &lncfg.UsageError{Err: mkErr(str, err)} } // At least one RPCListener is required. So listen on localhost per @@ -1700,6 +1664,39 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, return nil, mkErr("custom-message: %v", err) } + // Map old pprof flags to new pprof group flags. + // + // NOTE: This is a temporary measure to ensure compatibility with old + // flags. + if cfg.CPUProfile != "" { + if cfg.Pprof.CPUProfile != "" { + return nil, mkErr("cpuprofile and pprof.cpuprofile " + + "are mutually exclusive") + } + cfg.Pprof.CPUProfile = cfg.CPUProfile + } + if cfg.Profile != "" { + if cfg.Pprof.Profile != "" { + return nil, mkErr("profile and pprof.profile " + + "are mutually exclusive") + } + cfg.Pprof.Profile = cfg.Profile + } + if cfg.BlockingProfile != 0 { + if cfg.Pprof.BlockingProfile != 0 { + return nil, mkErr("blockingprofile and " + + "pprof.blockingprofile are mutually exclusive") + } + cfg.Pprof.BlockingProfile = cfg.BlockingProfile + } + if cfg.MutexProfile != 0 { + if cfg.Pprof.MutexProfile != 0 { + return nil, mkErr("mutexprofile and " + + "pprof.mutexprofile are mutually exclusive") + } + cfg.Pprof.MutexProfile = cfg.MutexProfile + } + // Validate the subconfigs for workers, caches, and the tower client. err = lncfg.Validate( cfg.Workers, @@ -1714,6 +1711,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, cfg.Htlcswitch, cfg.Invoices, cfg.Routing, + cfg.Pprof, ) if err != nil { return nil, err diff --git a/lncfg/error.go b/lncfg/error.go new file mode 100644 index 000000000..d8bca887b --- /dev/null +++ b/lncfg/error.go @@ -0,0 +1,25 @@ +package lncfg + +import "fmt" + +// UsageError is an error type that signals a problem with the supplied flags. +type UsageError struct { + Err error +} + +// Error returns the error string. +// +// NOTE: This is part of the error interface. +func (u *UsageError) Error() string { + return u.Err.Error() +} + +// Unwrap returns the underlying error. +func (u *UsageError) Unwrap() error { + return u.Err +} + +// mkErr creates a new error from a string. +func mkErr(format string, args ...interface{}) error { + return fmt.Errorf(format, args...) +} diff --git a/lncfg/pprof.go b/lncfg/pprof.go new file mode 100644 index 000000000..e136b2a8e --- /dev/null +++ b/lncfg/pprof.go @@ -0,0 +1,68 @@ +package lncfg + +import ( + "net" + "strconv" +) + +// Pprof holds the configuration options for LND's built-in pprof server. +// +//nolint:lll +type Pprof struct { + CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"` + + Profile string `long:"profile" description:"Enable HTTP profiling on either a port or host:port"` + + BlockingProfile int `long:"blockingprofile" description:"Used to enable a blocking profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every blocking event, and 0 including no events."` + + MutexProfile int `long:"mutexprofile" description:"Used to Enable a mutex profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every mutex event, and 0 including no events."` +} + +// Validate checks the values configured for the profiler. +func (p *Pprof) Validate() error { + if p.BlockingProfile > 0 { + log.Warn("Blocking profile enabled only useful for " + + "debugging because of significant performance impact") + } + + if p.MutexProfile > 0 { + log.Warn("Mutex profile enabled only useful for " + + "debugging because of significant performance impact") + } + + if p.CPUProfile != "" { + log.Warn("CPU profile enabled only useful for " + + "debugging because of significant performance impact") + } + + if p.Profile != "" { + str := "%v: The profile port must be between 1024 and 65535" + + // Try to parse Profile as a host:port. + _, hostPort, err := net.SplitHostPort(p.Profile) + if err == nil { + // Determine if the port is valid. + profilePort, err := strconv.Atoi(hostPort) + + if err != nil || profilePort < 1024 || + profilePort > 65535 { + + return &UsageError{Err: mkErr(str, hostPort)} + } + } else { + // Try to parse Profile as a port. + profilePort, err := strconv.Atoi(p.Profile) + if err != nil || profilePort < 1024 || + profilePort > 65535 { + + return &UsageError{Err: mkErr(str, p.Profile)} + } + + // Since the user just set a port, we will serve + // debugging information over localhost. + p.Profile = net.JoinHostPort("127.0.0.1", p.Profile) + } + } + + return nil +} diff --git a/lnd.go b/lnd.go index 849e1f574..8fb38b8f3 100644 --- a/lnd.go +++ b/lnd.go @@ -193,7 +193,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, defer cancel() // Enable http profiling server if requested. - if cfg.Profile != "" { + if cfg.Pprof.Profile != "" { // Create the http handler. pprofMux := http.NewServeMux() pprofMux.HandleFunc("/debug/pprof/", pprof.Index) @@ -202,11 +202,11 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, pprofMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) pprofMux.HandleFunc("/debug/pprof/trace", pprof.Trace) - if cfg.BlockingProfile != 0 { - runtime.SetBlockProfileRate(cfg.BlockingProfile) + if cfg.Pprof.BlockingProfile != 0 { + runtime.SetBlockProfileRate(cfg.Pprof.BlockingProfile) } - if cfg.MutexProfile != 0 { - runtime.SetMutexProfileFraction(cfg.MutexProfile) + if cfg.Pprof.MutexProfile != 0 { + runtime.SetMutexProfileFraction(cfg.Pprof.MutexProfile) } // Redirect all requests to the pprof handler, thus visiting @@ -216,11 +216,11 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, "/debug/pprof/", http.StatusSeeOther, )) - ltndLog.Infof("Pprof listening on %v", cfg.Profile) + ltndLog.Infof("Pprof listening on %v", cfg.Pprof.Profile) // Create the pprof server. pprofServer := &http.Server{ - Addr: cfg.Profile, + Addr: cfg.Pprof.Profile, Handler: pprofMux, ReadHeaderTimeout: cfg.HTTPHeaderTimeout, } @@ -245,8 +245,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, } // Write cpu profile if requested. - if cfg.CPUProfile != "" { - f, err := os.Create(cfg.CPUProfile) + if cfg.Pprof.CPUProfile != "" { + f, err := os.Create(cfg.Pprof.CPUProfile) if err != nil { return mkErr("unable to create CPU profile: %v", err) } diff --git a/sample-lnd.conf b/sample-lnd.conf index 50347fc70..b863e60bf 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -266,28 +266,32 @@ ; Example: ; debuglevel=debug,PEER=info -; Write CPU profile to the specified file. +; DEPRECATED: Use pprof.cpuprofile instead. Write CPU profile to the specified +; file. ; cpuprofile= -; Enable HTTP profiling on given port -- NOTE port must be between 1024 and -; 65536. The profile can be access at: http://localhost:/debug/pprof/. -; You can also provide it as host:port to enable profiling for remote debugging. -; For example 0.0.0.0: to enable profiling for all interfaces on the given -; port. +; DEPRECATED: Use pprof.profile instead.Enable HTTP profiling on given port +; -- NOTE port must be between 1024 and 65536. The profile can be access at: +; http://localhost:/debug/pprof/. You can also provide it as host:port to +; enable profiling for remote debugging. For example 0.0.0.0: to enable +; profiling for all interfaces on the given port. ; profile= -; Enable a blocking profile to be obtained from the profiling port. A blocking -; profile can show where goroutines are blocking (stuck on mutexes, I/O, etc). -; This takes a value from 0 to 1, with 0 turning off the setting, and 1 sampling -; every blocking event (it's a rate value). +; DEPRECATED: Use pprof.blockingprofile instead. Enable a blocking profile to be +; obtained from the profiling port. A blocking profile can show where goroutines +; are blocking (stuck on mutexes, I/O, etc). This takes a value from 0 to 1, +; with 0 turning off the setting, and 1 sampling every blocking event (it's a +; rate value). ; blockingprofile=0 -; Enable a mutex profile to be obtained from the profiling port. A mutex -; profile can show where goroutines are blocked on mutexes, and which mutexes -; have high contention. This takes a value from 0 to 1, with 0 turning off the -; setting, and 1 sampling every mutex event (it's a rate value). +; DEPRECATED: Use pprof.mutexprofile instead. Enable a mutex profile to be +; obtained from the profiling port. A mutex profile can show where goroutines +; are blocked on mutexes, and which mutexes have high contention. This takes a +; value from 0 to 1, with 0 turning off the setting, and 1 sampling every mutex +; event (it's a rate value). ; mutexprofile=0 + ; DEPRECATED: Allows the rpcserver to intentionally disconnect from peers with ; open channels. THIS FLAG WILL BE REMOVED IN 0.10.0. ; unsafe-disconnect=false @@ -1814,3 +1818,35 @@ ; no active gRPC streams. This might be useful to keep the underlying HTTP/2 ; connection open for future requests. ; grpc.client-allow-ping-without-stream=false + + +[pprof] + +; Enable HTTP profiling on given port -- NOTE port must be between 1024 and +; 65536. The profile can be access at: http://localhost:/debug/pprof/. +; You can also provide it as host:port to enable profiling for remote debugging. +; For example 0.0.0.0: to enable profiling for all interfaces on the given +; port. The built-in profiler has minimal overhead, so it is recommended to +; enable it. +; pprof.profile= + +; Write CPU profile to the specified file. This should only be used for +; debugging because compared to running a pprof server this will record the cpu +; profile constantly from the start of the program until the shutdown. +; pprof.cpuprofile= + +; Enable a blocking profile to be obtained from the profiling port. A blocking +; profile can show where goroutines are blocking (stuck on mutexes, I/O, etc). +; This takes a value from 0 to 1, with 0 turning off the setting, and 1 sampling +; every blocking event (it's a rate value). The blocking profile has high +; overhead and is off by default even when running the pprof server. It should +; only be used for debugging. +; pprof.blockingprofile=0 + +; Enable a mutex profile to be obtained from the profiling port. A mutex +; profile can show where goroutines are blocked on mutexes, and which mutexes +; have high contention. This takes a value from 0 to 1, with 0 turning off the +; setting, and 1 sampling every mutex event (it's a rate value). The mutex +; profile has high overhead and is off by default even when running the pprof +; server. It should only be used for debugging. +; pprof.mutexprofile=0