github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/profile/flamegraph.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package profile
    15  
    16  import (
    17  	"fmt"
    18  	"math"
    19  	"sort"
    20  
    21  	"github.com/google/pprof/profile"
    22  	"github.com/whtcorpsinc/milevadb/types"
    23  	"github.com/whtcorpsinc/milevadb/soliton/texttree"
    24  )
    25  
    26  type flamegraphNode struct {
    27  	cumValue int64
    28  	children map[uint64]*flamegraphNode
    29  	name     string
    30  }
    31  
    32  func newFlamegraphNode() *flamegraphNode {
    33  	return &flamegraphNode{
    34  		cumValue: 0,
    35  		children: make(map[uint64]*flamegraphNode),
    36  		name:     "",
    37  	}
    38  }
    39  
    40  // add the value from a sample into the flamegraph PosetDag.
    41  // This method should only be called on the root node.
    42  func (n *flamegraphNode) add(sample *profile.Sample) {
    43  	// FIXME: we take the last sample value by default, but some profiles have multiple samples.
    44  	//  - allocs:    alloc_objects, alloc_space, inuse_objects, inuse_space
    45  	//  - causet:     contentions, delay
    46  	//  - cpu:       samples, cpu
    47  	//  - heap:      alloc_objects, alloc_space, inuse_objects, inuse_space
    48  	//  - mutex:     contentions, delay
    49  
    50  	value := sample.Value[len(sample.Value)-1]
    51  	if value == 0 {
    52  		return
    53  	}
    54  
    55  	locs := sample.Location
    56  
    57  	for {
    58  		n.cumValue += value
    59  		if len(locs) == 0 {
    60  			return
    61  		}
    62  
    63  		// The previous implementation in MilevaDB identify nodes using location ID,
    64  		// but `go tool pprof` identify nodes using function ID. Should we follow?
    65  		loc := locs[len(locs)-1]
    66  		locID := loc.ID
    67  		child, ok := n.children[locID]
    68  		if !ok {
    69  			child = newFlamegraphNode()
    70  			n.children[locID] = child
    71  			if len(loc.Line) > 0 && loc.Line[0].Function != nil {
    72  				child.name = locs[len(locs)-1].Line[0].Function.Name
    73  			}
    74  		}
    75  		locs = locs[:len(locs)-1]
    76  		n = child
    77  	}
    78  }
    79  
    80  // defCauslectFuncUsage defCauslect the value by given function name
    81  func (n *flamegraphNode) defCauslectFuncUsage(name string) int64 {
    82  	if n.name == name {
    83  		return n.cumValue
    84  	}
    85  	if len(n.children) == 0 {
    86  		return 0
    87  	}
    88  	var usage int64 = 0
    89  	for _, child := range n.children {
    90  		usage = child.defCauslectFuncUsage(name) + usage
    91  	}
    92  	return usage
    93  }
    94  
    95  type flamegraphNodeWithLocation struct {
    96  	*flamegraphNode
    97  	locID uint64
    98  }
    99  
   100  // sortedChildren returns a list of children of this node, sorted by each
   101  // child's cumulative value.
   102  func (n *flamegraphNode) sortedChildren() []flamegraphNodeWithLocation {
   103  	children := make([]flamegraphNodeWithLocation, 0, len(n.children))
   104  	for locID, child := range n.children {
   105  		children = append(children, flamegraphNodeWithLocation{
   106  			flamegraphNode: child,
   107  			locID:          locID,
   108  		})
   109  	}
   110  	sort.Slice(children, func(i, j int) bool {
   111  		a, b := children[i], children[j]
   112  		if a.cumValue != b.cumValue {
   113  			return a.cumValue > b.cumValue
   114  		}
   115  		return a.locID < b.locID
   116  	})
   117  
   118  	return children
   119  }
   120  
   121  type flamegraphDefCauslector struct {
   122  	rows      [][]types.Causet
   123  	locations map[uint64]*profile.Location
   124  	total     int64
   125  	rootChild int
   126  }
   127  
   128  func newFlamegraphDefCauslector(p *profile.Profile) *flamegraphDefCauslector {
   129  	locations := make(map[uint64]*profile.Location, len(p.Location))
   130  	for _, loc := range p.Location {
   131  		locations[loc.ID] = loc
   132  	}
   133  	return &flamegraphDefCauslector{locations: locations}
   134  }
   135  
   136  func (c *flamegraphDefCauslector) locationName(locID uint64) (funcName, fileLine string) {
   137  	loc := c.locations[locID]
   138  	if len(loc.Line) == 0 {
   139  		return "<unknown>", "<unknown>"
   140  	}
   141  	line := loc.Line[0]
   142  	funcName = line.Function.Name
   143  	fileLine = fmt.Sprintf("%s:%d", line.Function.Filename, line.Line)
   144  	return
   145  }
   146  
   147  func (c *flamegraphDefCauslector) defCauslectChild(
   148  	node flamegraphNodeWithLocation,
   149  	depth int64,
   150  	indent string,
   151  	parentCumValue int64,
   152  	isLastChild bool,
   153  ) {
   154  	funcName, fileLine := c.locationName(node.locID)
   155  	c.rows = append(c.rows, types.MakeCausets(
   156  		texttree.PrettyIdentifier(funcName, indent, isLastChild),
   157  		percentage(node.cumValue, c.total),
   158  		percentage(node.cumValue, parentCumValue),
   159  		c.rootChild,
   160  		depth,
   161  		fileLine,
   162  	))
   163  
   164  	if len(node.children) == 0 {
   165  		return
   166  	}
   167  
   168  	indent4Child := texttree.Indent4Child(indent, isLastChild)
   169  	children := node.sortedChildren()
   170  	for i, child := range children {
   171  		c.defCauslectChild(child, depth+1, indent4Child, node.cumValue, i == len(children)-1)
   172  	}
   173  }
   174  
   175  func (c *flamegraphDefCauslector) defCauslect(root *flamegraphNode) {
   176  	c.rows = append(c.rows, types.MakeCausets("root", "100%", "100%", 0, 0, "root"))
   177  	if len(root.children) == 0 {
   178  		return
   179  	}
   180  
   181  	c.total = root.cumValue
   182  	indent4Child := texttree.Indent4Child("", false)
   183  	children := root.sortedChildren()
   184  	for i, child := range children {
   185  		c.rootChild = i + 1
   186  		c.defCauslectChild(child, 1, indent4Child, root.cumValue, i == len(children)-1)
   187  	}
   188  }
   189  
   190  func percentage(value, total int64) string {
   191  	var ratio float64
   192  	if total != 0 {
   193  		ratio = math.Abs(float64(value)/float64(total)) * 100
   194  	}
   195  	switch {
   196  	case ratio >= 99.95 && ratio <= 100.05:
   197  		return "100%"
   198  	case ratio >= 1.0:
   199  		return fmt.Sprintf("%.2f%%", ratio)
   200  	default:
   201  		return fmt.Sprintf("%.2g%%", ratio)
   202  	}
   203  }