github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/profile/profile.go (about) 1 package profile 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "runtime" 8 "runtime/pprof" 9 "runtime/trace" 10 "sync/atomic" 11 "time" 12 13 "github.com/bingoohuang/gg/pkg/ss" 14 ) 15 16 const ( 17 cpuMode = 1 << iota 18 heapMode 19 allocsMode 20 mutexMode 21 blockMode 22 traceMode 23 threadCreateMode 24 goroutineMode 25 ) 26 27 // Profile represents an active profiling session. 28 type Profile struct { 29 // mode holds the type of profiling that will be made 30 mode int 31 32 // memProfileRate holds the rate for the memory profile. 33 memProfileRate int 34 35 // closer holds a cleanup function that run after each profile 36 closer []func() 37 38 // stopped records if a call to profile.Stop has been made 39 stopped uint32 40 41 // duration set the profile to stop after the specified duration. 42 duration time.Duration 43 } 44 45 // Specs specifies the profile settings by specifies expression. 46 // like cpu,heap,allocs,mutex,block,trace,threadcreate,goroutine,d:5m,rate:4096. 47 func Specs(specs string) func(*Profile) { 48 return func(p *Profile) { 49 for _, spec := range ss.Split(specs, ss.WithIgnoreEmpty(true), ss.WithCase(ss.CaseLower), ss.WithSeps(", ")) { 50 switch spec { 51 case "cpu": 52 p.mode |= cpuMode 53 case "heap": 54 p.mode |= heapMode 55 case "allocs": 56 p.mode |= allocsMode 57 case "mutex": 58 p.mode |= mutexMode 59 case "block": 60 p.mode |= blockMode 61 case "trace": 62 p.mode |= traceMode 63 case "threadcreate": 64 p.mode |= threadCreateMode 65 case "goroutine": 66 p.mode |= goroutineMode 67 default: 68 switch { 69 case ss.HasPrefix(spec, "d:"): 70 if d, err := time.ParseDuration(spec[2:]); err == nil { 71 p.duration = d 72 continue 73 } 74 case ss.HasPrefix(spec, "rate:"): 75 if r, err := ss.ParseIntE(spec[5:]); err == nil { 76 p.memProfileRate = r 77 continue 78 } 79 } 80 log.Printf("W! unknown spec: %s", spec) 81 } 82 } 83 } 84 } 85 86 // DefaultMemProfileRate is the default memory profiling rate. 87 // See also http://golang.org/pkg/runtime/#pkg-variables 88 const DefaultMemProfileRate = 4096 89 90 // Stop stops the profile and flushes any unwritten data. 91 func (p *Profile) Stop() { 92 if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) { 93 // someone has already called close 94 return 95 } 96 for _, closer := range p.closer { 97 closer() 98 } 99 atomic.StoreUint32(&started, 0) 100 } 101 102 // started is non zero if a profile is running. 103 var started uint32 104 105 // Start starts a new profiling session. 106 // The caller should call the Stop method on the value returned 107 // to cleanly stop profiling. 108 func Start(options ...func(*Profile)) (interface{ Stop() }, error) { 109 if !atomic.CompareAndSwapUint32(&started, 0, 1) { 110 log.Fatal("profile: Start() already called") 111 } 112 113 prof := Profile{memProfileRate: DefaultMemProfileRate, duration: 15 * time.Second} 114 for _, option := range options { 115 option(&prof) 116 } 117 118 if prof.mode&cpuMode > 0 { 119 f, fcloser, err := createFile("cpu.pprof") 120 if err != nil { 121 return nil, err 122 } 123 pprof.StartCPUProfile(f) 124 prof.closer = append(prof.closer, func() { 125 pprof.StopCPUProfile() 126 fcloser() 127 }) 128 } 129 if prof.mode&allocsMode > 0 { 130 f, fcloser, err := createFile("allocs.pprof") 131 if err != nil { 132 return nil, err 133 } 134 old := runtime.MemProfileRate 135 runtime.MemProfileRate = prof.memProfileRate 136 prof.closer = append(prof.closer, func() { 137 pprof.Lookup("allocs").WriteTo(f, 0) 138 runtime.MemProfileRate = old 139 fcloser() 140 }) 141 } 142 if prof.mode&heapMode > 0 { 143 f, fcloser, err := createFile("heap.pprof") 144 if err != nil { 145 return nil, err 146 } 147 old := runtime.MemProfileRate 148 runtime.MemProfileRate = prof.memProfileRate 149 prof.closer = append(prof.closer, func() { 150 pprof.Lookup("heap").WriteTo(f, 0) 151 runtime.MemProfileRate = old 152 fcloser() 153 }) 154 } 155 if prof.mode&mutexMode > 0 { 156 f, fcloser, err := createFile("mutex.pprof") 157 if err != nil { 158 return nil, err 159 } 160 runtime.SetMutexProfileFraction(1) 161 prof.closer = append(prof.closer, func() { 162 if mp := pprof.Lookup("mutex"); mp != nil { 163 mp.WriteTo(f, 0) 164 } 165 runtime.SetMutexProfileFraction(0) 166 fcloser() 167 }) 168 } 169 if prof.mode&blockMode > 0 { 170 f, fcloser, err := createFile("block.pprof") 171 if err != nil { 172 return nil, err 173 } 174 runtime.SetBlockProfileRate(1) 175 prof.closer = append(prof.closer, func() { 176 pprof.Lookup("block").WriteTo(f, 0) 177 runtime.SetBlockProfileRate(0) 178 fcloser() 179 }) 180 } 181 if prof.mode&threadCreateMode > 0 { 182 f, fcloser, err := createFile("threadcreation.pprof") 183 if err != nil { 184 return nil, err 185 } 186 prof.closer = append(prof.closer, func() { 187 if mp := pprof.Lookup("threadcreate"); mp != nil { 188 mp.WriteTo(f, 0) 189 } 190 fcloser() 191 }) 192 } 193 if prof.mode&traceMode > 0 { 194 f, fcloser, err := createFile("trace.out") 195 if err != nil { 196 return nil, err 197 } 198 trace.Start(f) 199 prof.closer = append(prof.closer, func() { 200 trace.Stop() 201 fcloser() 202 }) 203 } 204 if prof.mode&goroutineMode > 0 { 205 f, fcloser, err := createFile("goroutine.pprof") 206 if err != nil { 207 return nil, err 208 } 209 prof.closer = append(prof.closer, func() { 210 if mp := pprof.Lookup("goroutine"); mp != nil { 211 mp.WriteTo(f, 0) 212 } 213 fcloser() 214 }) 215 } 216 217 if len(prof.closer) == 0 { 218 return nil, fmt.Errorf("no profiles") 219 } 220 221 if prof.duration > 0 { 222 time.Sleep(prof.duration) 223 log.Printf("profile: after %s, stopping profiles", prof.duration) 224 prof.Stop() 225 return nil, nil 226 } 227 228 return &prof, nil 229 } 230 231 func createFile(fn string) (*os.File, func(), error) { 232 f, err := os.Create(fn) 233 if err != nil { 234 log.Printf("E! profile: could not create %q: %v", fn, err) 235 return nil, nil, err 236 } 237 log.Printf("profile: profiling started, %s", fn) 238 239 fcloser := func() { 240 f.Close() 241 log.Printf("profile: profiling ended, %s", fn) 242 } 243 244 return f, fcloser, err 245 }