github.com/matrixorigin/matrixone@v0.7.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/matrixorigin/matrixone/pkg/logutil" 32 ) 33 34 var ( 35 cpuProfilePathFlag = flag.String("cpu-profile", "", "write cpu profile to the specified file") 36 allocsProfilePathFlag = flag.String("allocs-profile", "", "write allocs profile to the specified file") 37 httpListenAddr = flag.String("debug-http", "", "http server listen address") 38 ) 39 40 func startCPUProfile() func() { 41 cpuProfilePath := *cpuProfilePathFlag 42 if cpuProfilePath == "" { 43 cpuProfilePath = "cpu-profile" 44 } 45 f, err := os.Create(cpuProfilePath) 46 if err != nil { 47 panic(err) 48 } 49 err = pprof.StartCPUProfile(f) 50 if err != nil { 51 panic(err) 52 } 53 logutil.Infof("CPU profiling enabled, writing to %s", cpuProfilePath) 54 return func() { 55 pprof.StopCPUProfile() 56 f.Close() 57 } 58 } 59 60 func writeAllocsProfile() { 61 profile := pprof.Lookup("allocs") 62 if profile == nil { 63 return 64 } 65 profilePath := *allocsProfilePathFlag 66 if profilePath == "" { 67 profilePath = "allocs-profile" 68 } 69 f, err := os.Create(profilePath) 70 if err != nil { 71 panic(err) 72 } 73 defer f.Close() 74 if err := profile.WriteTo(f, 0); err != nil { 75 panic(err) 76 } 77 logutil.Infof("Allocs profile written to %s", profilePath) 78 } 79 80 func init() { 81 82 const cssStyles = ` 83 <style> 84 * { 85 margin: 0; 86 padding: 0; 87 font-size: 12px; 88 } 89 html { 90 padding: 10px; 91 } 92 .grey { 93 background-color: #EEE; 94 } 95 .bold { 96 font-weight: bold; 97 } 98 .underline { 99 text-decoration: underline; 100 } 101 .pad { 102 padding: 10px; 103 } 104 </style> 105 ` 106 107 // stacktrace 108 http.HandleFunc("/debug/stack/", func(w http.ResponseWriter, _ *http.Request) { 109 110 fmt.Fprint(w, cssStyles) 111 112 var records []runtime.StackRecord 113 l := 1024 114 for { 115 records = make([]runtime.StackRecord, l) 116 n, ok := runtime.GoroutineProfile(records) 117 if !ok { 118 l *= 2 119 continue 120 } 121 records = records[:n] 122 break 123 } 124 125 hashSums := make(map[uint64]bool) 126 var allInfos [][]positionInfo 127 128 for _, record := range records { 129 infos, sum := getStackInfo(record.Stack()) 130 if _, ok := hashSums[sum]; ok { 131 continue 132 } 133 hashSums[sum] = true 134 allInfos = append(allInfos, infos) 135 } 136 137 sort.Slice(allInfos, func(i, j int) bool { 138 aInfos := allInfos[i] 139 bInfos := allInfos[j] 140 aNumMOFrame := 0 141 for _, info := range aInfos { 142 if strings.Contains(info.PackagePath, "matrixone") || 143 strings.Contains(info.PackagePath, "main") { 144 aNumMOFrame = 1 145 } 146 } 147 bNumMOFrame := 0 148 for _, info := range bInfos { 149 if strings.Contains(info.PackagePath, "matrixone") || 150 strings.Contains(info.PackagePath, "main") { 151 bNumMOFrame = 1 152 } 153 } 154 if aNumMOFrame != bNumMOFrame { 155 return aNumMOFrame > bNumMOFrame 156 } 157 a := aInfos[len(aInfos)-1] 158 b := bInfos[len(bInfos)-1] 159 if a.FileOnDisk != b.FileOnDisk { 160 return a.FileOnDisk < b.FileOnDisk 161 } 162 return a.Line < b.Line 163 }) 164 165 for i, infos := range allInfos { 166 if i%2 == 0 { 167 fmt.Fprintf(w, `<div class="pad">`) 168 } else { 169 fmt.Fprintf(w, `<div class="pad grey">`) 170 } 171 for _, info := range infos { 172 fmt.Fprintf(w, `<p class="bold underline">%s %s:%d %s</p>`, info.PackagePath, path.Base(info.FileOnDisk), info.Line, info.FunctionName) 173 if strings.Contains(info.PackagePath, "matrixone") || 174 strings.Contains(info.PackagePath, "main") { 175 for _, line := range info.Content { 176 fmt.Fprintf(w, `<div class=""><pre>%s</pre></div>`, line) 177 } 178 } 179 } 180 fmt.Fprintf(w, "</div>") 181 } 182 183 }) 184 185 // heap 186 http.HandleFunc("/debug/heap/", func(w http.ResponseWriter, _ *http.Request) { 187 runtime.GC() 188 189 fmt.Fprint(w, cssStyles) 190 191 size := 1024 192 records := make([]runtime.MemProfileRecord, size) 193 for { 194 n, ok := runtime.MemProfile(records, false) 195 if !ok { 196 size *= 2 197 records = make([]runtime.MemProfileRecord, size) 198 continue 199 } 200 records = records[:n] 201 break 202 } 203 204 type position [3]uintptr 205 liveBytes := make(map[uint64]int64) 206 var positions []position 207 for _, record := range records { 208 pos := *(*position)(record.Stack0[:unsafe.Sizeof(position{})]) 209 _, sum := getStackInfo(pos[:]) 210 if _, ok := liveBytes[sum]; !ok { 211 positions = append(positions, pos) 212 } 213 liveBytes[sum] += record.AllocBytes - record.FreeBytes 214 } 215 sort.Slice(positions, func(i, j int) bool { 216 _, sum1 := getStackInfo(positions[i][:]) 217 _, sum2 := getStackInfo(positions[j][:]) 218 return liveBytes[sum1] > liveBytes[sum2] 219 }) 220 221 for i, pos := range positions { 222 posInfos, sum := getStackInfo(pos[:]) 223 if i%2 == 0 { 224 fmt.Fprintf(w, `<div class="pad">`) 225 } else { 226 fmt.Fprintf(w, `<div class="pad grey">`) 227 } 228 fmt.Fprintf(w, `<p>%v</p>`, formatSize(liveBytes[sum])) 229 for _, info := range posInfos { 230 fmt.Fprintf(w, `<p class="bold underline">%s %s:%d %s</p>`, info.PackagePath, path.Base(info.FileOnDisk), info.Line, info.FunctionName) 231 if strings.Contains(info.PackagePath, "matrixone") || 232 strings.Contains(info.PackagePath, "main") { 233 for _, line := range info.Content { 234 fmt.Fprintf(w, `<div class=""><pre>%s</pre></div>`, line) 235 } 236 } 237 } 238 fmt.Fprintf(w, "</div>") 239 } 240 241 }) 242 } 243 244 type positionInfo struct { 245 basicPositionInfo 246 Content []string 247 } 248 249 type basicPositionInfo struct { 250 FileOnDisk string 251 Line int 252 FullFunctionName string 253 FunctionName string 254 PackageAndFunctionName string 255 PackageName string 256 PackagePath string 257 } 258 259 func getPositionInfo(frame runtime.Frame) (info positionInfo) { 260 packageParentPath, packageAndFuncName := path.Split(frame.Function) 261 packageName, funcName, ok := strings.Cut(packageAndFuncName, ".") 262 if !ok { 263 panic("impossible") 264 } 265 packagePath := path.Join(packageParentPath, packageName) 266 info = positionInfo{ 267 basicPositionInfo: basicPositionInfo{ 268 FileOnDisk: frame.File, 269 Line: frame.Line, 270 FullFunctionName: frame.Function, 271 PackageAndFunctionName: packageAndFuncName, 272 FunctionName: funcName, 273 PackageName: packageName, 274 PackagePath: packagePath, 275 }, 276 Content: getFileContent(frame.File, frame.Line), 277 } 278 return info 279 } 280 281 func getFileContent(file string, line int) (ret []string) { 282 content, err := os.ReadFile(file) 283 if err != nil { 284 panic(err) 285 } 286 lines := strings.Split(string(content), "\n") 287 n := line - 1 288 if n > 0 { 289 ret = append(ret, lines[n-1]) 290 } 291 ret = append(ret, lines[n]) 292 if n+1 < len(lines) { 293 ret = append(ret, lines[n+1]) 294 } 295 return 296 } 297 298 func getStackInfo(stack []uintptr) ([]positionInfo, uint64) { 299 frames := runtime.CallersFrames(stack) 300 301 h := fnv.New64() 302 var infos []positionInfo 303 for { 304 frame, more := frames.Next() 305 info := getPositionInfo(frame) 306 infos = append(infos, info) 307 fmt.Fprintf(h, "%+v", info.basicPositionInfo) 308 if !more { 309 break 310 } 311 } 312 313 hashSum := h.Sum64() 314 return infos, hashSum 315 } 316 317 var units = []string{"B", "K", "M", "G", "T", "P", "E", "Z", "Y"} 318 319 func formatSize(n int64) string { 320 if n == 0 { 321 return "0" 322 } 323 return strings.TrimSpace(_formatBytes(n, 0)) 324 } 325 326 func _formatBytes(n int64, unitIndex int) string { 327 if n == 0 { 328 return "" 329 } 330 var str string 331 next := n / 1024 332 rem := n - next*1024 333 if rem > 0 { 334 str = fmt.Sprintf(" %d%s", rem, units[unitIndex]) 335 } 336 return _formatBytes(next, unitIndex+1) + str 337 }