github.com/matrixorigin/matrixone@v1.2.0/cmd/mo-service/debug.go (about) 1 // Copyright 2021 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "flag" 19 "fmt" 20 "hash/fnv" 21 "net/http" 22 _ "net/http/pprof" 23 "os" 24 "path" 25 "runtime" 26 "runtime/pprof" 27 "sort" 28 "strings" 29 "unsafe" 30 31 "github.com/felixge/fgprof" 32 "github.com/matrixorigin/matrixone/pkg/logutil" 33 "github.com/matrixorigin/matrixone/pkg/perfcounter" 34 "github.com/matrixorigin/matrixone/pkg/util/status" 35 ) 36 37 var ( 38 cpuProfilePathFlag = flag.String("cpu-profile", "", "write cpu profile to the specified file") 39 allocsProfilePathFlag = flag.String("allocs-profile", "", "write allocs profile to the specified file") 40 heapProfilePathFlag = flag.String("heap-profile", "", "write heap profile to the specified file") 41 httpListenAddr = flag.String("debug-http", "", "http server listen address") 42 statusServer = status.NewServer() 43 ) 44 45 func startCPUProfile() func() { 46 cpuProfilePath := *cpuProfilePathFlag 47 if cpuProfilePath == "" { 48 cpuProfilePath = "cpu-profile" 49 } 50 f, err := os.Create(cpuProfilePath) 51 if err != nil { 52 panic(err) 53 } 54 err = pprof.StartCPUProfile(f) 55 if err != nil { 56 panic(err) 57 } 58 logutil.Infof("CPU profiling enabled, writing to %s", cpuProfilePath) 59 return func() { 60 pprof.StopCPUProfile() 61 f.Close() 62 } 63 } 64 65 func writeAllocsProfile() { 66 profile := pprof.Lookup("allocs") 67 if profile == nil { 68 return 69 } 70 profilePath := *allocsProfilePathFlag 71 if profilePath == "" { 72 profilePath = "allocs-profile" 73 } 74 f, err := os.Create(profilePath) 75 if err != nil { 76 panic(err) 77 } 78 defer f.Close() 79 if err := profile.WriteTo(f, 0); err != nil { 80 panic(err) 81 } 82 logutil.Infof("Allocs profile written to %s", profilePath) 83 } 84 85 func writeHeapProfile() { 86 profile := pprof.Lookup("heap") 87 if profile == nil { 88 return 89 } 90 profilePath := *heapProfilePathFlag 91 if profilePath == "" { 92 profilePath = "heap-profile" 93 } 94 f, err := os.Create(profilePath) 95 if err != nil { 96 panic(err) 97 } 98 defer f.Close() 99 if err := profile.WriteTo(f, 0); err != nil { 100 panic(err) 101 } 102 logutil.Infof("Heap profile written to %s", profilePath) 103 } 104 105 func init() { 106 107 const cssStyles = ` 108 <style> 109 * { 110 margin: 0; 111 padding: 0; 112 font-size: 12px; 113 } 114 html { 115 padding: 10px; 116 } 117 .grey { 118 background-color: #EEE; 119 } 120 .bold { 121 font-weight: bold; 122 } 123 .underline { 124 text-decoration: underline; 125 } 126 .pad { 127 padding: 10px; 128 } 129 </style> 130 ` 131 132 // stacktrace 133 http.HandleFunc("/debug/stack/", func(w http.ResponseWriter, _ *http.Request) { 134 135 fmt.Fprint(w, cssStyles) 136 137 var records []runtime.StackRecord 138 l := 1024 139 for { 140 records = make([]runtime.StackRecord, l) 141 n, ok := runtime.GoroutineProfile(records) 142 if !ok { 143 l *= 2 144 continue 145 } 146 records = records[:n] 147 break 148 } 149 150 hashSums := make(map[uint64]bool) 151 var allInfos [][]positionInfo 152 153 for _, record := range records { 154 infos, sum := getStackInfo(record.Stack()) 155 if _, ok := hashSums[sum]; ok { 156 continue 157 } 158 hashSums[sum] = true 159 allInfos = append(allInfos, infos) 160 } 161 162 sort.Slice(allInfos, func(i, j int) bool { 163 aInfos := allInfos[i] 164 bInfos := allInfos[j] 165 aNumMOFrame := 0 166 for _, info := range aInfos { 167 if strings.Contains(info.PackagePath, "matrixone") || 168 strings.Contains(info.PackagePath, "main") { 169 aNumMOFrame = 1 170 } 171 } 172 bNumMOFrame := 0 173 for _, info := range bInfos { 174 if strings.Contains(info.PackagePath, "matrixone") || 175 strings.Contains(info.PackagePath, "main") { 176 bNumMOFrame = 1 177 } 178 } 179 if aNumMOFrame != bNumMOFrame { 180 return aNumMOFrame > bNumMOFrame 181 } 182 a := aInfos[len(aInfos)-1] 183 b := bInfos[len(bInfos)-1] 184 if a.FileOnDisk != b.FileOnDisk { 185 return a.FileOnDisk < b.FileOnDisk 186 } 187 return a.Line < b.Line 188 }) 189 190 for i, infos := range allInfos { 191 if i%2 == 0 { 192 fmt.Fprintf(w, `<div class="pad">`) 193 } else { 194 fmt.Fprintf(w, `<div class="pad grey">`) 195 } 196 for _, info := range infos { 197 fmt.Fprintf(w, `<p class="bold underline">%s %s:%d %s</p>`, info.PackagePath, path.Base(info.FileOnDisk), info.Line, info.FunctionName) 198 if strings.Contains(info.PackagePath, "matrixone") || 199 strings.Contains(info.PackagePath, "main") { 200 for _, line := range info.Content { 201 fmt.Fprintf(w, `<div class=""><pre>%s</pre></div>`, line) 202 } 203 } 204 } 205 fmt.Fprintf(w, "</div>") 206 } 207 208 }) 209 210 // heap 211 http.HandleFunc("/debug/heap/", func(w http.ResponseWriter, _ *http.Request) { 212 runtime.GC() 213 214 fmt.Fprint(w, cssStyles) 215 216 size := 1024 217 records := make([]runtime.MemProfileRecord, size) 218 for { 219 n, ok := runtime.MemProfile(records, false) 220 if !ok { 221 size *= 2 222 records = make([]runtime.MemProfileRecord, size) 223 continue 224 } 225 records = records[:n] 226 break 227 } 228 229 type position [3]uintptr 230 liveBytes := make(map[uint64]int64) 231 var positions []position 232 for _, record := range records { 233 pos := *(*position)(record.Stack0[:unsafe.Sizeof(position{})]) 234 _, sum := getStackInfo(pos[:]) 235 if _, ok := liveBytes[sum]; !ok { 236 positions = append(positions, pos) 237 } 238 liveBytes[sum] += record.AllocBytes - record.FreeBytes 239 } 240 sort.Slice(positions, func(i, j int) bool { 241 _, sum1 := getStackInfo(positions[i][:]) 242 _, sum2 := getStackInfo(positions[j][:]) 243 return liveBytes[sum1] > liveBytes[sum2] 244 }) 245 246 for i, pos := range positions { 247 posInfos, sum := getStackInfo(pos[:]) 248 if i%2 == 0 { 249 fmt.Fprintf(w, `<div class="pad">`) 250 } else { 251 fmt.Fprintf(w, `<div class="pad grey">`) 252 } 253 fmt.Fprintf(w, `<p>%v</p>`, formatSize(liveBytes[sum])) 254 for _, info := range posInfos { 255 fmt.Fprintf(w, `<p class="bold underline">%s %s:%d %s</p>`, info.PackagePath, path.Base(info.FileOnDisk), info.Line, info.FunctionName) 256 if strings.Contains(info.PackagePath, "matrixone") || 257 strings.Contains(info.PackagePath, "main") { 258 for _, line := range info.Content { 259 fmt.Fprintf(w, `<div class=""><pre>%s</pre></div>`, line) 260 } 261 } 262 } 263 fmt.Fprintf(w, "</div>") 264 } 265 266 }) 267 268 // global performance counter 269 v, ok := perfcounter.Named.Load(perfcounter.NameForGlobal) 270 if ok { 271 http.Handle("/debug/perfcounter/", v.(*perfcounter.CounterSet)) 272 } 273 274 // fgprof 275 http.Handle("/debug/fgprof/", fgprof.Handler()) 276 277 // status server 278 http.Handle("/debug/status/", statusServer) 279 280 } 281 282 type positionInfo struct { 283 basicPositionInfo 284 Content []string 285 } 286 287 type basicPositionInfo struct { 288 FileOnDisk string 289 Line int 290 FullFunctionName string 291 FunctionName string 292 PackageAndFunctionName string 293 PackageName string 294 PackagePath string 295 } 296 297 func getPositionInfo(frame runtime.Frame) (info positionInfo) { 298 packageParentPath, packageAndFuncName := path.Split(frame.Function) 299 packageName, funcName, ok := strings.Cut(packageAndFuncName, ".") 300 if !ok { 301 panic("impossible") 302 } 303 packagePath := path.Join(packageParentPath, packageName) 304 info = positionInfo{ 305 basicPositionInfo: basicPositionInfo{ 306 FileOnDisk: frame.File, 307 Line: frame.Line, 308 FullFunctionName: frame.Function, 309 PackageAndFunctionName: packageAndFuncName, 310 FunctionName: funcName, 311 PackageName: packageName, 312 PackagePath: packagePath, 313 }, 314 Content: getFileContent(frame.File, frame.Line), 315 } 316 return info 317 } 318 319 func getFileContent(file string, line int) (ret []string) { 320 content, err := os.ReadFile(file) 321 if err != nil { 322 panic(err) 323 } 324 lines := strings.Split(string(content), "\n") 325 n := line - 1 326 if n > 0 { 327 ret = append(ret, lines[n-1]) 328 } 329 ret = append(ret, lines[n]) 330 if n+1 < len(lines) { 331 ret = append(ret, lines[n+1]) 332 } 333 return 334 } 335 336 func getStackInfo(stack []uintptr) ([]positionInfo, uint64) { 337 frames := runtime.CallersFrames(stack) 338 339 h := fnv.New64() 340 var infos []positionInfo 341 for { 342 frame, more := frames.Next() 343 info := getPositionInfo(frame) 344 infos = append(infos, info) 345 fmt.Fprintf(h, "%+v", info.basicPositionInfo) 346 if !more { 347 break 348 } 349 } 350 351 hashSum := h.Sum64() 352 return infos, hashSum 353 } 354 355 var units = []string{"B", "K", "M", "G", "T", "P", "E", "Z", "Y"} 356 357 func formatSize(n int64) string { 358 if n == 0 { 359 return "0" 360 } 361 return strings.TrimSpace(_formatBytes(n, 0)) 362 } 363 364 func _formatBytes(n int64, unitIndex int) string { 365 if n == 0 { 366 return "" 367 } 368 var str string 369 next := n / 1024 370 rem := n - next*1024 371 if rem > 0 { 372 str = fmt.Sprintf(" %d%s", rem, units[unitIndex]) 373 } 374 return _formatBytes(next, unitIndex+1) + str 375 }