github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/soliton/profile/profile.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  	"bytes"
    18  	"io"
    19  	"io/ioutil"
    20  	"runtime/pprof"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/google/pprof/profile"
    26  	"github.com/whtcorpsinc/errors"
    27  	"github.com/whtcorpsinc/milevadb/types"
    28  	"github.com/whtcorpsinc/milevadb/soliton/texttree"
    29  )
    30  
    31  // CPUProfileInterval represents the duration of sampling CPU
    32  var CPUProfileInterval = 30 * time.Second
    33  
    34  // DefCauslector is used to defCauslect the profile results
    35  type DefCauslector struct{}
    36  
    37  // ProfileReaderToCausets reads data from reader and returns the flamegraph which is organized by tree form.
    38  func (c *DefCauslector) ProfileReaderToCausets(f io.Reader) ([][]types.Causet, error) {
    39  	p, err := profile.Parse(f)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	return c.profileToCausets(p)
    44  }
    45  
    46  func (c *DefCauslector) profileToFlamegraphNode(p *profile.Profile) (*flamegraphNode, error) {
    47  	err := p.CheckValid()
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	root := newFlamegraphNode()
    53  	for _, sample := range p.Sample {
    54  		root.add(sample)
    55  	}
    56  	return root, nil
    57  }
    58  
    59  func (c *DefCauslector) profileToCausets(p *profile.Profile) ([][]types.Causet, error) {
    60  	root, err := c.profileToFlamegraphNode(p)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	defCaus := newFlamegraphDefCauslector(p)
    65  	defCaus.defCauslect(root)
    66  	return defCaus.rows, nil
    67  }
    68  
    69  // cpuProfileGraph returns the CPU profile flamegraph which is organized by tree form
    70  func (c *DefCauslector) cpuProfileGraph() ([][]types.Causet, error) {
    71  	buffer := &bytes.Buffer{}
    72  	if err := pprof.StartCPUProfile(buffer); err != nil {
    73  		panic(err)
    74  	}
    75  	time.Sleep(CPUProfileInterval)
    76  	pprof.StopCPUProfile()
    77  	return c.ProfileReaderToCausets(buffer)
    78  }
    79  
    80  // ProfileGraph returns the CPU/memory/mutex/allocs/causet profile flamegraph which is organized by tree form
    81  func (c *DefCauslector) ProfileGraph(name string) ([][]types.Causet, error) {
    82  	if strings.ToLower(strings.TrimSpace(name)) == "cpu" {
    83  		return c.cpuProfileGraph()
    84  	}
    85  
    86  	p := pprof.Lookup(name)
    87  	if p == nil {
    88  		return nil, errors.Errorf("cannot retrieve %s profile", name)
    89  	}
    90  	debug := 0
    91  	if name == "goroutine" {
    92  		debug = 2
    93  	}
    94  	buffer := &bytes.Buffer{}
    95  	if err := p.WriteTo(buffer, debug); err != nil {
    96  		return nil, err
    97  	}
    98  	if name == "goroutine" {
    99  		return c.ParseGoroutines(buffer)
   100  	}
   101  	return c.ProfileReaderToCausets(buffer)
   102  }
   103  
   104  // ParseGoroutines returns the groutine list for given string representation
   105  func (c *DefCauslector) ParseGoroutines(reader io.Reader) ([][]types.Causet, error) {
   106  	content, err := ioutil.ReadAll(reader)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	goroutines := strings.Split(string(content), "\n\n")
   111  	var rows [][]types.Causet
   112  	for _, goroutine := range goroutines {
   113  		defCausIndex := strings.Index(goroutine, ":")
   114  		if defCausIndex < 0 {
   115  			return nil, errors.New("goroutine incompatible with current go version")
   116  		}
   117  
   118  		headers := strings.SplitN(strings.TrimSpace(goroutine[len("goroutine")+1:defCausIndex]), " ", 2)
   119  		if len(headers) != 2 {
   120  			return nil, errors.Errorf("incompatible goroutine headers: %s", goroutine[len("goroutine")+1:defCausIndex])
   121  		}
   122  		id, err := strconv.Atoi(strings.TrimSpace(headers[0]))
   123  		if err != nil {
   124  			return nil, errors.Annotatef(err, "invalid goroutine id: %s", headers[0])
   125  		}
   126  		state := strings.Trim(headers[1], "[]")
   127  		stack := strings.Split(strings.TrimSpace(goroutine[defCausIndex+1:]), "\n")
   128  		for i := 0; i < len(stack)/2; i++ {
   129  			fn := stack[i*2]
   130  			loc := stack[i*2+1]
   131  			var identifier string
   132  			if i == 0 {
   133  				identifier = fn
   134  			} else if i == len(stack)/2-1 {
   135  				identifier = string(texttree.TreeLastNode) + string(texttree.TreeNodeIdentifier) + fn
   136  			} else {
   137  				identifier = string(texttree.TreeMidbseNode) + string(texttree.TreeNodeIdentifier) + fn
   138  			}
   139  			rows = append(rows, types.MakeCausets(
   140  				identifier,
   141  				id,
   142  				state,
   143  				strings.TrimSpace(loc),
   144  			))
   145  		}
   146  	}
   147  	return rows, nil
   148  }
   149  
   150  // getFuncMemUsage get function memory usage from heap profile
   151  func (c *DefCauslector) getFuncMemUsage(name string) (int64, error) {
   152  	prof := pprof.Lookup("heap")
   153  	if prof == nil {
   154  		return 0, errors.Errorf("cannot retrieve %s profile", name)
   155  	}
   156  	debug := 0
   157  	buffer := &bytes.Buffer{}
   158  	if err := prof.WriteTo(buffer, debug); err != nil {
   159  		return 0, err
   160  	}
   161  	p, err := profile.Parse(buffer)
   162  	if err != nil {
   163  		return 0, err
   164  	}
   165  	root, err := c.profileToFlamegraphNode(p)
   166  	if err != nil {
   167  		return 0, err
   168  	}
   169  	return root.defCauslectFuncUsage(name), nil
   170  }