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  }