github.com/MetalBlockchain/metalgo@v1.11.9/utils/profiler/continuous.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package profiler 5 6 import ( 7 "fmt" 8 "time" 9 10 "golang.org/x/sync/errgroup" 11 12 "github.com/MetalBlockchain/metalgo/utils/filesystem" 13 ) 14 15 // Config that is used to describe the options of the continuous profiler. 16 type Config struct { 17 Dir string `json:"dir"` 18 Enabled bool `json:"enabled"` 19 Freq time.Duration `json:"freq"` 20 MaxNumFiles int `json:"maxNumFiles"` 21 } 22 23 // ContinuousProfiler periodically captures CPU, memory, and lock profiles 24 type ContinuousProfiler interface { 25 Dispatch() error 26 Shutdown() 27 } 28 29 type continuousProfiler struct { 30 profiler *profiler 31 freq time.Duration 32 maxNumFiles int 33 34 // Dispatch returns when closer is closed 35 closer chan struct{} 36 } 37 38 func NewContinuous(dir string, freq time.Duration, maxNumFiles int) ContinuousProfiler { 39 return &continuousProfiler{ 40 profiler: newProfiler(dir), 41 freq: freq, 42 maxNumFiles: maxNumFiles, 43 closer: make(chan struct{}), 44 } 45 } 46 47 func (p *continuousProfiler) Dispatch() error { 48 t := time.NewTicker(p.freq) 49 defer t.Stop() 50 51 for { 52 if err := p.start(); err != nil { 53 return err 54 } 55 56 select { 57 case <-p.closer: 58 return p.stop() 59 case <-t.C: 60 if err := p.stop(); err != nil { 61 return err 62 } 63 } 64 65 if err := p.rotate(); err != nil { 66 return err 67 } 68 } 69 } 70 71 func (p *continuousProfiler) start() error { 72 return p.profiler.StartCPUProfiler() 73 } 74 75 func (p *continuousProfiler) stop() error { 76 g := errgroup.Group{} 77 g.Go(p.profiler.StopCPUProfiler) 78 g.Go(p.profiler.MemoryProfile) 79 g.Go(p.profiler.LockProfile) 80 return g.Wait() 81 } 82 83 func (p *continuousProfiler) rotate() error { 84 g := errgroup.Group{} 85 g.Go(func() error { 86 return rotate(p.profiler.cpuProfileName, p.maxNumFiles) 87 }) 88 g.Go(func() error { 89 return rotate(p.profiler.memProfileName, p.maxNumFiles) 90 }) 91 g.Go(func() error { 92 return rotate(p.profiler.lockProfileName, p.maxNumFiles) 93 }) 94 return g.Wait() 95 } 96 97 func (p *continuousProfiler) Shutdown() { 98 close(p.closer) 99 } 100 101 // Renames the file at [name] to [name].1, the file at [name].1 to [name].2, etc. 102 // Assumes that there is a file at [name]. 103 func rotate(name string, maxNumFiles int) error { 104 for i := maxNumFiles - 1; i > 0; i-- { 105 sourceFilename := fmt.Sprintf("%s.%d", name, i) 106 destFilename := fmt.Sprintf("%s.%d", name, i+1) 107 if _, err := filesystem.RenameIfExists(sourceFilename, destFilename); err != nil { 108 return err 109 } 110 } 111 destFilename := name + ".1" 112 _, err := filesystem.RenameIfExists(name, destFilename) 113 return err 114 }