github.com/jfrog/jfrog-cli-core/v2@v2.51.0/utils/coreutils/profiler.go (about)

     1  package coreutils
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"runtime/pprof"
     8  	"time"
     9  
    10  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    11  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    12  )
    13  
    14  const (
    15  	// The default interval between 2 profiling actions
    16  	defaultInterval = time.Second
    17  	// The default number of profilings
    18  	defaultRepetitions = 3
    19  )
    20  
    21  // This struct wraps the "pprof" profiler in Go.
    22  // It is used for thread dumping.
    23  type Profiler struct {
    24  	interval    time.Duration
    25  	repetitions uint
    26  }
    27  
    28  type ProfilerOption func(*Profiler)
    29  
    30  func NewProfiler(opts ...ProfilerOption) *Profiler {
    31  	profiler := &Profiler{
    32  		interval:    defaultInterval,
    33  		repetitions: defaultRepetitions,
    34  	}
    35  	for _, opt := range opts {
    36  		opt(profiler)
    37  	}
    38  	return profiler
    39  }
    40  
    41  func WithInterval(interval time.Duration) ProfilerOption {
    42  	return func(p *Profiler) {
    43  		p.interval = interval
    44  	}
    45  }
    46  
    47  func WithRepetitions(repetitions uint) ProfilerOption {
    48  	return func(p *Profiler) {
    49  		p.repetitions = repetitions
    50  	}
    51  }
    52  
    53  func (p *Profiler) ThreadDump() (output string, err error) {
    54  	var outputFilePath string
    55  	if outputFilePath, err = p.threadDumpToFile(); err != nil {
    56  		return
    57  	}
    58  	defer func() {
    59  		err = errors.Join(err, errorutils.CheckError(os.Remove(outputFilePath)))
    60  	}()
    61  	return p.convertFileToString(outputFilePath)
    62  }
    63  
    64  func (p *Profiler) threadDumpToFile() (outputFilePath string, err error) {
    65  	outputFile, err := fileutils.CreateTempFile()
    66  	if err != nil {
    67  		return
    68  	}
    69  	defer func() {
    70  		err = errors.Join(err, errorutils.CheckError(outputFile.Close()))
    71  	}()
    72  
    73  	for i := 0; i < int(p.repetitions); i++ {
    74  		fmt.Fprintf(outputFile, "========== Thread dump #%d ==========\n", i)
    75  		prof := pprof.Lookup("goroutine")
    76  		if err = errorutils.CheckError(prof.WriteTo(outputFile, 1)); err != nil {
    77  			return
    78  		}
    79  		time.Sleep(p.interval)
    80  	}
    81  	return outputFile.Name(), nil
    82  }
    83  
    84  func (p *Profiler) convertFileToString(outputFilePath string) (string, error) {
    85  	if outputBytes, err := os.ReadFile(outputFilePath); err != nil {
    86  		return "", errorutils.CheckError(err)
    87  	} else {
    88  		return string(outputBytes), nil
    89  	}
    90  }