github.com/amazechain/amc@v0.1.3/internal/debug/api.go (about) 1 // Copyright 2023 The AmazeChain Authors 2 // This file is part of the AmazeChain library. 3 // 4 // The AmazeChain library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The AmazeChain library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the AmazeChain library. If not, see <http://www.gnu.org/licenses/>. 16 17 package debug 18 19 import ( 20 "bytes" 21 "errors" 22 "github.com/amazechain/amc/log" 23 rpc "github.com/amazechain/amc/modules/rpc/jsonrpc" 24 "github.com/hashicorp/go-bexpr" 25 "io" 26 "os" 27 "os/user" 28 "path/filepath" 29 "regexp" 30 "runtime" 31 "runtime/debug" 32 "runtime/pprof" 33 "strings" 34 "sync" 35 "time" 36 ) 37 38 // Handler is the global debugging handler. 39 var Handler = new(HandlerT) 40 41 // APIs return the collection of RPC services the tracer package offers. 42 func APIs() []rpc.API { 43 // Append all the local APIs and return 44 return []rpc.API{ 45 { 46 Namespace: "debug", 47 Service: Handler, 48 }, 49 } 50 } 51 52 // HandlerT implements the debugging API. 53 // Do not create values of this type, use the one 54 // in the Handler variable instead. 55 type HandlerT struct { 56 mu sync.Mutex 57 cpuW io.WriteCloser 58 cpuFile string 59 traceW io.WriteCloser 60 traceFile string 61 } 62 63 // MemStats returns detailed runtime memory statistics. 64 func (*HandlerT) MemStats() *runtime.MemStats { 65 s := new(runtime.MemStats) 66 runtime.ReadMemStats(s) 67 return s 68 } 69 70 // GcStats returns GC statistics. 71 func (*HandlerT) GcStats() *debug.GCStats { 72 s := new(debug.GCStats) 73 debug.ReadGCStats(s) 74 return s 75 } 76 77 // CpuProfile turns on CPU profiling for nsec seconds and writes 78 // profile data to file. 79 func (h *HandlerT) CpuProfile(file string, nsec uint) error { 80 if err := h.StartCPUProfile(file); err != nil { 81 return err 82 } 83 time.Sleep(time.Duration(nsec) * time.Second) 84 h.StopCPUProfile() 85 return nil 86 } 87 88 // StartCPUProfile turns on CPU profiling, writing to the given file. 89 func (h *HandlerT) StartCPUProfile(file string) error { 90 h.mu.Lock() 91 defer h.mu.Unlock() 92 if h.cpuW != nil { 93 return errors.New("CPU profiling already in progress") 94 } 95 f, err := os.Create(expandHome(file)) 96 if err != nil { 97 return err 98 } 99 if err := pprof.StartCPUProfile(f); err != nil { 100 f.Close() 101 return err 102 } 103 h.cpuW = f 104 h.cpuFile = file 105 log.Info("CPU profiling started", "dump", h.cpuFile) 106 return nil 107 } 108 109 // StopCPUProfile stops an ongoing CPU profile. 110 func (h *HandlerT) StopCPUProfile() error { 111 h.mu.Lock() 112 defer h.mu.Unlock() 113 pprof.StopCPUProfile() 114 if h.cpuW == nil { 115 return errors.New("CPU profiling not in progress") 116 } 117 log.Info("Done writing CPU profile", "dump", h.cpuFile) 118 h.cpuW.Close() 119 h.cpuW = nil 120 h.cpuFile = "" 121 return nil 122 } 123 124 // GoTrace turns on tracing for nsec seconds and writes 125 // trace data to file. 126 func (h *HandlerT) GoTrace(file string, nsec uint) error { 127 if err := h.StartGoTrace(file); err != nil { 128 return err 129 } 130 time.Sleep(time.Duration(nsec) * time.Second) 131 h.StopGoTrace() 132 return nil 133 } 134 135 // BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to 136 // file. It uses a profile rate of 1 for most accurate information. If a different rate is 137 // desired, set the rate and write the profile manually. 138 func (*HandlerT) BlockProfile(file string, nsec uint) error { 139 runtime.SetBlockProfileRate(1) 140 time.Sleep(time.Duration(nsec) * time.Second) 141 defer runtime.SetBlockProfileRate(0) 142 return writeProfile("block", file) 143 } 144 145 // SetBlockProfileRate sets the rate of goroutine block profile data collection. 146 // rate 0 disables block profiling. 147 func (*HandlerT) SetBlockProfileRate(rate int) { 148 runtime.SetBlockProfileRate(rate) 149 } 150 151 // WriteBlockProfile writes a goroutine blocking profile to the given file. 152 func (*HandlerT) WriteBlockProfile(file string) error { 153 return writeProfile("block", file) 154 } 155 156 // MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file. 157 // It uses a profile rate of 1 for most accurate information. If a different rate is 158 // desired, set the rate and write the profile manually. 159 func (*HandlerT) MutexProfile(file string, nsec uint) error { 160 runtime.SetMutexProfileFraction(1) 161 time.Sleep(time.Duration(nsec) * time.Second) 162 defer runtime.SetMutexProfileFraction(0) 163 return writeProfile("mutex", file) 164 } 165 166 // SetMutexProfileFraction sets the rate of mutex profiling. 167 func (*HandlerT) SetMutexProfileFraction(rate int) { 168 runtime.SetMutexProfileFraction(rate) 169 } 170 171 // WriteMutexProfile writes a goroutine blocking profile to the given file. 172 func (*HandlerT) WriteMutexProfile(file string) error { 173 return writeProfile("mutex", file) 174 } 175 176 // WriteMemProfile writes an allocation profile to the given file. 177 // Note that the profiling rate cannot be set through the API, 178 // it must be set on the command line. 179 func (*HandlerT) WriteMemProfile(file string) error { 180 return writeProfile("heap", file) 181 } 182 183 // Stacks returns a printed representation of the stacks of all goroutines. It 184 // also permits the following optional filters to be used: 185 // - filter: boolean expression of packages to filter for 186 func (*HandlerT) Stacks(filter *string) string { 187 buf := new(bytes.Buffer) 188 pprof.Lookup("goroutine").WriteTo(buf, 2) 189 190 // If any filtering was requested, execute them now 191 if filter != nil && len(*filter) > 0 { 192 expanded := *filter 193 194 // The input filter is a logical expression of package names. Transform 195 // it into a proper boolean expression that can be fed into a parser and 196 // interpreter: 197 // 198 // E.g. (eth || snap) && !p2p -> (eth in Value || snap in Value) && p2p not in Value 199 expanded = regexp.MustCompile(`[:/\.A-Za-z0-9_-]+`).ReplaceAllString(expanded, "`$0` in Value") 200 expanded = regexp.MustCompile("!(`[:/\\.A-Za-z0-9_-]+`)").ReplaceAllString(expanded, "$1 not") 201 expanded = strings.ReplaceAll(expanded, "||", "or") 202 expanded = strings.ReplaceAll(expanded, "&&", "and") 203 log.Info("Expanded filter expression", "filter", *filter, "expanded", expanded) 204 205 expr, err := bexpr.CreateEvaluator(expanded) 206 if err != nil { 207 log.Error("Failed to parse filter expression", "expanded", expanded, "err", err) 208 return "" 209 } 210 // Split the goroutine dump into segments and filter each 211 dump := buf.String() 212 buf.Reset() 213 214 for _, trace := range strings.Split(dump, "\n\n") { 215 if ok, _ := expr.Evaluate(map[string]string{"Value": trace}); ok { 216 buf.WriteString(trace) 217 buf.WriteString("\n\n") 218 } 219 } 220 } 221 return buf.String() 222 } 223 224 // FreeOSMemory forces a garbage collection. 225 func (*HandlerT) FreeOSMemory() { 226 debug.FreeOSMemory() 227 } 228 229 // SetGCPercent sets the garbage collection target percentage. It returns the previous 230 // setting. A negative value disables GC. 231 func (*HandlerT) SetGCPercent(v int) int { 232 return debug.SetGCPercent(v) 233 } 234 235 func writeProfile(name, file string) error { 236 p := pprof.Lookup(name) 237 log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) 238 f, err := os.Create(expandHome(file)) 239 if err != nil { 240 return err 241 } 242 defer f.Close() 243 return p.WriteTo(f, 0) 244 } 245 246 // expands home directory in file paths. 247 // ~someuser/tmp will not be expanded. 248 func expandHome(p string) string { 249 if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { 250 home := os.Getenv("HOME") 251 if home == "" { 252 if usr, err := user.Current(); err == nil { 253 home = usr.HomeDir 254 } 255 } 256 if home != "" { 257 p = home + p[1:] 258 } 259 } 260 return filepath.Clean(p) 261 }