diff --git a/config.go b/config.go index 513c217e69..02d1f569e7 100644 --- a/config.go +++ b/config.go @@ -352,12 +352,7 @@ 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"` - - 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 +816,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 +826,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 +852,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 +1331,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 +1428,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 @@ -1714,6 +1673,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 0000000000..d8bca887be --- /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 0000000000..cebb492cc4 --- /dev/null +++ b/lncfg/pprof.go @@ -0,0 +1,67 @@ +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 b69383a893..c0858e83e9 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) } @@ -876,6 +876,7 @@ func startGrpcListen(cfg *Config, grpcServer *grpc.Server, // If Prometheus monitoring is enabled, start the Prometheus exporter. if cfg.Prometheus.Enabled() { + err := monitoring.ExportPrometheusMetrics( grpcServer, cfg.Prometheus, ) diff --git a/sample-lnd.conf b/sample-lnd.conf index df6dcf615c..7183f3a3b5 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -266,27 +266,6 @@ ; Example: ; debuglevel=debug,PEER=info -; 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. -; 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). -; 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). -; mutexprofile=0 ; DEPRECATED: Allows the rpcserver to intentionally disconnect from peers with ; open channels. THIS FLAG WILL BE REMOVED IN 0.10.0. @@ -1811,3 +1790,28 @@ ; 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. +; pprof.profile= + +; Write CPU profile to the specified file. +; 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). +; 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). +; pprof.mutexprofile=0 \ No newline at end of file