From 6461d6f0f814eb0b20f52c5cb5af43a9f20654c9 Mon Sep 17 00:00:00 2001 From: radu Date: Fri, 28 Nov 2025 19:35:39 +0200 Subject: [PATCH 1/3] testing with ram --- cmd/chainsimulator/main.go | 79 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/cmd/chainsimulator/main.go b/cmd/chainsimulator/main.go index bc9da76..35e2951 100644 --- a/cmd/chainsimulator/main.go +++ b/cmd/chainsimulator/main.go @@ -3,14 +3,17 @@ package main import ( "errors" "fmt" + "net/http" "os" "os/signal" "runtime/debug" + "runtime/pprof" "strconv" "strings" "syscall" "time" + "github.com/gin-gonic/gin" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/closing" @@ -177,6 +180,35 @@ func startChainSimulator(ctx *cli.Context) error { return err } + pathLogsSave := ctx.GlobalString(pathWhereToSaveLogs.Name) + timestampMilisecond := time.Unix(startTimeUnix, 0).UnixNano() / 1000000 + cpuprofile1 := fmt.Sprintf("%s/cpu-%d.pprof", pathLogsSave, timestampMilisecond) + f, err := os.Create(cpuprofile1) + if err != nil { + fmt.Fprintf(os.Stderr, "could not create CPU profile: %v\n", err) + panic(err) + } + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "could not start CPU profile: %v\n", err) + panic(err) + } + + log.Info("starting CPU profile") + + // Start a background goroutine to periodically sync the pprof file + // This ensures we have data even if the process is killed abruptly + // Ensure pprof is stopped and file is synced/closed even on early exits + defer func() { + log.Info("stopping CPU profile (defer)") + pprof.StopCPUProfile() + if err := f.Sync(); err != nil { + log.Error("error syncing CPU profile file (defer)", "err", err) + } + if err := f.Close(); err != nil { + log.Error("error closing CPU profile file (defer)", "err", err) + } + }() + var alterConfigsError error argsChainSimulator := chainSimulator.ArgsChainSimulator{ BypassTxSignatureCheck: bypassTxsSignature, @@ -270,7 +302,28 @@ func startChainSimulator(ctx *cli.Context) error { return err } - err = endpointsProc.ExtendProxyServer(proxyInstance.GetHttpServer()) + // Create a channel for programmatic shutdown + shutdownChan := make(chan struct{}) + + // Add a shutdown endpoint before extending the proxy server + httpServer := proxyInstance.GetHttpServer() + ginEngine, ok := httpServer.Handler.(*gin.Engine) + if !ok { + return fmt.Errorf("cannot cast httpServer.Handler to gin.Engine") + } + + ginEngine.POST("/simulator/shutdown", func(c *gin.Context) { + log.Info("shutdown requested via HTTP endpoint") + c.JSON(http.StatusOK, gin.H{"message": "shutdown initiated"}) + + // Trigger shutdown in a goroutine to allow the response to be sent + go func() { + time.Sleep(100 * time.Millisecond) + close(shutdownChan) + }() + }) + + err = endpointsProc.ExtendProxyServer(httpServer) if err != nil { return err } @@ -282,9 +335,29 @@ func startChainSimulator(ctx *cli.Context) error { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) - <-interrupt - log.Info("close") + // Wait for either signal or programmatic shutdown + select { + case sig := <-interrupt: + log.Info("close", "signal", sig) + case <-shutdownChan: + log.Info("close", "trigger", "HTTP shutdown endpoint") + } + + // Stop CPU profiling FIRST and flush to disk + log.Info("stopping CPU profile") + pprof.StopCPUProfile() + + // CRITICAL: Sync forces buffered data to be written to disk + if err := f.Sync(); err != nil { + log.Error("error syncing CPU profile file", "err", err) + } + + if err := f.Close(); err != nil { + log.Error("error closing CPU profile file", "err", err) + } else { + log.Info("CPU profile file closed successfully") + } generator.Close() From 22aab80ff6c277addafd4a187f3e228ff94c7024 Mon Sep 17 00:00:00 2001 From: radu Date: Tue, 2 Dec 2025 15:30:25 +0200 Subject: [PATCH 2/3] cpu profile added --- cmd/chainsimulator/flags.go | 4 ++ cmd/chainsimulator/main.go | 96 +++++++++++++++++++++---------------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/cmd/chainsimulator/flags.go b/cmd/chainsimulator/flags.go index 3b955b5..e059cdd 100644 --- a/cmd/chainsimulator/flags.go +++ b/cmd/chainsimulator/flags.go @@ -147,6 +147,10 @@ var ( Name: "fetch-configs-and-close", Usage: "This flag is used to specify to fetch all configs and close the chain simulator after", } + profileMode = cli.BoolFlag{ + Name: "profile-mode", + Usage: "Boolean option for enabling CPU profiling. If set, CPU profile will be saved to a file.", + } ) func applyFlags(ctx *cli.Context, cfg *config.Config) { diff --git a/cmd/chainsimulator/main.go b/cmd/chainsimulator/main.go index 35e2951..f17595c 100644 --- a/cmd/chainsimulator/main.go +++ b/cmd/chainsimulator/main.go @@ -89,6 +89,7 @@ func main() { skipConfigsDownload, fetchConfigsAndClose, pathWhereToSaveLogs, + profileMode, } app.Authors = []cli.Author{ @@ -180,34 +181,22 @@ func startChainSimulator(ctx *cli.Context) error { return err } - pathLogsSave := ctx.GlobalString(pathWhereToSaveLogs.Name) - timestampMilisecond := time.Unix(startTimeUnix, 0).UnixNano() / 1000000 - cpuprofile1 := fmt.Sprintf("%s/cpu-%d.pprof", pathLogsSave, timestampMilisecond) - f, err := os.Create(cpuprofile1) - if err != nil { - fmt.Fprintf(os.Stderr, "could not create CPU profile: %v\n", err) - panic(err) - } - if err := pprof.StartCPUProfile(f); err != nil { - fmt.Fprintf(os.Stderr, "could not start CPU profile: %v\n", err) - panic(err) - } - - log.Info("starting CPU profile") - - // Start a background goroutine to periodically sync the pprof file - // This ensures we have data even if the process is killed abruptly - // Ensure pprof is stopped and file is synced/closed even on early exits - defer func() { - log.Info("stopping CPU profile (defer)") - pprof.StopCPUProfile() - if err := f.Sync(); err != nil { - log.Error("error syncing CPU profile file (defer)", "err", err) - } - if err := f.Close(); err != nil { - log.Error("error closing CPU profile file (defer)", "err", err) + // CPU profiling setup - only if profile-mode flag is set + var profileFile *os.File + enableProfiling := ctx.GlobalBool(profileMode.Name) + if enableProfiling { + pathLogsSave := ctx.GlobalString(pathWhereToSaveLogs.Name) + profileFile, err = startCPUProfiling(pathLogsSave, startTimeUnix) + if err != nil { + return fmt.Errorf("%w while starting CPU profiling", err) } - }() + + // Ensure pprof is stopped and file is synced/closed even on early exits + defer func() { + log.Info("stopping CPU profile (defer)") + stopCPUProfiling(profileFile) + }() + } var alterConfigsError error argsChainSimulator := chainSimulator.ArgsChainSimulator{ @@ -344,19 +333,9 @@ func startChainSimulator(ctx *cli.Context) error { log.Info("close", "trigger", "HTTP shutdown endpoint") } - // Stop CPU profiling FIRST and flush to disk - log.Info("stopping CPU profile") - pprof.StopCPUProfile() - - // CRITICAL: Sync forces buffered data to be written to disk - if err := f.Sync(); err != nil { - log.Error("error syncing CPU profile file", "err", err) - } - - if err := f.Close(); err != nil { - log.Error("error closing CPU profile file", "err", err) - } else { - log.Info("CPU profile file closed successfully") + // Stop CPU profiling FIRST and flush to disk (only if profiling is enabled) + if enableProfiling { + stopCPUProfiling(profileFile) } generator.Close() @@ -372,6 +351,43 @@ func startChainSimulator(ctx *cli.Context) error { return nil } +func startCPUProfiling(pathLogsSave string, startTimeUnix int64) (*os.File, error) { + timestampMilisecond := time.Unix(startTimeUnix, 0).UnixNano() / 1000000 + cpuProfilePath := fmt.Sprintf("%s/cpu-%d.pprof", pathLogsSave, timestampMilisecond) + + profileFile, err := os.Create(cpuProfilePath) + if err != nil { + return nil, fmt.Errorf("could not create CPU profile: %w", err) + } + + if err := pprof.StartCPUProfile(profileFile); err != nil { + _ = profileFile.Close() + return nil, fmt.Errorf("could not start CPU profile: %w", err) + } + + log.Info("CPU profiling started", "path", cpuProfilePath) + return profileFile, nil +} + +func stopCPUProfiling(profileFile *os.File) { + if profileFile == nil { + return + } + + log.Info("stopping CPU profile") + pprof.StopCPUProfile() + + if err := profileFile.Sync(); err != nil { + log.Error("error syncing CPU profile file", "err", err) + } + + if err := profileFile.Close(); err != nil { + log.Error("error closing CPU profile file", "err", err) + } else { + log.Info("CPU profile file closed successfully") + } +} + func initializeLogger(ctx *cli.Context, cfg config.Config) (closing.Closer, error) { logLevelFlagValue := ctx.GlobalString(logLevel.Name) err := logger.SetLogLevel(logLevelFlagValue) From f66e0e5eabfb24cc883026bd91cfbfce2c92cc2c Mon Sep 17 00:00:00 2001 From: radu Date: Tue, 2 Dec 2025 16:09:08 +0200 Subject: [PATCH 3/3] updated name to enable-profiling --- cmd/chainsimulator/flags.go | 4 ++-- cmd/chainsimulator/main.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/chainsimulator/flags.go b/cmd/chainsimulator/flags.go index e059cdd..e3efd18 100644 --- a/cmd/chainsimulator/flags.go +++ b/cmd/chainsimulator/flags.go @@ -147,8 +147,8 @@ var ( Name: "fetch-configs-and-close", Usage: "This flag is used to specify to fetch all configs and close the chain simulator after", } - profileMode = cli.BoolFlag{ - Name: "profile-mode", + enableProfiling = cli.BoolFlag{ + Name: "enable-profiling", Usage: "Boolean option for enabling CPU profiling. If set, CPU profile will be saved to a file.", } ) diff --git a/cmd/chainsimulator/main.go b/cmd/chainsimulator/main.go index f17595c..9bdcf66 100644 --- a/cmd/chainsimulator/main.go +++ b/cmd/chainsimulator/main.go @@ -89,7 +89,7 @@ func main() { skipConfigsDownload, fetchConfigsAndClose, pathWhereToSaveLogs, - profileMode, + enableProfiling, } app.Authors = []cli.Author{ @@ -181,10 +181,10 @@ func startChainSimulator(ctx *cli.Context) error { return err } - // CPU profiling setup - only if profile-mode flag is set + // CPU profiling setup - only if enable-profiling flag is set var profileFile *os.File - enableProfiling := ctx.GlobalBool(profileMode.Name) - if enableProfiling { + profilingEnabled := ctx.GlobalBool(enableProfiling.Name) + if profilingEnabled { pathLogsSave := ctx.GlobalString(pathWhereToSaveLogs.Name) profileFile, err = startCPUProfiling(pathLogsSave, startTimeUnix) if err != nil { @@ -334,7 +334,7 @@ func startChainSimulator(ctx *cli.Context) error { } // Stop CPU profiling FIRST and flush to disk (only if profiling is enabled) - if enableProfiling { + if profilingEnabled { stopCPUProfiling(profileFile) }