github.com/NebulousLabs/Sia@v1.3.7/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 "github.com/NebulousLabs/Sia/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 }