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  }