@@ -3,14 +3,17 @@ package main
33import (
44 "errors"
55 "fmt"
6+ "net/http"
67 "os"
78 "os/signal"
89 "runtime/debug"
10+ "runtime/pprof"
911 "strconv"
1012 "strings"
1113 "syscall"
1214 "time"
1315
16+ "github.com/gin-gonic/gin"
1417 "github.com/multiversx/mx-chain-core-go/core"
1518 "github.com/multiversx/mx-chain-core-go/core/check"
1619 "github.com/multiversx/mx-chain-core-go/core/closing"
@@ -86,6 +89,7 @@ func main() {
8689 skipConfigsDownload ,
8790 fetchConfigsAndClose ,
8891 pathWhereToSaveLogs ,
92+ enableProfiling ,
8993 }
9094
9195 app .Authors = []cli.Author {
@@ -177,6 +181,23 @@ func startChainSimulator(ctx *cli.Context) error {
177181 return err
178182 }
179183
184+ // CPU profiling setup - only if enable-profiling flag is set
185+ var profileFile * os.File
186+ profilingEnabled := ctx .GlobalBool (enableProfiling .Name )
187+ if profilingEnabled {
188+ pathLogsSave := ctx .GlobalString (pathWhereToSaveLogs .Name )
189+ profileFile , err = startCPUProfiling (pathLogsSave , startTimeUnix )
190+ if err != nil {
191+ return fmt .Errorf ("%w while starting CPU profiling" , err )
192+ }
193+
194+ // Ensure pprof is stopped and file is synced/closed even on early exits
195+ defer func () {
196+ log .Info ("stopping CPU profile (defer)" )
197+ stopCPUProfiling (profileFile )
198+ }()
199+ }
200+
180201 var alterConfigsError error
181202 argsChainSimulator := chainSimulator.ArgsChainSimulator {
182203 BypassTxSignatureCheck : bypassTxsSignature ,
@@ -277,7 +298,28 @@ func startChainSimulator(ctx *cli.Context) error {
277298 return err
278299 }
279300
280- err = endpointsProc .ExtendProxyServer (proxyInstance .GetHttpServer ())
301+ // Create a channel for programmatic shutdown
302+ shutdownChan := make (chan struct {})
303+
304+ // Add a shutdown endpoint before extending the proxy server
305+ httpServer := proxyInstance .GetHttpServer ()
306+ ginEngine , ok := httpServer .Handler .(* gin.Engine )
307+ if ! ok {
308+ return fmt .Errorf ("cannot cast httpServer.Handler to gin.Engine" )
309+ }
310+
311+ ginEngine .POST ("/simulator/shutdown" , func (c * gin.Context ) {
312+ log .Info ("shutdown requested via HTTP endpoint" )
313+ c .JSON (http .StatusOK , gin.H {"message" : "shutdown initiated" })
314+
315+ // Trigger shutdown in a goroutine to allow the response to be sent
316+ go func () {
317+ time .Sleep (100 * time .Millisecond )
318+ close (shutdownChan )
319+ }()
320+ })
321+
322+ err = endpointsProc .ExtendProxyServer (httpServer )
281323 if err != nil {
282324 return err
283325 }
@@ -289,9 +331,19 @@ func startChainSimulator(ctx *cli.Context) error {
289331
290332 interrupt := make (chan os.Signal , 1 )
291333 signal .Notify (interrupt , syscall .SIGINT , syscall .SIGTERM )
292- <- interrupt
293334
294- log .Info ("close" )
335+ // Wait for either signal or programmatic shutdown
336+ select {
337+ case sig := <- interrupt :
338+ log .Info ("close" , "signal" , sig )
339+ case <- shutdownChan :
340+ log .Info ("close" , "trigger" , "HTTP shutdown endpoint" )
341+ }
342+
343+ // Stop CPU profiling FIRST and flush to disk (only if profiling is enabled)
344+ if profilingEnabled {
345+ stopCPUProfiling (profileFile )
346+ }
295347
296348 generator .Close ()
297349
@@ -306,6 +358,43 @@ func startChainSimulator(ctx *cli.Context) error {
306358 return nil
307359}
308360
361+ func startCPUProfiling (pathLogsSave string , startTimeUnix int64 ) (* os.File , error ) {
362+ timestampMilisecond := time .Unix (startTimeUnix , 0 ).UnixNano () / 1000000
363+ cpuProfilePath := fmt .Sprintf ("%s/cpu-%d.pprof" , pathLogsSave , timestampMilisecond )
364+
365+ profileFile , err := os .Create (cpuProfilePath )
366+ if err != nil {
367+ return nil , fmt .Errorf ("could not create CPU profile: %w" , err )
368+ }
369+
370+ if err := pprof .StartCPUProfile (profileFile ); err != nil {
371+ _ = profileFile .Close ()
372+ return nil , fmt .Errorf ("could not start CPU profile: %w" , err )
373+ }
374+
375+ log .Info ("CPU profiling started" , "path" , cpuProfilePath )
376+ return profileFile , nil
377+ }
378+
379+ func stopCPUProfiling (profileFile * os.File ) {
380+ if profileFile == nil {
381+ return
382+ }
383+
384+ log .Info ("stopping CPU profile" )
385+ pprof .StopCPUProfile ()
386+
387+ if err := profileFile .Sync (); err != nil {
388+ log .Error ("error syncing CPU profile file" , "err" , err )
389+ }
390+
391+ if err := profileFile .Close (); err != nil {
392+ log .Error ("error closing CPU profile file" , "err" , err )
393+ } else {
394+ log .Info ("CPU profile file closed successfully" )
395+ }
396+ }
397+
309398func initializeLogger (ctx * cli.Context , cfg config.Config ) (closing.Closer , error ) {
310399 logLevelFlagValue := ctx .GlobalString (logLevel .Name )
311400 err := logger .SetLogLevel (logLevelFlagValue )
0 commit comments