gitlab.com/SiaPrime/SiaPrime@v1.4.1/profile/profile.go (about)

     1  package profile
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"runtime/pprof"
    10  	"runtime/trace"
    11  	"sync"
    12  	"time"
    13  
    14  	"gitlab.com/SiaPrime/SiaPrime/persist"
    15  )
    16  
    17  // There's a global lock on cpu and memory profiling, because I'm not sure what
    18  // happens if multiple threads call each at the same time. This lock might be
    19  // unnecessary.
    20  var (
    21  	cpuActive   bool
    22  	cpuLock     sync.Mutex
    23  	memActive   bool
    24  	memLock     sync.Mutex
    25  	traceActive bool
    26  	traceLock   sync.Mutex
    27  )
    28  
    29  // StartCPUProfile starts cpu profiling. An error will be returned if a cpu
    30  // profiler is already running.
    31  func StartCPUProfile(profileDir, identifier string) error {
    32  	// Lock the cpu profile lock so that only one profiler is running at a
    33  	// time.
    34  	cpuLock.Lock()
    35  	if cpuActive {
    36  		cpuLock.Unlock()
    37  		return errors.New("cannot start cpu profiler, a profiler is already running")
    38  	}
    39  	cpuActive = true
    40  	cpuLock.Unlock()
    41  
    42  	// Start profiling into the profile dir, using the identifer. The timestamp
    43  	// of the start time of the profiling will be included in the filename.
    44  	cpuProfileFile, err := os.Create(filepath.Join(profileDir, "cpu-profile-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".prof"))
    45  	if err != nil {
    46  		return err
    47  	}
    48  	pprof.StartCPUProfile(cpuProfileFile)
    49  	return nil
    50  }
    51  
    52  // StopCPUProfile stops cpu profiling.
    53  func StopCPUProfile() {
    54  	cpuLock.Lock()
    55  	if cpuActive {
    56  		pprof.StopCPUProfile()
    57  		cpuActive = false
    58  	}
    59  	cpuLock.Unlock()
    60  }
    61  
    62  // SaveMemProfile saves the current memory structure of the program. An error
    63  // will be returned if memory profiling is already in progress. Unlike for cpu
    64  // profiling, there is no 'stopMemProfile' call - everything happens at once.
    65  func SaveMemProfile(profileDir, identifier string) error {
    66  	memLock.Lock()
    67  	if memActive {
    68  		memLock.Unlock()
    69  		return errors.New("cannot start memory profiler, a memory profiler is already running")
    70  	}
    71  	memActive = true
    72  	memLock.Unlock()
    73  
    74  	// Save the memory profile.
    75  	memFile, err := os.Create(filepath.Join(profileDir, "mem-profile-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".prof"))
    76  	if err != nil {
    77  		return err
    78  	}
    79  	pprof.WriteHeapProfile(memFile)
    80  
    81  	memLock.Lock()
    82  	memActive = false
    83  	memLock.Unlock()
    84  	return nil
    85  }
    86  
    87  // StartTrace starts trace. An error will be returned if a trace
    88  // is already running.
    89  func StartTrace(traceDir, identifier string) error {
    90  	// Lock the trace lock so that only one profiler is running at a
    91  	// time.
    92  	traceLock.Lock()
    93  	if traceActive {
    94  		traceLock.Unlock()
    95  		return errors.New("cannot start trace, it is already running")
    96  	}
    97  	traceActive = true
    98  	traceLock.Unlock()
    99  
   100  	// Start trace into the trace dir, using the identifer. The timestamp
   101  	// of the start time of the trace will be included in the filename.
   102  	traceFile, err := os.Create(filepath.Join(traceDir, "trace-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".trace"))
   103  	if err != nil {
   104  		return err
   105  	}
   106  	return trace.Start(traceFile)
   107  }
   108  
   109  // StopTrace stops trace.
   110  func StopTrace() {
   111  	traceLock.Lock()
   112  	if traceActive {
   113  		trace.Stop()
   114  		traceActive = false
   115  	}
   116  	traceLock.Unlock()
   117  }
   118  
   119  // startContinuousLog creates dir and saves inexpensive logs periodically.
   120  // It also runs the restart function periodically.
   121  func startContinuousLog(dir string, sleepCap time.Duration, restart func()) {
   122  	// Create the folder for all of the profiling results.
   123  	err := os.MkdirAll(dir, 0700)
   124  	if err != nil {
   125  		fmt.Println(err)
   126  		return
   127  	}
   128  	// Continuously log statistics about the running Sia application.
   129  	go func() {
   130  		// Create the logger.
   131  		log, err := persist.NewFileLogger(filepath.Join(dir, "continuousStats.log"))
   132  		if err != nil {
   133  			fmt.Println("Stats logging failed:", err)
   134  			return
   135  		}
   136  		// Collect statistics in an infinite loop.
   137  		sleepTime := time.Second * 10
   138  		for {
   139  			// Sleep for an exponential amount of time each iteration, this
   140  			// keeps the size of the log small while still providing lots of
   141  			// information.
   142  			restart()
   143  			time.Sleep(sleepTime)
   144  			sleepTime = time.Duration(1.2 * float64(sleepTime))
   145  			if sleepCap != 0*time.Second && sleepTime > sleepCap {
   146  				sleepTime = sleepCap
   147  			}
   148  			var m runtime.MemStats
   149  			runtime.ReadMemStats(&m)
   150  			log.Printf("\n\tGoroutines: %v\n\tAlloc: %v\n\tTotalAlloc: %v\n\tHeapAlloc: %v\n\tHeapSys: %v\n", runtime.NumGoroutine(), m.Alloc, m.TotalAlloc, m.HeapAlloc, m.HeapSys)
   151  		}
   152  	}()
   153  }
   154  
   155  // StartContinuousProfile will continuously print statistics about the cpu
   156  // usage, memory usage, and runtime stats of the program, and run an execution
   157  // logger. Select one (recommended) or more functionalities by passing the
   158  // corresponding flag(s)
   159  func StartContinuousProfile(profileDir string, profileCPU bool, profileMem bool, profileTrace bool) {
   160  	sleepCap := 0 * time.Second // Unlimited.
   161  	if profileTrace {
   162  		sleepCap = 10 * time.Minute
   163  	}
   164  	startContinuousLog(profileDir, sleepCap, func() {
   165  		if profileCPU {
   166  			StopCPUProfile()
   167  			StartCPUProfile(profileDir, "continuousProfileCPU")
   168  		}
   169  		if profileMem {
   170  			SaveMemProfile(profileDir, "continuousProfileMem")
   171  		}
   172  		if profileTrace {
   173  			StopTrace()
   174  			StartTrace(profileDir, "continuousProfileTrace")
   175  		}
   176  	})
   177  }