Commit 1b5413b8 authored by Artyom Artamonov's avatar Artyom Artamonov Committed by GitHub

op-service/pprof: Support profiling to file (#6739) (#8709)

parent 4250b6e5
......@@ -12,7 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......
......@@ -8,7 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/stretchr/testify/require"
......@@ -30,7 +30,7 @@ func validBatcherConfig() batcher.CLIConfig {
TxMgrConfig: txmgr.NewCLIConfig("fake", txmgr.DefaultBatcherFlagValues),
LogConfig: log.DefaultCLIConfig(),
MetricsConfig: metrics.DefaultCLIConfig(),
PprofConfig: pprof.DefaultCLIConfig(),
PprofConfig: oppprof.DefaultCLIConfig(),
// The compressor config is not checked in config.Check()
RPC: rpc.DefaultCLIConfig(),
}
......
......@@ -5,9 +5,7 @@ import (
"errors"
"fmt"
"io"
"net"
_ "net/http/pprof"
"strconv"
"strings"
"sync/atomic"
"time"
......@@ -24,7 +22,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/httputil"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......@@ -62,12 +60,11 @@ type BatcherService struct {
Version string
pprofSrv *httputil.HTTPServer
pprofService *oppprof.Service
metricsSrv *httputil.HTTPServer
rpcServer *oprpc.Server
balanceMetricer io.Closer
stopped atomic.Bool
NotSubmittingOnStart bool
......@@ -111,7 +108,7 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string,
return fmt.Errorf("failed to start metrics server: %w", err)
}
if err := bs.initPProf(cfg); err != nil {
return fmt.Errorf("failed to start pprof server: %w", err)
return fmt.Errorf("failed to init profiling: %w", err)
}
bs.initDriver()
if err := bs.initRPCServer(cfg); err != nil {
......@@ -216,16 +213,19 @@ func (bs *BatcherService) initTxManager(cfg *CLIConfig) error {
}
func (bs *BatcherService) initPProf(cfg *CLIConfig) error {
if !cfg.PprofConfig.Enabled {
return nil
}
log.Debug("starting pprof server", "addr", net.JoinHostPort(cfg.PprofConfig.ListenAddr, strconv.Itoa(cfg.PprofConfig.ListenPort)))
srv, err := oppprof.StartServer(cfg.PprofConfig.ListenAddr, cfg.PprofConfig.ListenPort)
if err != nil {
return err
bs.pprofService = oppprof.New(
cfg.PprofConfig.ListenEnabled,
cfg.PprofConfig.ListenAddr,
cfg.PprofConfig.ListenPort,
cfg.PprofConfig.ProfileType,
cfg.PprofConfig.ProfileDir,
cfg.PprofConfig.ProfileFilename,
)
if err := bs.pprofService.Start(); err != nil {
return fmt.Errorf("failed to start pprof service: %w", err)
}
bs.pprofSrv = srv
log.Info("started pprof server", "addr", srv.Addr())
return nil
}
......@@ -326,8 +326,8 @@ func (bs *BatcherService) Stop(ctx context.Context) error {
result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err))
}
}
if bs.pprofSrv != nil {
if err := bs.pprofSrv.Stop(ctx); err != nil {
if bs.pprofService != nil {
if err := bs.pprofService.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop PProf server: %w", err))
}
}
......
......@@ -10,7 +10,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......
......@@ -11,7 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......
......@@ -15,7 +15,7 @@ import (
openum "github.com/ethereum-optimism/optimism/op-service/enum"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......
......@@ -24,7 +24,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/httputil"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......@@ -46,7 +46,7 @@ type Service struct {
l1Client *ethclient.Client
pollClient client.RPC
pprofSrv *httputil.HTTPServer
pprofService *oppprof.Service
metricsSrv *httputil.HTTPServer
balanceMetricer io.Closer
......@@ -71,28 +71,28 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error {
if err := s.initTxManager(cfg); err != nil {
return err
return fmt.Errorf("failed to init tx manager: %w", err)
}
if err := s.initL1Client(ctx, cfg); err != nil {
return err
return fmt.Errorf("failed to init l1 client: %w", err)
}
if err := s.initRollupClient(ctx, cfg); err != nil {
return err
return fmt.Errorf("failed to init rollup client: %w", err)
}
if err := s.initPollClient(ctx, cfg); err != nil {
return err
return fmt.Errorf("failed to init poll client: %w", err)
}
if err := s.initPProfServer(&cfg.PprofConfig); err != nil {
return err
if err := s.initPProf(&cfg.PprofConfig); err != nil {
return fmt.Errorf("failed to init profiling: %w", err)
}
if err := s.initMetricsServer(&cfg.MetricsConfig); err != nil {
return err
return fmt.Errorf("failed to init metrics server: %w", err)
}
if err := s.initGameLoader(cfg); err != nil {
return err
return fmt.Errorf("failed to init game loader: %w", err)
}
if err := s.initScheduler(ctx, cfg); err != nil {
return err
return fmt.Errorf("failed to init scheduler: %w", err)
}
s.initMonitor(cfg)
......@@ -129,17 +129,20 @@ func (s *Service) initPollClient(ctx context.Context, cfg *config.Config) error
return nil
}
func (s *Service) initPProfServer(cfg *oppprof.CLIConfig) error {
if !cfg.Enabled {
return nil
}
s.logger.Debug("starting pprof", "addr", cfg.ListenAddr, "port", cfg.ListenPort)
pprofSrv, err := oppprof.StartServer(cfg.ListenAddr, cfg.ListenPort)
if err != nil {
return fmt.Errorf("failed to start pprof server: %w", err)
func (s *Service) initPProf(cfg *oppprof.CLIConfig) error {
s.pprofService = oppprof.New(
cfg.ListenEnabled,
cfg.ListenAddr,
cfg.ListenPort,
cfg.ProfileType,
cfg.ProfileDir,
cfg.ProfileFilename,
)
if err := s.pprofService.Start(); err != nil {
return fmt.Errorf("failed to start pprof service: %w", err)
}
s.pprofSrv = pprofSrv
s.logger.Info("started pprof server", "addr", pprofSrv.Addr())
return nil
}
......@@ -231,8 +234,8 @@ func (s *Service) Stop(ctx context.Context) error {
if s.faultGamesCloser != nil {
s.faultGamesCloser()
}
if s.pprofSrv != nil {
if err := s.pprofSrv.Stop(ctx); err != nil {
if s.pprofService != nil {
if err := s.pprofService.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close pprof server: %w", err))
}
}
......
......@@ -12,7 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)
......
......@@ -9,7 +9,7 @@ import (
opflags "github.com/ethereum-optimism/optimism/op-service/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)
......
......@@ -41,7 +41,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
......
......@@ -6,7 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-heartbeat/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/urfave/cli/v2"
)
......
......@@ -23,7 +23,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/httputil"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
)
const (
......@@ -60,13 +60,14 @@ func Main(version string) func(ctx *cli.Context) error {
}
type HeartbeatService struct {
pprof, metrics, http *httputil.HTTPServer
metrics, http *httputil.HTTPServer
pprofService *oppprof.Service
}
func (hs *HeartbeatService) Stop(ctx context.Context) error {
var result error
if hs.pprof != nil {
result = errors.Join(result, hs.pprof.Stop(ctx))
if hs.pprofService != nil {
result = errors.Join(result, hs.pprofService.Stop(ctx))
}
if hs.metrics != nil {
result = errors.Join(result, hs.metrics.Stop(ctx))
......@@ -93,14 +94,17 @@ func Start(ctx context.Context, l log.Logger, cfg Config, version string) (*Hear
}
pprofCfg := cfg.Pprof
if pprofCfg.Enabled {
l.Debug("starting pprof", "addr", pprofCfg.ListenAddr, "port", pprofCfg.ListenPort)
pprofSrv, err := oppprof.StartServer(pprofCfg.ListenAddr, pprofCfg.ListenPort)
if err != nil {
return nil, errors.Join(fmt.Errorf("failed to start pprof server: %w", err), hs.Stop(ctx))
}
l.Info("started pprof server", "addr", pprofSrv.Addr())
hs.pprof = pprofSrv
hs.pprofService = oppprof.New(
pprofCfg.ListenEnabled,
pprofCfg.ListenAddr,
pprofCfg.ListenPort,
pprofCfg.ProfileType,
pprofCfg.ProfileDir,
pprofCfg.ProfileFilename,
)
if err := hs.pprofService.Start(); err != nil {
return nil, fmt.Errorf("failed to start pprof service: %w", err)
}
metrics := NewMetrics(registry)
......
......@@ -10,6 +10,7 @@ import (
openum "github.com/ethereum-optimism/optimism/op-service/enum"
opflags "github.com/ethereum-optimism/optimism/op-service/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/sources"
)
......@@ -176,23 +177,6 @@ var (
Value: 7300,
EnvVars: prefixEnvVars("METRICS_PORT"),
}
PprofEnabledFlag = &cli.BoolFlag{
Name: "pprof.enabled",
Usage: "Enable the pprof server",
EnvVars: prefixEnvVars("PPROF_ENABLED"),
}
PprofAddrFlag = &cli.StringFlag{
Name: "pprof.addr",
Usage: "pprof listening address",
Value: "0.0.0.0", // TODO(CLI-4159): Switch to 127.0.0.1
EnvVars: prefixEnvVars("PPROF_ADDR"),
}
PprofPortFlag = &cli.IntFlag{
Name: "pprof.port",
Usage: "pprof listening port",
Value: 6060,
EnvVars: prefixEnvVars("PPROF_PORT"),
}
SnapshotLog = &cli.StringFlag{
Name: "snapshotlog.file",
Usage: "Path to the snapshot log file",
......@@ -289,9 +273,6 @@ var optionalFlags = []cli.Flag{
MetricsEnabledFlag,
MetricsAddrFlag,
MetricsPortFlag,
PprofEnabledFlag,
PprofAddrFlag,
PprofPortFlag,
SnapshotLog,
HeartbeatEnabledFlag,
HeartbeatMonikerFlag,
......@@ -317,6 +298,7 @@ func init() {
DeprecatedFlags = append(DeprecatedFlags, deprecatedP2PFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, P2PFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, DeprecatedFlags...)
optionalFlags = append(optionalFlags, opflags.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...)
......
......@@ -12,7 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum/go-ethereum/log"
)
......
......@@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"net"
"strconv"
"sync/atomic"
"time"
......@@ -26,7 +24,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/version"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum-optimism/optimism/op-service/sources"
)
......@@ -55,7 +53,7 @@ type OpNode struct {
rollupHalt string // when to halt the rollup, disabled if empty
pprofSrv *httputil.HTTPServer
pprofService *oppprof.Service
metricsSrv *httputil.HTTPServer
beacon *sources.L1BeaconClient
......@@ -142,7 +140,7 @@ func (n *OpNode) init(ctx context.Context, cfg *Config, snapshotLog log.Logger)
n.metrics.RecordUp()
n.initHeartbeat(cfg)
if err := n.initPProf(cfg); err != nil {
return fmt.Errorf("failed to init pprof server: %w", err)
return fmt.Errorf("failed to init profiling: %w", err)
}
return nil
}
......@@ -393,16 +391,19 @@ func (n *OpNode) initHeartbeat(cfg *Config) {
}
func (n *OpNode) initPProf(cfg *Config) error {
if !cfg.Pprof.Enabled {
return nil
}
log.Debug("starting pprof server", "addr", net.JoinHostPort(cfg.Pprof.ListenAddr, strconv.Itoa(cfg.Pprof.ListenPort)))
srv, err := oppprof.StartServer(cfg.Pprof.ListenAddr, cfg.Pprof.ListenPort)
if err != nil {
return err
n.pprofService = oppprof.New(
cfg.Pprof.ListenEnabled,
cfg.Pprof.ListenAddr,
cfg.Pprof.ListenPort,
cfg.Pprof.ProfileType,
cfg.Pprof.ProfileDir,
cfg.Pprof.ProfileFilename,
)
if err := n.pprofService.Start(); err != nil {
return fmt.Errorf("failed to start pprof service: %w", err)
}
n.pprofSrv = srv
log.Info("started pprof server", "addr", srv.Addr())
return nil
}
......@@ -621,8 +622,8 @@ func (n *OpNode) Stop(ctx context.Context) error {
}
// Close metrics and pprof only after we are done idling
if n.pprofSrv != nil {
if err := n.pprofSrv.Stop(ctx); err != nil {
if n.pprofService != nil {
if err := n.pprofService.Stop(ctx); err != nil {
result = multierror.Append(result, fmt.Errorf("failed to close pprof server: %w", err))
}
}
......
......@@ -9,12 +9,14 @@ import (
"os"
"strings"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/flags"
"github.com/ethereum-optimism/optimism/op-node/node"
p2pcli "github.com/ethereum-optimism/optimism/op-node/p2p/cli"
......@@ -22,8 +24,6 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
opflags "github.com/ethereum-optimism/optimism/op-service/flags"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/sources"
)
// NewConfig creates a Config from the provided flags or environment variables.
......@@ -88,11 +88,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
ListenAddr: ctx.String(flags.MetricsAddrFlag.Name),
ListenPort: ctx.Int(flags.MetricsPortFlag.Name),
},
Pprof: oppprof.CLIConfig{
Enabled: ctx.Bool(flags.PprofEnabledFlag.Name),
ListenAddr: ctx.String(flags.PprofAddrFlag.Name),
ListenPort: ctx.Int(flags.PprofPortFlag.Name),
},
Pprof: oppprof.ReadCLIConfig(ctx),
P2P: p2pConfig,
P2PSigner: p2pSignerSetup,
L1EpochPollInterval: ctx.Duration(flags.L1EpochPollIntervalFlag.Name),
......
......@@ -9,7 +9,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......
......@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-proposer/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
......
......@@ -5,8 +5,6 @@ import (
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
"sync/atomic"
"time"
......@@ -18,7 +16,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/httputil"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
......@@ -61,7 +59,7 @@ type ProposerService struct {
Version string
pprofSrv *httputil.HTTPServer
pprofService *oppprof.Service
metricsSrv *httputil.HTTPServer
rpcServer *oprpc.Server
......@@ -105,7 +103,7 @@ func (ps *ProposerService) initFromCLIConfig(ctx context.Context, version string
return fmt.Errorf("failed to start metrics server: %w", err)
}
if err := ps.initPProf(cfg); err != nil {
return fmt.Errorf("failed to start pprof server: %w", err)
return fmt.Errorf("failed to init profiling: %w", err)
}
if err := ps.initDriver(); err != nil {
return fmt.Errorf("failed to init Driver: %w", err)
......@@ -166,16 +164,19 @@ func (ps *ProposerService) initTxManager(cfg *CLIConfig) error {
}
func (ps *ProposerService) initPProf(cfg *CLIConfig) error {
if !cfg.PprofConfig.Enabled {
return nil
}
log.Debug("starting pprof server", "addr", net.JoinHostPort(cfg.PprofConfig.ListenAddr, strconv.Itoa(cfg.PprofConfig.ListenPort)))
srv, err := oppprof.StartServer(cfg.PprofConfig.ListenAddr, cfg.PprofConfig.ListenPort)
if err != nil {
return err
ps.pprofService = oppprof.New(
cfg.PprofConfig.ListenEnabled,
cfg.PprofConfig.ListenAddr,
cfg.PprofConfig.ListenPort,
cfg.PprofConfig.ProfileType,
cfg.PprofConfig.ProfileDir,
cfg.PprofConfig.ProfileFilename,
)
if err := ps.pprofService.Start(); err != nil {
return fmt.Errorf("failed to start pprof service: %w", err)
}
ps.pprofSrv = srv
log.Info("started pprof server", "addr", srv.Addr())
return nil
}
......@@ -294,8 +295,8 @@ func (ps *ProposerService) Stop(ctx context.Context) error {
result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err))
}
}
if ps.pprofSrv != nil {
if err := ps.pprofSrv.Stop(ctx); err != nil {
if ps.pprofService != nil {
if err := ps.pprofService.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop PProf server: %w", err))
}
}
......
package flags
import (
"os"
"path/filepath"
)
// PathFlag accepts path to some file or directory and splits it
// into dirPath and filename (both can be empty)
type PathFlag struct {
originalPath string
dirPath string
filename string
}
func (f *PathFlag) Set(path string) error {
f.originalPath = path
fileInfo, err := os.Stat(path)
if err != nil && !os.IsNotExist(err) {
return err
}
if fileInfo != nil && fileInfo.IsDir() {
f.dirPath = path
return nil
}
f.dirPath, f.filename = filepath.Split(path)
return nil
}
func (f *PathFlag) String() string {
return f.originalPath
}
func (f *PathFlag) Clone() any {
return &PathFlag{
originalPath: f.originalPath,
dirPath: f.dirPath,
filename: f.filename,
}
}
func (f *PathFlag) Dir() string {
return f.dirPath
}
func (f *PathFlag) Filename() string {
return f.filename
}
package pprof
package oppprof
import (
"errors"
"fmt"
"math"
"strings"
opservice "github.com/ethereum-optimism/optimism/op-service"
openum "github.com/ethereum-optimism/optimism/op-service/enum"
"github.com/ethereum-optimism/optimism/op-service/flags"
"github.com/urfave/cli/v2"
)
......@@ -12,13 +16,45 @@ const (
EnabledFlagName = "pprof.enabled"
ListenAddrFlagName = "pprof.addr"
PortFlagName = "pprof.port"
ProfileTypeFlagName = "pprof.type"
ProfilePathFlagName = "pprof.path"
defaultListenAddr = "0.0.0.0"
defaultListenPort = 6060
)
var allowedProfileTypes = []profileType{"cpu", "heap", "goroutine", "threadcreate", "block", "mutex", "allocs"}
type profileType string
func (t profileType) String() string {
return string(t)
}
func (t *profileType) Set(value string) error {
if !validProfileType(profileType(value)) {
return fmt.Errorf("unknown profile type: %q", value)
}
*t = profileType(value)
return nil
}
func (t *profileType) Clone() any {
cpy := *t
return &cpy
}
func validProfileType(value profileType) bool {
for _, k := range allowedProfileTypes {
if k == value {
return true
}
}
return false
}
func DefaultCLIConfig() CLIConfig {
return CLIConfig{
Enabled: false,
ListenEnabled: false,
ListenAddr: defaultListenAddr,
ListenPort: defaultListenPort,
}
......@@ -43,17 +79,36 @@ func CLIFlags(envPrefix string) []cli.Flag {
Value: defaultListenPort,
EnvVars: opservice.PrefixEnvVar(envPrefix, "PPROF_PORT"),
},
&cli.GenericFlag{
Name: ProfilePathFlagName,
Usage: "pprof file path. If it is a directory, the path is {dir}/{profileType}.prof",
Value: new(flags.PathFlag),
EnvVars: opservice.PrefixEnvVar(envPrefix, "PPROF_PATH"),
},
&cli.GenericFlag{
Name: ProfileTypeFlagName,
Usage: "pprof profile type. One of " + openum.EnumString(allowedProfileTypes),
Value: func() *profileType {
defaultProfType := profileType("")
return &defaultProfType
}(),
EnvVars: opservice.PrefixEnvVar(envPrefix, "PPROF_TYPE"),
},
}
}
type CLIConfig struct {
Enabled bool
ListenEnabled bool
ListenAddr string
ListenPort int
ProfileType profileType
ProfileDir string
ProfileFilename string
}
func (m CLIConfig) Check() error {
if !m.Enabled {
if !m.ListenEnabled {
return nil
}
......@@ -65,9 +120,13 @@ func (m CLIConfig) Check() error {
}
func ReadCLIConfig(ctx *cli.Context) CLIConfig {
profilePathFlag := ctx.Generic(ProfilePathFlagName).(*flags.PathFlag)
return CLIConfig{
Enabled: ctx.Bool(EnabledFlagName),
ListenEnabled: ctx.Bool(EnabledFlagName),
ListenAddr: ctx.String(ListenAddrFlagName),
ListenPort: ctx.Int(PortFlagName),
ProfileType: profileType(strings.ToLower(ctx.String(ProfileTypeFlagName))),
ProfileDir: profilePathFlag.Dir(),
ProfileFilename: profilePathFlag.Filename(),
}
}
package oppprof
import (
"context"
"io"
"net"
"net/http"
httpPprof "net/http/pprof"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strconv"
"github.com/ethereum-optimism/optimism/op-service/httputil"
"github.com/ethereum/go-ethereum/log"
)
type Service struct {
listenEnabled bool
listenAddr string
listenPort int
profileType string
profileDir string
profileFilename string
cpuFile io.Closer
httpServer *httputil.HTTPServer
}
func New(listenEnabled bool, listenAddr string, listenPort int, profType profileType, profileDir, profileFilename string) *Service {
return &Service{
listenEnabled: listenEnabled,
listenAddr: listenAddr,
listenPort: listenPort,
profileType: string(profType),
profileDir: profileDir,
profileFilename: profileFilename,
}
}
func (s *Service) Start() error {
switch s.profileType {
case "cpu":
if err := s.startCPUProfile(); err != nil {
return err
}
case "block":
runtime.SetBlockProfileRate(1)
case "mutex":
runtime.SetMutexProfileFraction(1)
}
if s.listenEnabled {
if err := s.startServer(); err != nil {
return err
}
}
if s.profileType != "" {
log.Info("start profiling to file", "profile_type", s.profileType, "profile_filepath", s.buildTargetFilePath())
}
return nil
}
func (s *Service) Stop(ctx context.Context) error {
switch s.profileType {
case "cpu":
pprof.StopCPUProfile()
if s.cpuFile != nil {
if err := s.cpuFile.Close(); err != nil {
return err
}
}
case "heap":
runtime.GC()
fallthrough
default:
profile := pprof.Lookup(s.profileType)
if profile == nil {
break
}
filepath := s.buildTargetFilePath()
log.Info("saving profile info", "profile_type", s.profileType, "profile_filepath", s.buildTargetFilePath())
f, err := os.Create(filepath)
if err != nil {
return err
}
defer f.Close()
_ = profile.WriteTo(f, 0)
}
if s.httpServer != nil {
if err := s.httpServer.Stop(ctx); err != nil {
return err
}
}
return nil
}
func (s *Service) startServer() error {
log.Debug("starting pprof server", "addr", net.JoinHostPort(s.listenAddr, strconv.Itoa(s.listenPort)))
mux := http.NewServeMux()
// have to do below to support multiple servers, since the
// pprof import only uses DefaultServeMux
mux.Handle("/debug/pprof/", http.HandlerFunc(httpPprof.Index))
mux.Handle("/debug/pprof/profile", http.HandlerFunc(httpPprof.Profile))
mux.Handle("/debug/pprof/symbol", http.HandlerFunc(httpPprof.Symbol))
mux.Handle("/debug/pprof/trace", http.HandlerFunc(httpPprof.Trace))
addr := net.JoinHostPort(s.listenAddr, strconv.Itoa(s.listenPort))
var err error
s.httpServer, err = httputil.StartHTTPServer(addr, mux)
if err != nil {
return err
}
log.Info("started pprof server", "addr", s.httpServer.Addr())
return nil
}
func (s *Service) startCPUProfile() error {
f, err := os.Create(s.buildTargetFilePath())
if err != nil {
return err
}
err = pprof.StartCPUProfile(f)
s.cpuFile = f
return err
}
func (s *Service) buildTargetFilePath() string {
filename := s.profileType + ".prof"
if s.profileFilename != "" {
filename = s.profileFilename
}
return filepath.Join(s.profileDir, filename)
}
package pprof
import (
"net"
"net/http"
"net/http/pprof"
"strconv"
"github.com/ethereum-optimism/optimism/op-service/httputil"
)
func StartServer(hostname string, port int) (*httputil.HTTPServer, error) {
mux := http.NewServeMux()
// have to do below to support multiple servers, since the
// pprof import only uses DefaultServeMux
mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
addr := net.JoinHostPort(hostname, strconv.Itoa(port))
return httputil.StartHTTPServer(addr, mux)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment