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  }