You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
3.9 KiB
141 lines
3.9 KiB
// Copyright 2019 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package profile
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
"runtime/pprof"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/pprof/profile"
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/texttree"
|
|
)
|
|
|
|
// CPUProfileInterval represents the duration of sampling CPU
|
|
var CPUProfileInterval = 30 * time.Second
|
|
|
|
// Collector is used to collect the profile results
|
|
type Collector struct{}
|
|
|
|
// ProfileReaderToDatums reads data from reader and returns the flamegraph which is organized by tree form.
|
|
func (c *Collector) ProfileReaderToDatums(f io.Reader) ([][]types.Datum, error) {
|
|
p, err := profile.Parse(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.profileToDatums(p)
|
|
}
|
|
|
|
func (c *Collector) profileToDatums(p *profile.Profile) ([][]types.Datum, error) {
|
|
err := p.CheckValid()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
root := newFlamegraphNode()
|
|
for _, sample := range p.Sample {
|
|
root.add(sample)
|
|
}
|
|
|
|
col := newFlamegraphCollector(p)
|
|
col.collect(root)
|
|
return col.rows, nil
|
|
}
|
|
|
|
// cpuProfileGraph returns the CPU profile flamegraph which is organized by tree form
|
|
func (c *Collector) cpuProfileGraph() ([][]types.Datum, error) {
|
|
buffer := &bytes.Buffer{}
|
|
if err := pprof.StartCPUProfile(buffer); err != nil {
|
|
panic(err)
|
|
}
|
|
time.Sleep(CPUProfileInterval)
|
|
pprof.StopCPUProfile()
|
|
return c.ProfileReaderToDatums(buffer)
|
|
}
|
|
|
|
// ProfileGraph returns the CPU/memory/mutex/allocs/block profile flamegraph which is organized by tree form
|
|
func (c *Collector) ProfileGraph(name string) ([][]types.Datum, error) {
|
|
if strings.ToLower(strings.TrimSpace(name)) == "cpu" {
|
|
return c.cpuProfileGraph()
|
|
}
|
|
|
|
p := pprof.Lookup(name)
|
|
if p == nil {
|
|
return nil, errors.Errorf("cannot retrieve %s profile", name)
|
|
}
|
|
debug := 0
|
|
if name == "goroutine" {
|
|
debug = 2
|
|
}
|
|
buffer := &bytes.Buffer{}
|
|
if err := p.WriteTo(buffer, debug); err != nil {
|
|
return nil, err
|
|
}
|
|
if name == "goroutine" {
|
|
return c.ParseGoroutines(buffer)
|
|
}
|
|
return c.ProfileReaderToDatums(buffer)
|
|
}
|
|
|
|
// ParseGoroutines returns the groutine list for given string representation
|
|
func (c *Collector) ParseGoroutines(reader io.Reader) ([][]types.Datum, error) {
|
|
content, err := ioutil.ReadAll(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
goroutines := strings.Split(string(content), "\n\n")
|
|
var rows [][]types.Datum
|
|
for _, goroutine := range goroutines {
|
|
colIndex := strings.Index(goroutine, ":")
|
|
if colIndex < 0 {
|
|
return nil, errors.New("goroutine incompatible with current go version")
|
|
}
|
|
|
|
headers := strings.SplitN(strings.TrimSpace(goroutine[len("goroutine")+1:colIndex]), " ", 2)
|
|
if len(headers) != 2 {
|
|
return nil, errors.Errorf("incompatible goroutine headers: %s", goroutine[len("goroutine")+1:colIndex])
|
|
}
|
|
id, err := strconv.Atoi(strings.TrimSpace(headers[0]))
|
|
if err != nil {
|
|
return nil, errors.Annotatef(err, "invalid goroutine id: %s", headers[0])
|
|
}
|
|
state := strings.Trim(headers[1], "[]")
|
|
stack := strings.Split(strings.TrimSpace(goroutine[colIndex+1:]), "\n")
|
|
for i := 0; i < len(stack)/2; i++ {
|
|
fn := stack[i*2]
|
|
loc := stack[i*2+1]
|
|
var identifier string
|
|
if i == 0 {
|
|
identifier = fn
|
|
} else if i == len(stack)/2-1 {
|
|
identifier = string(texttree.TreeLastNode) + string(texttree.TreeNodeIdentifier) + fn
|
|
} else {
|
|
identifier = string(texttree.TreeMiddleNode) + string(texttree.TreeNodeIdentifier) + fn
|
|
}
|
|
rows = append(rows, types.MakeDatums(
|
|
identifier,
|
|
id,
|
|
state,
|
|
strings.TrimSpace(loc),
|
|
))
|
|
}
|
|
}
|
|
return rows, nil
|
|
}
|
|
|