Initial commit: Go 1.23 release state
This commit is contained in:
482
src/internal/profile/encode.go
Normal file
482
src/internal/profile/encode.go
Normal file
@@ -0,0 +1,482 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package profile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (p *Profile) decoder() []decoder {
|
||||
return profileDecoder
|
||||
}
|
||||
|
||||
// preEncode populates the unexported fields to be used by encode
|
||||
// (with suffix X) from the corresponding exported fields. The
|
||||
// exported fields are cleared up to facilitate testing.
|
||||
func (p *Profile) preEncode() {
|
||||
strings := make(map[string]int)
|
||||
addString(strings, "")
|
||||
|
||||
for _, st := range p.SampleType {
|
||||
st.typeX = addString(strings, st.Type)
|
||||
st.unitX = addString(strings, st.Unit)
|
||||
}
|
||||
|
||||
for _, s := range p.Sample {
|
||||
s.labelX = nil
|
||||
var keys []string
|
||||
for k := range s.Label {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
vs := s.Label[k]
|
||||
for _, v := range vs {
|
||||
s.labelX = append(s.labelX,
|
||||
Label{
|
||||
keyX: addString(strings, k),
|
||||
strX: addString(strings, v),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
var numKeys []string
|
||||
for k := range s.NumLabel {
|
||||
numKeys = append(numKeys, k)
|
||||
}
|
||||
sort.Strings(numKeys)
|
||||
for _, k := range numKeys {
|
||||
vs := s.NumLabel[k]
|
||||
for _, v := range vs {
|
||||
s.labelX = append(s.labelX,
|
||||
Label{
|
||||
keyX: addString(strings, k),
|
||||
numX: v,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
s.locationIDX = nil
|
||||
for _, l := range s.Location {
|
||||
s.locationIDX = append(s.locationIDX, l.ID)
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range p.Mapping {
|
||||
m.fileX = addString(strings, m.File)
|
||||
m.buildIDX = addString(strings, m.BuildID)
|
||||
}
|
||||
|
||||
for _, l := range p.Location {
|
||||
for i, ln := range l.Line {
|
||||
if ln.Function != nil {
|
||||
l.Line[i].functionIDX = ln.Function.ID
|
||||
} else {
|
||||
l.Line[i].functionIDX = 0
|
||||
}
|
||||
}
|
||||
if l.Mapping != nil {
|
||||
l.mappingIDX = l.Mapping.ID
|
||||
} else {
|
||||
l.mappingIDX = 0
|
||||
}
|
||||
}
|
||||
for _, f := range p.Function {
|
||||
f.nameX = addString(strings, f.Name)
|
||||
f.systemNameX = addString(strings, f.SystemName)
|
||||
f.filenameX = addString(strings, f.Filename)
|
||||
}
|
||||
|
||||
p.dropFramesX = addString(strings, p.DropFrames)
|
||||
p.keepFramesX = addString(strings, p.KeepFrames)
|
||||
|
||||
if pt := p.PeriodType; pt != nil {
|
||||
pt.typeX = addString(strings, pt.Type)
|
||||
pt.unitX = addString(strings, pt.Unit)
|
||||
}
|
||||
|
||||
p.stringTable = make([]string, len(strings))
|
||||
for s, i := range strings {
|
||||
p.stringTable[i] = s
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Profile) encode(b *buffer) {
|
||||
for _, x := range p.SampleType {
|
||||
encodeMessage(b, 1, x)
|
||||
}
|
||||
for _, x := range p.Sample {
|
||||
encodeMessage(b, 2, x)
|
||||
}
|
||||
for _, x := range p.Mapping {
|
||||
encodeMessage(b, 3, x)
|
||||
}
|
||||
for _, x := range p.Location {
|
||||
encodeMessage(b, 4, x)
|
||||
}
|
||||
for _, x := range p.Function {
|
||||
encodeMessage(b, 5, x)
|
||||
}
|
||||
encodeStrings(b, 6, p.stringTable)
|
||||
encodeInt64Opt(b, 7, p.dropFramesX)
|
||||
encodeInt64Opt(b, 8, p.keepFramesX)
|
||||
encodeInt64Opt(b, 9, p.TimeNanos)
|
||||
encodeInt64Opt(b, 10, p.DurationNanos)
|
||||
if pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) {
|
||||
encodeMessage(b, 11, p.PeriodType)
|
||||
}
|
||||
encodeInt64Opt(b, 12, p.Period)
|
||||
}
|
||||
|
||||
var profileDecoder = []decoder{
|
||||
nil, // 0
|
||||
// repeated ValueType sample_type = 1
|
||||
func(b *buffer, m message) error {
|
||||
x := new(ValueType)
|
||||
pp := m.(*Profile)
|
||||
pp.SampleType = append(pp.SampleType, x)
|
||||
return decodeMessage(b, x)
|
||||
},
|
||||
// repeated Sample sample = 2
|
||||
func(b *buffer, m message) error {
|
||||
x := new(Sample)
|
||||
pp := m.(*Profile)
|
||||
pp.Sample = append(pp.Sample, x)
|
||||
return decodeMessage(b, x)
|
||||
},
|
||||
// repeated Mapping mapping = 3
|
||||
func(b *buffer, m message) error {
|
||||
x := new(Mapping)
|
||||
pp := m.(*Profile)
|
||||
pp.Mapping = append(pp.Mapping, x)
|
||||
return decodeMessage(b, x)
|
||||
},
|
||||
// repeated Location location = 4
|
||||
func(b *buffer, m message) error {
|
||||
x := new(Location)
|
||||
pp := m.(*Profile)
|
||||
pp.Location = append(pp.Location, x)
|
||||
return decodeMessage(b, x)
|
||||
},
|
||||
// repeated Function function = 5
|
||||
func(b *buffer, m message) error {
|
||||
x := new(Function)
|
||||
pp := m.(*Profile)
|
||||
pp.Function = append(pp.Function, x)
|
||||
return decodeMessage(b, x)
|
||||
},
|
||||
// repeated string string_table = 6
|
||||
func(b *buffer, m message) error {
|
||||
err := decodeStrings(b, &m.(*Profile).stringTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.(*Profile).stringTable[0] != "" {
|
||||
return errors.New("string_table[0] must be ''")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// repeated int64 drop_frames = 7
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) },
|
||||
// repeated int64 keep_frames = 8
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) },
|
||||
// repeated int64 time_nanos = 9
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).TimeNanos) },
|
||||
// repeated int64 duration_nanos = 10
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) },
|
||||
// optional string period_type = 11
|
||||
func(b *buffer, m message) error {
|
||||
x := new(ValueType)
|
||||
pp := m.(*Profile)
|
||||
pp.PeriodType = x
|
||||
return decodeMessage(b, x)
|
||||
},
|
||||
// repeated int64 period = 12
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) },
|
||||
// repeated int64 comment = 13
|
||||
func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Profile).commentX) },
|
||||
// int64 defaultSampleType = 14
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).defaultSampleTypeX) },
|
||||
}
|
||||
|
||||
// postDecode takes the unexported fields populated by decode (with
|
||||
// suffix X) and populates the corresponding exported fields.
|
||||
// The unexported fields are cleared up to facilitate testing.
|
||||
func (p *Profile) postDecode() error {
|
||||
var err error
|
||||
|
||||
mappings := make(map[uint64]*Mapping)
|
||||
for _, m := range p.Mapping {
|
||||
m.File, err = getString(p.stringTable, &m.fileX, err)
|
||||
m.BuildID, err = getString(p.stringTable, &m.buildIDX, err)
|
||||
mappings[m.ID] = m
|
||||
}
|
||||
|
||||
functions := make(map[uint64]*Function)
|
||||
for _, f := range p.Function {
|
||||
f.Name, err = getString(p.stringTable, &f.nameX, err)
|
||||
f.SystemName, err = getString(p.stringTable, &f.systemNameX, err)
|
||||
f.Filename, err = getString(p.stringTable, &f.filenameX, err)
|
||||
functions[f.ID] = f
|
||||
}
|
||||
|
||||
locations := make(map[uint64]*Location)
|
||||
for _, l := range p.Location {
|
||||
l.Mapping = mappings[l.mappingIDX]
|
||||
l.mappingIDX = 0
|
||||
for i, ln := range l.Line {
|
||||
if id := ln.functionIDX; id != 0 {
|
||||
l.Line[i].Function = functions[id]
|
||||
if l.Line[i].Function == nil {
|
||||
return fmt.Errorf("Function ID %d not found", id)
|
||||
}
|
||||
l.Line[i].functionIDX = 0
|
||||
}
|
||||
}
|
||||
locations[l.ID] = l
|
||||
}
|
||||
|
||||
for _, st := range p.SampleType {
|
||||
st.Type, err = getString(p.stringTable, &st.typeX, err)
|
||||
st.Unit, err = getString(p.stringTable, &st.unitX, err)
|
||||
}
|
||||
|
||||
for _, s := range p.Sample {
|
||||
labels := make(map[string][]string)
|
||||
numLabels := make(map[string][]int64)
|
||||
for _, l := range s.labelX {
|
||||
var key, value string
|
||||
key, err = getString(p.stringTable, &l.keyX, err)
|
||||
if l.strX != 0 {
|
||||
value, err = getString(p.stringTable, &l.strX, err)
|
||||
labels[key] = append(labels[key], value)
|
||||
} else {
|
||||
numLabels[key] = append(numLabels[key], l.numX)
|
||||
}
|
||||
}
|
||||
if len(labels) > 0 {
|
||||
s.Label = labels
|
||||
}
|
||||
if len(numLabels) > 0 {
|
||||
s.NumLabel = numLabels
|
||||
}
|
||||
s.Location = nil
|
||||
for _, lid := range s.locationIDX {
|
||||
s.Location = append(s.Location, locations[lid])
|
||||
}
|
||||
s.locationIDX = nil
|
||||
}
|
||||
|
||||
p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err)
|
||||
p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err)
|
||||
|
||||
if pt := p.PeriodType; pt == nil {
|
||||
p.PeriodType = &ValueType{}
|
||||
}
|
||||
|
||||
if pt := p.PeriodType; pt != nil {
|
||||
pt.Type, err = getString(p.stringTable, &pt.typeX, err)
|
||||
pt.Unit, err = getString(p.stringTable, &pt.unitX, err)
|
||||
}
|
||||
for _, i := range p.commentX {
|
||||
var c string
|
||||
c, err = getString(p.stringTable, &i, err)
|
||||
p.Comments = append(p.Comments, c)
|
||||
}
|
||||
|
||||
p.commentX = nil
|
||||
p.DefaultSampleType, err = getString(p.stringTable, &p.defaultSampleTypeX, err)
|
||||
p.stringTable = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *ValueType) decoder() []decoder {
|
||||
return valueTypeDecoder
|
||||
}
|
||||
|
||||
func (p *ValueType) encode(b *buffer) {
|
||||
encodeInt64Opt(b, 1, p.typeX)
|
||||
encodeInt64Opt(b, 2, p.unitX)
|
||||
}
|
||||
|
||||
var valueTypeDecoder = []decoder{
|
||||
nil, // 0
|
||||
// optional int64 type = 1
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) },
|
||||
// optional int64 unit = 2
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) },
|
||||
}
|
||||
|
||||
func (p *Sample) decoder() []decoder {
|
||||
return sampleDecoder
|
||||
}
|
||||
|
||||
func (p *Sample) encode(b *buffer) {
|
||||
encodeUint64s(b, 1, p.locationIDX)
|
||||
for _, x := range p.Value {
|
||||
encodeInt64(b, 2, x)
|
||||
}
|
||||
for _, x := range p.labelX {
|
||||
encodeMessage(b, 3, x)
|
||||
}
|
||||
}
|
||||
|
||||
var sampleDecoder = []decoder{
|
||||
nil, // 0
|
||||
// repeated uint64 location = 1
|
||||
func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) },
|
||||
// repeated int64 value = 2
|
||||
func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) },
|
||||
// repeated Label label = 3
|
||||
func(b *buffer, m message) error {
|
||||
s := m.(*Sample)
|
||||
n := len(s.labelX)
|
||||
s.labelX = append(s.labelX, Label{})
|
||||
return decodeMessage(b, &s.labelX[n])
|
||||
},
|
||||
}
|
||||
|
||||
func (p Label) decoder() []decoder {
|
||||
return labelDecoder
|
||||
}
|
||||
|
||||
func (p Label) encode(b *buffer) {
|
||||
encodeInt64Opt(b, 1, p.keyX)
|
||||
encodeInt64Opt(b, 2, p.strX)
|
||||
encodeInt64Opt(b, 3, p.numX)
|
||||
}
|
||||
|
||||
var labelDecoder = []decoder{
|
||||
nil, // 0
|
||||
// optional int64 key = 1
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).keyX) },
|
||||
// optional int64 str = 2
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).strX) },
|
||||
// optional int64 num = 3
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).numX) },
|
||||
}
|
||||
|
||||
func (p *Mapping) decoder() []decoder {
|
||||
return mappingDecoder
|
||||
}
|
||||
|
||||
func (p *Mapping) encode(b *buffer) {
|
||||
encodeUint64Opt(b, 1, p.ID)
|
||||
encodeUint64Opt(b, 2, p.Start)
|
||||
encodeUint64Opt(b, 3, p.Limit)
|
||||
encodeUint64Opt(b, 4, p.Offset)
|
||||
encodeInt64Opt(b, 5, p.fileX)
|
||||
encodeInt64Opt(b, 6, p.buildIDX)
|
||||
encodeBoolOpt(b, 7, p.HasFunctions)
|
||||
encodeBoolOpt(b, 8, p.HasFilenames)
|
||||
encodeBoolOpt(b, 9, p.HasLineNumbers)
|
||||
encodeBoolOpt(b, 10, p.HasInlineFrames)
|
||||
}
|
||||
|
||||
var mappingDecoder = []decoder{
|
||||
nil, // 0
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) }, // optional uint64 id = 1
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) }, // optional uint64 memory_offset = 2
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) }, // optional uint64 memory_limit = 3
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) }, // optional uint64 file_offset = 4
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) }, // optional int64 filename = 5
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) }, // optional int64 build_id = 6
|
||||
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) }, // optional bool has_functions = 7
|
||||
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) }, // optional bool has_filenames = 8
|
||||
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) }, // optional bool has_line_numbers = 9
|
||||
func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10
|
||||
}
|
||||
|
||||
func (p *Location) decoder() []decoder {
|
||||
return locationDecoder
|
||||
}
|
||||
|
||||
func (p *Location) encode(b *buffer) {
|
||||
encodeUint64Opt(b, 1, p.ID)
|
||||
encodeUint64Opt(b, 2, p.mappingIDX)
|
||||
encodeUint64Opt(b, 3, p.Address)
|
||||
for i := range p.Line {
|
||||
encodeMessage(b, 4, &p.Line[i])
|
||||
}
|
||||
}
|
||||
|
||||
var locationDecoder = []decoder{
|
||||
nil, // 0
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) }, // optional uint64 id = 1;
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2;
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) }, // optional uint64 address = 3;
|
||||
func(b *buffer, m message) error { // repeated Line line = 4
|
||||
pp := m.(*Location)
|
||||
n := len(pp.Line)
|
||||
pp.Line = append(pp.Line, Line{})
|
||||
return decodeMessage(b, &pp.Line[n])
|
||||
},
|
||||
}
|
||||
|
||||
func (p *Line) decoder() []decoder {
|
||||
return lineDecoder
|
||||
}
|
||||
|
||||
func (p *Line) encode(b *buffer) {
|
||||
encodeUint64Opt(b, 1, p.functionIDX)
|
||||
encodeInt64Opt(b, 2, p.Line)
|
||||
}
|
||||
|
||||
var lineDecoder = []decoder{
|
||||
nil, // 0
|
||||
// optional uint64 function_id = 1
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },
|
||||
// optional int64 line = 2
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },
|
||||
}
|
||||
|
||||
func (p *Function) decoder() []decoder {
|
||||
return functionDecoder
|
||||
}
|
||||
|
||||
func (p *Function) encode(b *buffer) {
|
||||
encodeUint64Opt(b, 1, p.ID)
|
||||
encodeInt64Opt(b, 2, p.nameX)
|
||||
encodeInt64Opt(b, 3, p.systemNameX)
|
||||
encodeInt64Opt(b, 4, p.filenameX)
|
||||
encodeInt64Opt(b, 5, p.StartLine)
|
||||
}
|
||||
|
||||
var functionDecoder = []decoder{
|
||||
nil, // 0
|
||||
// optional uint64 id = 1
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) },
|
||||
// optional int64 function_name = 2
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) },
|
||||
// optional int64 function_system_name = 3
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) },
|
||||
// repeated int64 filename = 4
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) },
|
||||
// optional int64 start_line = 5
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) },
|
||||
}
|
||||
|
||||
func addString(strings map[string]int, s string) int64 {
|
||||
i, ok := strings[s]
|
||||
if !ok {
|
||||
i = len(strings)
|
||||
strings[s] = i
|
||||
}
|
||||
return int64(i)
|
||||
}
|
||||
|
||||
func getString(strings []string, strng *int64, err error) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s := int(*strng)
|
||||
if s < 0 || s >= len(strings) {
|
||||
return "", errMalformed
|
||||
}
|
||||
*strng = 0
|
||||
return strings[s], nil
|
||||
}
|
||||
54
src/internal/profile/filter.go
Normal file
54
src/internal/profile/filter.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Implements methods to filter samples from profiles.
|
||||
|
||||
package profile
|
||||
|
||||
// TagMatch selects tags for filtering
|
||||
type TagMatch func(key, val string, nval int64) bool
|
||||
|
||||
// FilterSamplesByTag removes all samples from the profile, except
|
||||
// those that match focus and do not match the ignore regular
|
||||
// expression.
|
||||
func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) {
|
||||
samples := make([]*Sample, 0, len(p.Sample))
|
||||
for _, s := range p.Sample {
|
||||
focused, ignored := focusedSample(s, focus, ignore)
|
||||
fm = fm || focused
|
||||
im = im || ignored
|
||||
if focused && !ignored {
|
||||
samples = append(samples, s)
|
||||
}
|
||||
}
|
||||
p.Sample = samples
|
||||
return
|
||||
}
|
||||
|
||||
// focusedSample checks a sample against focus and ignore regexps.
|
||||
// Returns whether the focus/ignore regexps match any tags.
|
||||
func focusedSample(s *Sample, focus, ignore TagMatch) (fm, im bool) {
|
||||
fm = focus == nil
|
||||
for key, vals := range s.Label {
|
||||
for _, val := range vals {
|
||||
if ignore != nil && ignore(key, val, 0) {
|
||||
im = true
|
||||
}
|
||||
if !fm && focus(key, val, 0) {
|
||||
fm = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for key, vals := range s.NumLabel {
|
||||
for _, val := range vals {
|
||||
if ignore != nil && ignore(key, "", val) {
|
||||
im = true
|
||||
}
|
||||
if !fm && focus(key, "", val) {
|
||||
fm = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return fm, im
|
||||
}
|
||||
519
src/internal/profile/graph.go
Normal file
519
src/internal/profile/graph.go
Normal file
@@ -0,0 +1,519 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package profile represents a pprof profile as a directed graph.
|
||||
//
|
||||
// This package is a simplified fork of github.com/google/pprof/internal/graph.
|
||||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Options encodes the options for constructing a graph
|
||||
type Options struct {
|
||||
SampleValue func(s []int64) int64 // Function to compute the value of a sample
|
||||
SampleMeanDivisor func(s []int64) int64 // Function to compute the divisor for mean graphs, or nil
|
||||
|
||||
DropNegative bool // Drop nodes with overall negative values
|
||||
|
||||
KeptNodes NodeSet // If non-nil, only use nodes in this set
|
||||
}
|
||||
|
||||
// Nodes is an ordered collection of graph nodes.
|
||||
type Nodes []*Node
|
||||
|
||||
// Node is an entry on a profiling report. It represents a unique
|
||||
// program location.
|
||||
type Node struct {
|
||||
// Info describes the source location associated to this node.
|
||||
Info NodeInfo
|
||||
|
||||
// Function represents the function that this node belongs to. On
|
||||
// graphs with sub-function resolution (eg line number or
|
||||
// addresses), two nodes in a NodeMap that are part of the same
|
||||
// function have the same value of Node.Function. If the Node
|
||||
// represents the whole function, it points back to itself.
|
||||
Function *Node
|
||||
|
||||
// Values associated to this node. Flat is exclusive to this node,
|
||||
// Cum includes all descendents.
|
||||
Flat, FlatDiv, Cum, CumDiv int64
|
||||
|
||||
// In and out Contains the nodes immediately reaching or reached by
|
||||
// this node.
|
||||
In, Out EdgeMap
|
||||
}
|
||||
|
||||
// Graph summarizes a performance profile into a format that is
|
||||
// suitable for visualization.
|
||||
type Graph struct {
|
||||
Nodes Nodes
|
||||
}
|
||||
|
||||
// FlatValue returns the exclusive value for this node, computing the
|
||||
// mean if a divisor is available.
|
||||
func (n *Node) FlatValue() int64 {
|
||||
if n.FlatDiv == 0 {
|
||||
return n.Flat
|
||||
}
|
||||
return n.Flat / n.FlatDiv
|
||||
}
|
||||
|
||||
// CumValue returns the inclusive value for this node, computing the
|
||||
// mean if a divisor is available.
|
||||
func (n *Node) CumValue() int64 {
|
||||
if n.CumDiv == 0 {
|
||||
return n.Cum
|
||||
}
|
||||
return n.Cum / n.CumDiv
|
||||
}
|
||||
|
||||
// AddToEdge increases the weight of an edge between two nodes. If
|
||||
// there isn't such an edge one is created.
|
||||
func (n *Node) AddToEdge(to *Node, v int64, residual, inline bool) {
|
||||
n.AddToEdgeDiv(to, 0, v, residual, inline)
|
||||
}
|
||||
|
||||
// AddToEdgeDiv increases the weight of an edge between two nodes. If
|
||||
// there isn't such an edge one is created.
|
||||
func (n *Node) AddToEdgeDiv(to *Node, dv, v int64, residual, inline bool) {
|
||||
if e := n.Out.FindTo(to); e != nil {
|
||||
e.WeightDiv += dv
|
||||
e.Weight += v
|
||||
if residual {
|
||||
e.Residual = true
|
||||
}
|
||||
if !inline {
|
||||
e.Inline = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
info := &Edge{Src: n, Dest: to, WeightDiv: dv, Weight: v, Residual: residual, Inline: inline}
|
||||
n.Out.Add(info)
|
||||
to.In.Add(info)
|
||||
}
|
||||
|
||||
// NodeInfo contains the attributes for a node.
|
||||
type NodeInfo struct {
|
||||
Name string
|
||||
Address uint64
|
||||
StartLine, Lineno int
|
||||
}
|
||||
|
||||
// PrintableName calls the Node's Formatter function with a single space separator.
|
||||
func (i *NodeInfo) PrintableName() string {
|
||||
return strings.Join(i.NameComponents(), " ")
|
||||
}
|
||||
|
||||
// NameComponents returns the components of the printable name to be used for a node.
|
||||
func (i *NodeInfo) NameComponents() []string {
|
||||
var name []string
|
||||
if i.Address != 0 {
|
||||
name = append(name, fmt.Sprintf("%016x", i.Address))
|
||||
}
|
||||
if fun := i.Name; fun != "" {
|
||||
name = append(name, fun)
|
||||
}
|
||||
|
||||
switch {
|
||||
case i.Lineno != 0:
|
||||
// User requested line numbers, provide what we have.
|
||||
name = append(name, fmt.Sprintf(":%d", i.Lineno))
|
||||
case i.Name != "":
|
||||
// User requested function name. It was already included.
|
||||
default:
|
||||
// Do not leave it empty if there is no information at all.
|
||||
name = append(name, "<unknown>")
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// NodeMap maps from a node info struct to a node. It is used to merge
|
||||
// report entries with the same info.
|
||||
type NodeMap map[NodeInfo]*Node
|
||||
|
||||
// NodeSet is a collection of node info structs.
|
||||
type NodeSet map[NodeInfo]bool
|
||||
|
||||
// NodePtrSet is a collection of nodes. Trimming a graph or tree requires a set
|
||||
// of objects which uniquely identify the nodes to keep. In a graph, NodeInfo
|
||||
// works as a unique identifier; however, in a tree multiple nodes may share
|
||||
// identical NodeInfos. A *Node does uniquely identify a node so we can use that
|
||||
// instead. Though a *Node also uniquely identifies a node in a graph,
|
||||
// currently, during trimming, graphs are rebuilt from scratch using only the
|
||||
// NodeSet, so there would not be the required context of the initial graph to
|
||||
// allow for the use of *Node.
|
||||
type NodePtrSet map[*Node]bool
|
||||
|
||||
// FindOrInsertNode takes the info for a node and either returns a matching node
|
||||
// from the node map if one exists, or adds one to the map if one does not.
|
||||
// If kept is non-nil, nodes are only added if they can be located on it.
|
||||
func (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node {
|
||||
if kept != nil {
|
||||
if _, ok := kept[info]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if n, ok := nm[info]; ok {
|
||||
return n
|
||||
}
|
||||
|
||||
n := &Node{
|
||||
Info: info,
|
||||
}
|
||||
nm[info] = n
|
||||
if info.Address == 0 && info.Lineno == 0 {
|
||||
// This node represents the whole function, so point Function
|
||||
// back to itself.
|
||||
n.Function = n
|
||||
return n
|
||||
}
|
||||
// Find a node that represents the whole function.
|
||||
info.Address = 0
|
||||
info.Lineno = 0
|
||||
n.Function = nm.FindOrInsertNode(info, nil)
|
||||
return n
|
||||
}
|
||||
|
||||
// EdgeMap is used to represent the incoming/outgoing edges from a node.
|
||||
type EdgeMap []*Edge
|
||||
|
||||
func (em EdgeMap) FindTo(n *Node) *Edge {
|
||||
for _, e := range em {
|
||||
if e.Dest == n {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (em *EdgeMap) Add(e *Edge) {
|
||||
*em = append(*em, e)
|
||||
}
|
||||
|
||||
func (em *EdgeMap) Delete(e *Edge) {
|
||||
for i, edge := range *em {
|
||||
if edge == e {
|
||||
(*em)[i] = (*em)[len(*em)-1]
|
||||
*em = (*em)[:len(*em)-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edge contains any attributes to be represented about edges in a graph.
|
||||
type Edge struct {
|
||||
Src, Dest *Node
|
||||
// The summary weight of the edge
|
||||
Weight, WeightDiv int64
|
||||
|
||||
// residual edges connect nodes that were connected through a
|
||||
// separate node, which has been removed from the report.
|
||||
Residual bool
|
||||
// An inline edge represents a call that was inlined into the caller.
|
||||
Inline bool
|
||||
}
|
||||
|
||||
// WeightValue returns the weight value for this edge, normalizing if a
|
||||
// divisor is available.
|
||||
func (e *Edge) WeightValue() int64 {
|
||||
if e.WeightDiv == 0 {
|
||||
return e.Weight
|
||||
}
|
||||
return e.Weight / e.WeightDiv
|
||||
}
|
||||
|
||||
// NewGraph computes a graph from a profile.
|
||||
func NewGraph(prof *Profile, o *Options) *Graph {
|
||||
nodes, locationMap := CreateNodes(prof, o)
|
||||
seenNode := make(map[*Node]bool)
|
||||
seenEdge := make(map[nodePair]bool)
|
||||
for _, sample := range prof.Sample {
|
||||
var w, dw int64
|
||||
w = o.SampleValue(sample.Value)
|
||||
if o.SampleMeanDivisor != nil {
|
||||
dw = o.SampleMeanDivisor(sample.Value)
|
||||
}
|
||||
if dw == 0 && w == 0 {
|
||||
continue
|
||||
}
|
||||
for k := range seenNode {
|
||||
delete(seenNode, k)
|
||||
}
|
||||
for k := range seenEdge {
|
||||
delete(seenEdge, k)
|
||||
}
|
||||
var parent *Node
|
||||
// A residual edge goes over one or more nodes that were not kept.
|
||||
residual := false
|
||||
|
||||
// Group the sample frames, based on a global map.
|
||||
// Count only the last two frames as a call edge. Frames higher up
|
||||
// the stack are unlikely to be repeated calls (e.g. runtime.main
|
||||
// calling main.main). So adding weights to call edges higher up
|
||||
// the stack may be not reflecting the actual call edge weights
|
||||
// in the program. Without a branch profile this is just an
|
||||
// approximation.
|
||||
i := 1
|
||||
if last := len(sample.Location) - 1; last < i {
|
||||
i = last
|
||||
}
|
||||
for ; i >= 0; i-- {
|
||||
l := sample.Location[i]
|
||||
locNodes := locationMap.get(l.ID)
|
||||
for ni := len(locNodes) - 1; ni >= 0; ni-- {
|
||||
n := locNodes[ni]
|
||||
if n == nil {
|
||||
residual = true
|
||||
continue
|
||||
}
|
||||
// Add cum weight to all nodes in stack, avoiding double counting.
|
||||
_, sawNode := seenNode[n]
|
||||
if !sawNode {
|
||||
seenNode[n] = true
|
||||
n.addSample(dw, w, false)
|
||||
}
|
||||
// Update edge weights for all edges in stack, avoiding double counting.
|
||||
if (!sawNode || !seenEdge[nodePair{n, parent}]) && parent != nil && n != parent {
|
||||
seenEdge[nodePair{n, parent}] = true
|
||||
parent.AddToEdgeDiv(n, dw, w, residual, ni != len(locNodes)-1)
|
||||
}
|
||||
|
||||
parent = n
|
||||
residual = false
|
||||
}
|
||||
}
|
||||
if parent != nil && !residual {
|
||||
// Add flat weight to leaf node.
|
||||
parent.addSample(dw, w, true)
|
||||
}
|
||||
}
|
||||
|
||||
return selectNodesForGraph(nodes, o.DropNegative)
|
||||
}
|
||||
|
||||
func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph {
|
||||
// Collect nodes into a graph.
|
||||
gNodes := make(Nodes, 0, len(nodes))
|
||||
for _, n := range nodes {
|
||||
if n == nil {
|
||||
continue
|
||||
}
|
||||
if n.Cum == 0 && n.Flat == 0 {
|
||||
continue
|
||||
}
|
||||
if dropNegative && isNegative(n) {
|
||||
continue
|
||||
}
|
||||
gNodes = append(gNodes, n)
|
||||
}
|
||||
return &Graph{gNodes}
|
||||
}
|
||||
|
||||
type nodePair struct {
|
||||
src, dest *Node
|
||||
}
|
||||
|
||||
// isNegative returns true if the node is considered as "negative" for the
|
||||
// purposes of drop_negative.
|
||||
func isNegative(n *Node) bool {
|
||||
switch {
|
||||
case n.Flat < 0:
|
||||
return true
|
||||
case n.Flat == 0 && n.Cum < 0:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type locationMap struct {
|
||||
s []Nodes // a slice for small sequential IDs
|
||||
m map[uint64]Nodes // fallback for large IDs (unlikely)
|
||||
}
|
||||
|
||||
func (l *locationMap) add(id uint64, n Nodes) {
|
||||
if id < uint64(len(l.s)) {
|
||||
l.s[id] = n
|
||||
} else {
|
||||
l.m[id] = n
|
||||
}
|
||||
}
|
||||
|
||||
func (l locationMap) get(id uint64) Nodes {
|
||||
if id < uint64(len(l.s)) {
|
||||
return l.s[id]
|
||||
} else {
|
||||
return l.m[id]
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNodes creates graph nodes for all locations in a profile. It
|
||||
// returns set of all nodes, plus a mapping of each location to the
|
||||
// set of corresponding nodes (one per location.Line).
|
||||
func CreateNodes(prof *Profile, o *Options) (Nodes, locationMap) {
|
||||
locations := locationMap{make([]Nodes, len(prof.Location)+1), make(map[uint64]Nodes)}
|
||||
nm := make(NodeMap, len(prof.Location))
|
||||
for _, l := range prof.Location {
|
||||
lines := l.Line
|
||||
if len(lines) == 0 {
|
||||
lines = []Line{{}} // Create empty line to include location info.
|
||||
}
|
||||
nodes := make(Nodes, len(lines))
|
||||
for ln := range lines {
|
||||
nodes[ln] = nm.findOrInsertLine(l, lines[ln], o)
|
||||
}
|
||||
locations.add(l.ID, nodes)
|
||||
}
|
||||
return nm.nodes(), locations
|
||||
}
|
||||
|
||||
func (nm NodeMap) nodes() Nodes {
|
||||
nodes := make(Nodes, 0, len(nm))
|
||||
for _, n := range nm {
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (nm NodeMap) findOrInsertLine(l *Location, li Line, o *Options) *Node {
|
||||
var objfile string
|
||||
if m := l.Mapping; m != nil && m.File != "" {
|
||||
objfile = m.File
|
||||
}
|
||||
|
||||
if ni := nodeInfo(l, li, objfile, o); ni != nil {
|
||||
return nm.FindOrInsertNode(*ni, o.KeptNodes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nodeInfo(l *Location, line Line, objfile string, o *Options) *NodeInfo {
|
||||
if line.Function == nil {
|
||||
return &NodeInfo{Address: l.Address}
|
||||
}
|
||||
ni := &NodeInfo{
|
||||
Address: l.Address,
|
||||
Lineno: int(line.Line),
|
||||
Name: line.Function.Name,
|
||||
}
|
||||
ni.StartLine = int(line.Function.StartLine)
|
||||
return ni
|
||||
}
|
||||
|
||||
// Sum adds the flat and cum values of a set of nodes.
|
||||
func (ns Nodes) Sum() (flat int64, cum int64) {
|
||||
for _, n := range ns {
|
||||
flat += n.Flat
|
||||
cum += n.Cum
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Node) addSample(dw, w int64, flat bool) {
|
||||
// Update sample value
|
||||
if flat {
|
||||
n.FlatDiv += dw
|
||||
n.Flat += w
|
||||
} else {
|
||||
n.CumDiv += dw
|
||||
n.Cum += w
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a text representation of a graph, for debugging purposes.
|
||||
func (g *Graph) String() string {
|
||||
var s []string
|
||||
|
||||
nodeIndex := make(map[*Node]int, len(g.Nodes))
|
||||
|
||||
for i, n := range g.Nodes {
|
||||
nodeIndex[n] = i + 1
|
||||
}
|
||||
|
||||
for i, n := range g.Nodes {
|
||||
name := n.Info.PrintableName()
|
||||
var in, out []int
|
||||
|
||||
for _, from := range n.In {
|
||||
in = append(in, nodeIndex[from.Src])
|
||||
}
|
||||
for _, to := range n.Out {
|
||||
out = append(out, nodeIndex[to.Dest])
|
||||
}
|
||||
s = append(s, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", i+1, name, n.Flat, n.Cum, in, out))
|
||||
}
|
||||
return strings.Join(s, "\n")
|
||||
}
|
||||
|
||||
// Sort returns a slice of the edges in the map, in a consistent
|
||||
// order. The sort order is first based on the edge weight
|
||||
// (higher-to-lower) and then by the node names to avoid flakiness.
|
||||
func (em EdgeMap) Sort() []*Edge {
|
||||
el := make(edgeList, 0, len(em))
|
||||
for _, w := range em {
|
||||
el = append(el, w)
|
||||
}
|
||||
|
||||
sort.Sort(el)
|
||||
return el
|
||||
}
|
||||
|
||||
// Sum returns the total weight for a set of nodes.
|
||||
func (em EdgeMap) Sum() int64 {
|
||||
var ret int64
|
||||
for _, edge := range em {
|
||||
ret += edge.Weight
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type edgeList []*Edge
|
||||
|
||||
func (el edgeList) Len() int {
|
||||
return len(el)
|
||||
}
|
||||
|
||||
func (el edgeList) Less(i, j int) bool {
|
||||
if el[i].Weight != el[j].Weight {
|
||||
return abs64(el[i].Weight) > abs64(el[j].Weight)
|
||||
}
|
||||
|
||||
from1 := el[i].Src.Info.PrintableName()
|
||||
from2 := el[j].Src.Info.PrintableName()
|
||||
if from1 != from2 {
|
||||
return from1 < from2
|
||||
}
|
||||
|
||||
to1 := el[i].Dest.Info.PrintableName()
|
||||
to2 := el[j].Dest.Info.PrintableName()
|
||||
|
||||
return to1 < to2
|
||||
}
|
||||
|
||||
func (el edgeList) Swap(i, j int) {
|
||||
el[i], el[j] = el[j], el[i]
|
||||
}
|
||||
|
||||
func abs64(i int64) int64 {
|
||||
if i < 0 {
|
||||
return -i
|
||||
}
|
||||
return i
|
||||
}
|
||||
461
src/internal/profile/merge.go
Normal file
461
src/internal/profile/merge.go
Normal file
@@ -0,0 +1,461 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Merge merges all the profiles in profs into a single Profile.
|
||||
// Returns a new profile independent of the input profiles. The merged
|
||||
// profile is compacted to eliminate unused samples, locations,
|
||||
// functions and mappings. Profiles must have identical profile sample
|
||||
// and period types or the merge will fail. profile.Period of the
|
||||
// resulting profile will be the maximum of all profiles, and
|
||||
// profile.TimeNanos will be the earliest nonzero one.
|
||||
func Merge(srcs []*Profile) (*Profile, error) {
|
||||
if len(srcs) == 0 {
|
||||
return nil, fmt.Errorf("no profiles to merge")
|
||||
}
|
||||
p, err := combineHeaders(srcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pm := &profileMerger{
|
||||
p: p,
|
||||
samples: make(map[sampleKey]*Sample, len(srcs[0].Sample)),
|
||||
locations: make(map[locationKey]*Location, len(srcs[0].Location)),
|
||||
functions: make(map[functionKey]*Function, len(srcs[0].Function)),
|
||||
mappings: make(map[mappingKey]*Mapping, len(srcs[0].Mapping)),
|
||||
}
|
||||
|
||||
for _, src := range srcs {
|
||||
// Clear the profile-specific hash tables
|
||||
pm.locationsByID = make(map[uint64]*Location, len(src.Location))
|
||||
pm.functionsByID = make(map[uint64]*Function, len(src.Function))
|
||||
pm.mappingsByID = make(map[uint64]mapInfo, len(src.Mapping))
|
||||
|
||||
if len(pm.mappings) == 0 && len(src.Mapping) > 0 {
|
||||
// The Mapping list has the property that the first mapping
|
||||
// represents the main binary. Take the first Mapping we see,
|
||||
// otherwise the operations below will add mappings in an
|
||||
// arbitrary order.
|
||||
pm.mapMapping(src.Mapping[0])
|
||||
}
|
||||
|
||||
for _, s := range src.Sample {
|
||||
if !isZeroSample(s) {
|
||||
pm.mapSample(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range p.Sample {
|
||||
if isZeroSample(s) {
|
||||
// If there are any zero samples, re-merge the profile to GC
|
||||
// them.
|
||||
return Merge([]*Profile{p})
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Normalize normalizes the source profile by multiplying each value in profile by the
|
||||
// ratio of the sum of the base profile's values of that sample type to the sum of the
|
||||
// source profile's value of that sample type.
|
||||
func (p *Profile) Normalize(pb *Profile) error {
|
||||
|
||||
if err := p.compatible(pb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseVals := make([]int64, len(p.SampleType))
|
||||
for _, s := range pb.Sample {
|
||||
for i, v := range s.Value {
|
||||
baseVals[i] += v
|
||||
}
|
||||
}
|
||||
|
||||
srcVals := make([]int64, len(p.SampleType))
|
||||
for _, s := range p.Sample {
|
||||
for i, v := range s.Value {
|
||||
srcVals[i] += v
|
||||
}
|
||||
}
|
||||
|
||||
normScale := make([]float64, len(baseVals))
|
||||
for i := range baseVals {
|
||||
if srcVals[i] == 0 {
|
||||
normScale[i] = 0.0
|
||||
} else {
|
||||
normScale[i] = float64(baseVals[i]) / float64(srcVals[i])
|
||||
}
|
||||
}
|
||||
p.ScaleN(normScale)
|
||||
return nil
|
||||
}
|
||||
|
||||
func isZeroSample(s *Sample) bool {
|
||||
for _, v := range s.Value {
|
||||
if v != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type profileMerger struct {
|
||||
p *Profile
|
||||
|
||||
// Memoization tables within a profile.
|
||||
locationsByID map[uint64]*Location
|
||||
functionsByID map[uint64]*Function
|
||||
mappingsByID map[uint64]mapInfo
|
||||
|
||||
// Memoization tables for profile entities.
|
||||
samples map[sampleKey]*Sample
|
||||
locations map[locationKey]*Location
|
||||
functions map[functionKey]*Function
|
||||
mappings map[mappingKey]*Mapping
|
||||
}
|
||||
|
||||
type mapInfo struct {
|
||||
m *Mapping
|
||||
offset int64
|
||||
}
|
||||
|
||||
func (pm *profileMerger) mapSample(src *Sample) *Sample {
|
||||
s := &Sample{
|
||||
Location: make([]*Location, len(src.Location)),
|
||||
Value: make([]int64, len(src.Value)),
|
||||
Label: make(map[string][]string, len(src.Label)),
|
||||
NumLabel: make(map[string][]int64, len(src.NumLabel)),
|
||||
NumUnit: make(map[string][]string, len(src.NumLabel)),
|
||||
}
|
||||
for i, l := range src.Location {
|
||||
s.Location[i] = pm.mapLocation(l)
|
||||
}
|
||||
for k, v := range src.Label {
|
||||
vv := make([]string, len(v))
|
||||
copy(vv, v)
|
||||
s.Label[k] = vv
|
||||
}
|
||||
for k, v := range src.NumLabel {
|
||||
u := src.NumUnit[k]
|
||||
vv := make([]int64, len(v))
|
||||
uu := make([]string, len(u))
|
||||
copy(vv, v)
|
||||
copy(uu, u)
|
||||
s.NumLabel[k] = vv
|
||||
s.NumUnit[k] = uu
|
||||
}
|
||||
// Check memoization table. Must be done on the remapped location to
|
||||
// account for the remapped mapping. Add current values to the
|
||||
// existing sample.
|
||||
k := s.key()
|
||||
if ss, ok := pm.samples[k]; ok {
|
||||
for i, v := range src.Value {
|
||||
ss.Value[i] += v
|
||||
}
|
||||
return ss
|
||||
}
|
||||
copy(s.Value, src.Value)
|
||||
pm.samples[k] = s
|
||||
pm.p.Sample = append(pm.p.Sample, s)
|
||||
return s
|
||||
}
|
||||
|
||||
// key generates sampleKey to be used as a key for maps.
|
||||
func (sample *Sample) key() sampleKey {
|
||||
ids := make([]string, len(sample.Location))
|
||||
for i, l := range sample.Location {
|
||||
ids[i] = strconv.FormatUint(l.ID, 16)
|
||||
}
|
||||
|
||||
labels := make([]string, 0, len(sample.Label))
|
||||
for k, v := range sample.Label {
|
||||
labels = append(labels, fmt.Sprintf("%q%q", k, v))
|
||||
}
|
||||
sort.Strings(labels)
|
||||
|
||||
numlabels := make([]string, 0, len(sample.NumLabel))
|
||||
for k, v := range sample.NumLabel {
|
||||
numlabels = append(numlabels, fmt.Sprintf("%q%x%x", k, v, sample.NumUnit[k]))
|
||||
}
|
||||
sort.Strings(numlabels)
|
||||
|
||||
return sampleKey{
|
||||
strings.Join(ids, "|"),
|
||||
strings.Join(labels, ""),
|
||||
strings.Join(numlabels, ""),
|
||||
}
|
||||
}
|
||||
|
||||
type sampleKey struct {
|
||||
locations string
|
||||
labels string
|
||||
numlabels string
|
||||
}
|
||||
|
||||
func (pm *profileMerger) mapLocation(src *Location) *Location {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if l, ok := pm.locationsByID[src.ID]; ok {
|
||||
pm.locationsByID[src.ID] = l
|
||||
return l
|
||||
}
|
||||
|
||||
mi := pm.mapMapping(src.Mapping)
|
||||
l := &Location{
|
||||
ID: uint64(len(pm.p.Location) + 1),
|
||||
Mapping: mi.m,
|
||||
Address: uint64(int64(src.Address) + mi.offset),
|
||||
Line: make([]Line, len(src.Line)),
|
||||
IsFolded: src.IsFolded,
|
||||
}
|
||||
for i, ln := range src.Line {
|
||||
l.Line[i] = pm.mapLine(ln)
|
||||
}
|
||||
// Check memoization table. Must be done on the remapped location to
|
||||
// account for the remapped mapping ID.
|
||||
k := l.key()
|
||||
if ll, ok := pm.locations[k]; ok {
|
||||
pm.locationsByID[src.ID] = ll
|
||||
return ll
|
||||
}
|
||||
pm.locationsByID[src.ID] = l
|
||||
pm.locations[k] = l
|
||||
pm.p.Location = append(pm.p.Location, l)
|
||||
return l
|
||||
}
|
||||
|
||||
// key generates locationKey to be used as a key for maps.
|
||||
func (l *Location) key() locationKey {
|
||||
key := locationKey{
|
||||
addr: l.Address,
|
||||
isFolded: l.IsFolded,
|
||||
}
|
||||
if l.Mapping != nil {
|
||||
// Normalizes address to handle address space randomization.
|
||||
key.addr -= l.Mapping.Start
|
||||
key.mappingID = l.Mapping.ID
|
||||
}
|
||||
lines := make([]string, len(l.Line)*2)
|
||||
for i, line := range l.Line {
|
||||
if line.Function != nil {
|
||||
lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
|
||||
}
|
||||
lines[i*2+1] = strconv.FormatInt(line.Line, 16)
|
||||
}
|
||||
key.lines = strings.Join(lines, "|")
|
||||
return key
|
||||
}
|
||||
|
||||
type locationKey struct {
|
||||
addr, mappingID uint64
|
||||
lines string
|
||||
isFolded bool
|
||||
}
|
||||
|
||||
func (pm *profileMerger) mapMapping(src *Mapping) mapInfo {
|
||||
if src == nil {
|
||||
return mapInfo{}
|
||||
}
|
||||
|
||||
if mi, ok := pm.mappingsByID[src.ID]; ok {
|
||||
return mi
|
||||
}
|
||||
|
||||
// Check memoization tables.
|
||||
mk := src.key()
|
||||
if m, ok := pm.mappings[mk]; ok {
|
||||
mi := mapInfo{m, int64(m.Start) - int64(src.Start)}
|
||||
pm.mappingsByID[src.ID] = mi
|
||||
return mi
|
||||
}
|
||||
m := &Mapping{
|
||||
ID: uint64(len(pm.p.Mapping) + 1),
|
||||
Start: src.Start,
|
||||
Limit: src.Limit,
|
||||
Offset: src.Offset,
|
||||
File: src.File,
|
||||
BuildID: src.BuildID,
|
||||
HasFunctions: src.HasFunctions,
|
||||
HasFilenames: src.HasFilenames,
|
||||
HasLineNumbers: src.HasLineNumbers,
|
||||
HasInlineFrames: src.HasInlineFrames,
|
||||
}
|
||||
pm.p.Mapping = append(pm.p.Mapping, m)
|
||||
|
||||
// Update memoization tables.
|
||||
pm.mappings[mk] = m
|
||||
mi := mapInfo{m, 0}
|
||||
pm.mappingsByID[src.ID] = mi
|
||||
return mi
|
||||
}
|
||||
|
||||
// key generates encoded strings of Mapping to be used as a key for
|
||||
// maps.
|
||||
func (m *Mapping) key() mappingKey {
|
||||
// Normalize addresses to handle address space randomization.
|
||||
// Round up to next 4K boundary to avoid minor discrepancies.
|
||||
const mapsizeRounding = 0x1000
|
||||
|
||||
size := m.Limit - m.Start
|
||||
size = size + mapsizeRounding - 1
|
||||
size = size - (size % mapsizeRounding)
|
||||
key := mappingKey{
|
||||
size: size,
|
||||
offset: m.Offset,
|
||||
}
|
||||
|
||||
switch {
|
||||
case m.BuildID != "":
|
||||
key.buildIDOrFile = m.BuildID
|
||||
case m.File != "":
|
||||
key.buildIDOrFile = m.File
|
||||
default:
|
||||
// A mapping containing neither build ID nor file name is a fake mapping. A
|
||||
// key with empty buildIDOrFile is used for fake mappings so that they are
|
||||
// treated as the same mapping during merging.
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
type mappingKey struct {
|
||||
size, offset uint64
|
||||
buildIDOrFile string
|
||||
}
|
||||
|
||||
func (pm *profileMerger) mapLine(src Line) Line {
|
||||
ln := Line{
|
||||
Function: pm.mapFunction(src.Function),
|
||||
Line: src.Line,
|
||||
}
|
||||
return ln
|
||||
}
|
||||
|
||||
func (pm *profileMerger) mapFunction(src *Function) *Function {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
if f, ok := pm.functionsByID[src.ID]; ok {
|
||||
return f
|
||||
}
|
||||
k := src.key()
|
||||
if f, ok := pm.functions[k]; ok {
|
||||
pm.functionsByID[src.ID] = f
|
||||
return f
|
||||
}
|
||||
f := &Function{
|
||||
ID: uint64(len(pm.p.Function) + 1),
|
||||
Name: src.Name,
|
||||
SystemName: src.SystemName,
|
||||
Filename: src.Filename,
|
||||
StartLine: src.StartLine,
|
||||
}
|
||||
pm.functions[k] = f
|
||||
pm.functionsByID[src.ID] = f
|
||||
pm.p.Function = append(pm.p.Function, f)
|
||||
return f
|
||||
}
|
||||
|
||||
// key generates a struct to be used as a key for maps.
|
||||
func (f *Function) key() functionKey {
|
||||
return functionKey{
|
||||
f.StartLine,
|
||||
f.Name,
|
||||
f.SystemName,
|
||||
f.Filename,
|
||||
}
|
||||
}
|
||||
|
||||
type functionKey struct {
|
||||
startLine int64
|
||||
name, systemName, fileName string
|
||||
}
|
||||
|
||||
// combineHeaders checks that all profiles can be merged and returns
|
||||
// their combined profile.
|
||||
func combineHeaders(srcs []*Profile) (*Profile, error) {
|
||||
for _, s := range srcs[1:] {
|
||||
if err := srcs[0].compatible(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var timeNanos, durationNanos, period int64
|
||||
var comments []string
|
||||
seenComments := map[string]bool{}
|
||||
var defaultSampleType string
|
||||
for _, s := range srcs {
|
||||
if timeNanos == 0 || s.TimeNanos < timeNanos {
|
||||
timeNanos = s.TimeNanos
|
||||
}
|
||||
durationNanos += s.DurationNanos
|
||||
if period == 0 || period < s.Period {
|
||||
period = s.Period
|
||||
}
|
||||
for _, c := range s.Comments {
|
||||
if seen := seenComments[c]; !seen {
|
||||
comments = append(comments, c)
|
||||
seenComments[c] = true
|
||||
}
|
||||
}
|
||||
if defaultSampleType == "" {
|
||||
defaultSampleType = s.DefaultSampleType
|
||||
}
|
||||
}
|
||||
|
||||
p := &Profile{
|
||||
SampleType: make([]*ValueType, len(srcs[0].SampleType)),
|
||||
|
||||
DropFrames: srcs[0].DropFrames,
|
||||
KeepFrames: srcs[0].KeepFrames,
|
||||
|
||||
TimeNanos: timeNanos,
|
||||
DurationNanos: durationNanos,
|
||||
PeriodType: srcs[0].PeriodType,
|
||||
Period: period,
|
||||
|
||||
Comments: comments,
|
||||
DefaultSampleType: defaultSampleType,
|
||||
}
|
||||
copy(p.SampleType, srcs[0].SampleType)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// compatible determines if two profiles can be compared/merged.
|
||||
// returns nil if the profiles are compatible; otherwise an error with
|
||||
// details on the incompatibility.
|
||||
func (p *Profile) compatible(pb *Profile) error {
|
||||
if !equalValueType(p.PeriodType, pb.PeriodType) {
|
||||
return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
|
||||
}
|
||||
|
||||
if len(p.SampleType) != len(pb.SampleType) {
|
||||
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
|
||||
}
|
||||
|
||||
for i := range p.SampleType {
|
||||
if !equalValueType(p.SampleType[i], pb.SampleType[i]) {
|
||||
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// equalValueType returns true if the two value types are semantically
|
||||
// equal. It ignores the internal fields used during encode/decode.
|
||||
func equalValueType(st1, st2 *ValueType) bool {
|
||||
return st1.Type == st2.Type && st1.Unit == st2.Unit
|
||||
}
|
||||
568
src/internal/profile/profile.go
Normal file
568
src/internal/profile/profile.go
Normal file
@@ -0,0 +1,568 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package profile provides a representation of
|
||||
// github.com/google/pprof/proto/profile.proto and
|
||||
// methods to encode/decode/merge profiles in this format.
|
||||
package profile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Profile is an in-memory representation of profile.proto.
|
||||
type Profile struct {
|
||||
SampleType []*ValueType
|
||||
DefaultSampleType string
|
||||
Sample []*Sample
|
||||
Mapping []*Mapping
|
||||
Location []*Location
|
||||
Function []*Function
|
||||
Comments []string
|
||||
|
||||
DropFrames string
|
||||
KeepFrames string
|
||||
|
||||
TimeNanos int64
|
||||
DurationNanos int64
|
||||
PeriodType *ValueType
|
||||
Period int64
|
||||
|
||||
commentX []int64
|
||||
dropFramesX int64
|
||||
keepFramesX int64
|
||||
stringTable []string
|
||||
defaultSampleTypeX int64
|
||||
}
|
||||
|
||||
// ValueType corresponds to Profile.ValueType
|
||||
type ValueType struct {
|
||||
Type string // cpu, wall, inuse_space, etc
|
||||
Unit string // seconds, nanoseconds, bytes, etc
|
||||
|
||||
typeX int64
|
||||
unitX int64
|
||||
}
|
||||
|
||||
// Sample corresponds to Profile.Sample
|
||||
type Sample struct {
|
||||
Location []*Location
|
||||
Value []int64
|
||||
Label map[string][]string
|
||||
NumLabel map[string][]int64
|
||||
NumUnit map[string][]string
|
||||
|
||||
locationIDX []uint64
|
||||
labelX []Label
|
||||
}
|
||||
|
||||
// Label corresponds to Profile.Label
|
||||
type Label struct {
|
||||
keyX int64
|
||||
// Exactly one of the two following values must be set
|
||||
strX int64
|
||||
numX int64 // Integer value for this label
|
||||
}
|
||||
|
||||
// Mapping corresponds to Profile.Mapping
|
||||
type Mapping struct {
|
||||
ID uint64
|
||||
Start uint64
|
||||
Limit uint64
|
||||
Offset uint64
|
||||
File string
|
||||
BuildID string
|
||||
HasFunctions bool
|
||||
HasFilenames bool
|
||||
HasLineNumbers bool
|
||||
HasInlineFrames bool
|
||||
|
||||
fileX int64
|
||||
buildIDX int64
|
||||
}
|
||||
|
||||
// Location corresponds to Profile.Location
|
||||
type Location struct {
|
||||
ID uint64
|
||||
Mapping *Mapping
|
||||
Address uint64
|
||||
Line []Line
|
||||
IsFolded bool
|
||||
|
||||
mappingIDX uint64
|
||||
}
|
||||
|
||||
// Line corresponds to Profile.Line
|
||||
type Line struct {
|
||||
Function *Function
|
||||
Line int64
|
||||
|
||||
functionIDX uint64
|
||||
}
|
||||
|
||||
// Function corresponds to Profile.Function
|
||||
type Function struct {
|
||||
ID uint64
|
||||
Name string
|
||||
SystemName string
|
||||
Filename string
|
||||
StartLine int64
|
||||
|
||||
nameX int64
|
||||
systemNameX int64
|
||||
filenameX int64
|
||||
}
|
||||
|
||||
// Parse parses a profile and checks for its validity. The input must be an
|
||||
// encoded pprof protobuf, which may optionally be gzip-compressed.
|
||||
func Parse(r io.Reader) (*Profile, error) {
|
||||
orig, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(orig))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decompressing profile: %v", err)
|
||||
}
|
||||
data, err := io.ReadAll(gz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decompressing profile: %v", err)
|
||||
}
|
||||
orig = data
|
||||
}
|
||||
|
||||
p, err := parseUncompressed(orig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing profile: %w", err)
|
||||
}
|
||||
|
||||
if err := p.CheckValid(); err != nil {
|
||||
return nil, fmt.Errorf("malformed profile: %v", err)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
var errMalformed = fmt.Errorf("malformed profile format")
|
||||
var ErrNoData = fmt.Errorf("empty input file")
|
||||
|
||||
func parseUncompressed(data []byte) (*Profile, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, ErrNoData
|
||||
}
|
||||
|
||||
p := &Profile{}
|
||||
if err := unmarshal(data, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := p.postDecode(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Write writes the profile as a gzip-compressed marshaled protobuf.
|
||||
func (p *Profile) Write(w io.Writer) error {
|
||||
p.preEncode()
|
||||
b := marshal(p)
|
||||
zw := gzip.NewWriter(w)
|
||||
defer zw.Close()
|
||||
_, err := zw.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckValid tests whether the profile is valid. Checks include, but are
|
||||
// not limited to:
|
||||
// - len(Profile.Sample[n].value) == len(Profile.value_unit)
|
||||
// - Sample.id has a corresponding Profile.Location
|
||||
func (p *Profile) CheckValid() error {
|
||||
// Check that sample values are consistent
|
||||
sampleLen := len(p.SampleType)
|
||||
if sampleLen == 0 && len(p.Sample) != 0 {
|
||||
return fmt.Errorf("missing sample type information")
|
||||
}
|
||||
for _, s := range p.Sample {
|
||||
if len(s.Value) != sampleLen {
|
||||
return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all mappings/locations/functions are in the tables
|
||||
// Check that there are no duplicate ids
|
||||
mappings := make(map[uint64]*Mapping, len(p.Mapping))
|
||||
for _, m := range p.Mapping {
|
||||
if m.ID == 0 {
|
||||
return fmt.Errorf("found mapping with reserved ID=0")
|
||||
}
|
||||
if mappings[m.ID] != nil {
|
||||
return fmt.Errorf("multiple mappings with same id: %d", m.ID)
|
||||
}
|
||||
mappings[m.ID] = m
|
||||
}
|
||||
functions := make(map[uint64]*Function, len(p.Function))
|
||||
for _, f := range p.Function {
|
||||
if f.ID == 0 {
|
||||
return fmt.Errorf("found function with reserved ID=0")
|
||||
}
|
||||
if functions[f.ID] != nil {
|
||||
return fmt.Errorf("multiple functions with same id: %d", f.ID)
|
||||
}
|
||||
functions[f.ID] = f
|
||||
}
|
||||
locations := make(map[uint64]*Location, len(p.Location))
|
||||
for _, l := range p.Location {
|
||||
if l.ID == 0 {
|
||||
return fmt.Errorf("found location with reserved id=0")
|
||||
}
|
||||
if locations[l.ID] != nil {
|
||||
return fmt.Errorf("multiple locations with same id: %d", l.ID)
|
||||
}
|
||||
locations[l.ID] = l
|
||||
if m := l.Mapping; m != nil {
|
||||
if m.ID == 0 || mappings[m.ID] != m {
|
||||
return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
|
||||
}
|
||||
}
|
||||
for _, ln := range l.Line {
|
||||
if f := ln.Function; f != nil {
|
||||
if f.ID == 0 || functions[f.ID] != f {
|
||||
return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Aggregate merges the locations in the profile into equivalence
|
||||
// classes preserving the request attributes. It also updates the
|
||||
// samples to point to the merged locations.
|
||||
func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
|
||||
for _, m := range p.Mapping {
|
||||
m.HasInlineFrames = m.HasInlineFrames && inlineFrame
|
||||
m.HasFunctions = m.HasFunctions && function
|
||||
m.HasFilenames = m.HasFilenames && filename
|
||||
m.HasLineNumbers = m.HasLineNumbers && linenumber
|
||||
}
|
||||
|
||||
// Aggregate functions
|
||||
if !function || !filename {
|
||||
for _, f := range p.Function {
|
||||
if !function {
|
||||
f.Name = ""
|
||||
f.SystemName = ""
|
||||
}
|
||||
if !filename {
|
||||
f.Filename = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate locations
|
||||
if !inlineFrame || !address || !linenumber {
|
||||
for _, l := range p.Location {
|
||||
if !inlineFrame && len(l.Line) > 1 {
|
||||
l.Line = l.Line[len(l.Line)-1:]
|
||||
}
|
||||
if !linenumber {
|
||||
for i := range l.Line {
|
||||
l.Line[i].Line = 0
|
||||
}
|
||||
}
|
||||
if !address {
|
||||
l.Address = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p.CheckValid()
|
||||
}
|
||||
|
||||
// Print dumps a text representation of a profile. Intended mainly
|
||||
// for debugging purposes.
|
||||
func (p *Profile) String() string {
|
||||
|
||||
ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
|
||||
if pt := p.PeriodType; pt != nil {
|
||||
ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
|
||||
}
|
||||
ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
|
||||
if p.TimeNanos != 0 {
|
||||
ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
|
||||
}
|
||||
if p.DurationNanos != 0 {
|
||||
ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos)))
|
||||
}
|
||||
|
||||
ss = append(ss, "Samples:")
|
||||
var sh1 string
|
||||
for _, s := range p.SampleType {
|
||||
sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit)
|
||||
}
|
||||
ss = append(ss, strings.TrimSpace(sh1))
|
||||
for _, s := range p.Sample {
|
||||
var sv string
|
||||
for _, v := range s.Value {
|
||||
sv = fmt.Sprintf("%s %10d", sv, v)
|
||||
}
|
||||
sv = sv + ": "
|
||||
for _, l := range s.Location {
|
||||
sv = sv + fmt.Sprintf("%d ", l.ID)
|
||||
}
|
||||
ss = append(ss, sv)
|
||||
const labelHeader = " "
|
||||
if len(s.Label) > 0 {
|
||||
ls := labelHeader
|
||||
for k, v := range s.Label {
|
||||
ls = ls + fmt.Sprintf("%s:%v ", k, v)
|
||||
}
|
||||
ss = append(ss, ls)
|
||||
}
|
||||
if len(s.NumLabel) > 0 {
|
||||
ls := labelHeader
|
||||
for k, v := range s.NumLabel {
|
||||
ls = ls + fmt.Sprintf("%s:%v ", k, v)
|
||||
}
|
||||
ss = append(ss, ls)
|
||||
}
|
||||
}
|
||||
|
||||
ss = append(ss, "Locations")
|
||||
for _, l := range p.Location {
|
||||
locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
|
||||
if m := l.Mapping; m != nil {
|
||||
locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
|
||||
}
|
||||
if len(l.Line) == 0 {
|
||||
ss = append(ss, locStr)
|
||||
}
|
||||
for li := range l.Line {
|
||||
lnStr := "??"
|
||||
if fn := l.Line[li].Function; fn != nil {
|
||||
lnStr = fmt.Sprintf("%s %s:%d s=%d",
|
||||
fn.Name,
|
||||
fn.Filename,
|
||||
l.Line[li].Line,
|
||||
fn.StartLine)
|
||||
if fn.Name != fn.SystemName {
|
||||
lnStr = lnStr + "(" + fn.SystemName + ")"
|
||||
}
|
||||
}
|
||||
ss = append(ss, locStr+lnStr)
|
||||
// Do not print location details past the first line
|
||||
locStr = " "
|
||||
}
|
||||
}
|
||||
|
||||
ss = append(ss, "Mappings")
|
||||
for _, m := range p.Mapping {
|
||||
bits := ""
|
||||
if m.HasFunctions {
|
||||
bits += "[FN]"
|
||||
}
|
||||
if m.HasFilenames {
|
||||
bits += "[FL]"
|
||||
}
|
||||
if m.HasLineNumbers {
|
||||
bits += "[LN]"
|
||||
}
|
||||
if m.HasInlineFrames {
|
||||
bits += "[IN]"
|
||||
}
|
||||
ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
|
||||
m.ID,
|
||||
m.Start, m.Limit, m.Offset,
|
||||
m.File,
|
||||
m.BuildID,
|
||||
bits))
|
||||
}
|
||||
|
||||
return strings.Join(ss, "\n") + "\n"
|
||||
}
|
||||
|
||||
// Merge adds profile p adjusted by ratio r into profile p. Profiles
|
||||
// must be compatible (same Type and SampleType).
|
||||
// TODO(rsilvera): consider normalizing the profiles based on the
|
||||
// total samples collected.
|
||||
func (p *Profile) Merge(pb *Profile, r float64) error {
|
||||
if err := p.Compatible(pb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pb = pb.Copy()
|
||||
|
||||
// Keep the largest of the two periods.
|
||||
if pb.Period > p.Period {
|
||||
p.Period = pb.Period
|
||||
}
|
||||
|
||||
p.DurationNanos += pb.DurationNanos
|
||||
|
||||
p.Mapping = append(p.Mapping, pb.Mapping...)
|
||||
for i, m := range p.Mapping {
|
||||
m.ID = uint64(i + 1)
|
||||
}
|
||||
p.Location = append(p.Location, pb.Location...)
|
||||
for i, l := range p.Location {
|
||||
l.ID = uint64(i + 1)
|
||||
}
|
||||
p.Function = append(p.Function, pb.Function...)
|
||||
for i, f := range p.Function {
|
||||
f.ID = uint64(i + 1)
|
||||
}
|
||||
|
||||
if r != 1.0 {
|
||||
for _, s := range pb.Sample {
|
||||
for i, v := range s.Value {
|
||||
s.Value[i] = int64((float64(v) * r))
|
||||
}
|
||||
}
|
||||
}
|
||||
p.Sample = append(p.Sample, pb.Sample...)
|
||||
return p.CheckValid()
|
||||
}
|
||||
|
||||
// Compatible determines if two profiles can be compared/merged.
|
||||
// returns nil if the profiles are compatible; otherwise an error with
|
||||
// details on the incompatibility.
|
||||
func (p *Profile) Compatible(pb *Profile) error {
|
||||
if !compatibleValueTypes(p.PeriodType, pb.PeriodType) {
|
||||
return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
|
||||
}
|
||||
|
||||
if len(p.SampleType) != len(pb.SampleType) {
|
||||
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
|
||||
}
|
||||
|
||||
for i := range p.SampleType {
|
||||
if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) {
|
||||
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasFunctions determines if all locations in this profile have
|
||||
// symbolized function information.
|
||||
func (p *Profile) HasFunctions() bool {
|
||||
for _, l := range p.Location {
|
||||
if l.Mapping == nil || !l.Mapping.HasFunctions {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HasFileLines determines if all locations in this profile have
|
||||
// symbolized file and line number information.
|
||||
func (p *Profile) HasFileLines() bool {
|
||||
for _, l := range p.Location {
|
||||
if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func compatibleValueTypes(v1, v2 *ValueType) bool {
|
||||
if v1 == nil || v2 == nil {
|
||||
return true // No grounds to disqualify.
|
||||
}
|
||||
return v1.Type == v2.Type && v1.Unit == v2.Unit
|
||||
}
|
||||
|
||||
// Copy makes a fully independent copy of a profile.
|
||||
func (p *Profile) Copy() *Profile {
|
||||
p.preEncode()
|
||||
b := marshal(p)
|
||||
|
||||
pp := &Profile{}
|
||||
if err := unmarshal(b, pp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := pp.postDecode(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return pp
|
||||
}
|
||||
|
||||
// Demangler maps symbol names to a human-readable form. This may
|
||||
// include C++ demangling and additional simplification. Names that
|
||||
// are not demangled may be missing from the resulting map.
|
||||
type Demangler func(name []string) (map[string]string, error)
|
||||
|
||||
// Demangle attempts to demangle and optionally simplify any function
|
||||
// names referenced in the profile. It works on a best-effort basis:
|
||||
// it will silently preserve the original names in case of any errors.
|
||||
func (p *Profile) Demangle(d Demangler) error {
|
||||
// Collect names to demangle.
|
||||
var names []string
|
||||
for _, fn := range p.Function {
|
||||
names = append(names, fn.SystemName)
|
||||
}
|
||||
|
||||
// Update profile with demangled names.
|
||||
demangled, err := d(names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fn := range p.Function {
|
||||
if dd, ok := demangled[fn.SystemName]; ok {
|
||||
fn.Name = dd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Empty reports whether the profile contains no samples.
|
||||
func (p *Profile) Empty() bool {
|
||||
return len(p.Sample) == 0
|
||||
}
|
||||
|
||||
// Scale multiplies all sample values in a profile by a constant.
|
||||
func (p *Profile) Scale(ratio float64) {
|
||||
if ratio == 1 {
|
||||
return
|
||||
}
|
||||
ratios := make([]float64, len(p.SampleType))
|
||||
for i := range p.SampleType {
|
||||
ratios[i] = ratio
|
||||
}
|
||||
p.ScaleN(ratios)
|
||||
}
|
||||
|
||||
// ScaleN multiplies each sample values in a sample by a different amount.
|
||||
func (p *Profile) ScaleN(ratios []float64) error {
|
||||
if len(p.SampleType) != len(ratios) {
|
||||
return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
|
||||
}
|
||||
allOnes := true
|
||||
for _, r := range ratios {
|
||||
if r != 1 {
|
||||
allOnes = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allOnes {
|
||||
return nil
|
||||
}
|
||||
for _, s := range p.Sample {
|
||||
for i, v := range s.Value {
|
||||
if ratios[i] != 1 {
|
||||
s.Value[i] = int64(float64(v) * ratios[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
356
src/internal/profile/proto.go
Normal file
356
src/internal/profile/proto.go
Normal file
@@ -0,0 +1,356 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is a simple protocol buffer encoder and decoder.
|
||||
//
|
||||
// A protocol message must implement the message interface:
|
||||
// decoder() []decoder
|
||||
// encode(*buffer)
|
||||
//
|
||||
// The decode method returns a slice indexed by field number that gives the
|
||||
// function to decode that field.
|
||||
// The encode method encodes its receiver into the given buffer.
|
||||
//
|
||||
// The two methods are simple enough to be implemented by hand rather than
|
||||
// by using a protocol compiler.
|
||||
//
|
||||
// See profile.go for examples of messages implementing this interface.
|
||||
//
|
||||
// There is no support for groups, message sets, or "has" bits.
|
||||
|
||||
package profile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
field int
|
||||
typ int
|
||||
u64 uint64
|
||||
data []byte
|
||||
tmp [16]byte
|
||||
}
|
||||
|
||||
type decoder func(*buffer, message) error
|
||||
|
||||
type message interface {
|
||||
decoder() []decoder
|
||||
encode(*buffer)
|
||||
}
|
||||
|
||||
func marshal(m message) []byte {
|
||||
var b buffer
|
||||
m.encode(&b)
|
||||
return b.data
|
||||
}
|
||||
|
||||
func encodeVarint(b *buffer, x uint64) {
|
||||
for x >= 128 {
|
||||
b.data = append(b.data, byte(x)|0x80)
|
||||
x >>= 7
|
||||
}
|
||||
b.data = append(b.data, byte(x))
|
||||
}
|
||||
|
||||
func encodeLength(b *buffer, tag int, len int) {
|
||||
encodeVarint(b, uint64(tag)<<3|2)
|
||||
encodeVarint(b, uint64(len))
|
||||
}
|
||||
|
||||
func encodeUint64(b *buffer, tag int, x uint64) {
|
||||
// append varint to b.data
|
||||
encodeVarint(b, uint64(tag)<<3|0)
|
||||
encodeVarint(b, x)
|
||||
}
|
||||
|
||||
func encodeUint64s(b *buffer, tag int, x []uint64) {
|
||||
if len(x) > 2 {
|
||||
// Use packed encoding
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
encodeVarint(b, u)
|
||||
}
|
||||
n2 := len(b.data)
|
||||
encodeLength(b, tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
encodeUint64(b, tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeUint64Opt(b *buffer, tag int, x uint64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
encodeUint64(b, tag, x)
|
||||
}
|
||||
|
||||
func encodeInt64(b *buffer, tag int, x int64) {
|
||||
u := uint64(x)
|
||||
encodeUint64(b, tag, u)
|
||||
}
|
||||
|
||||
func encodeInt64Opt(b *buffer, tag int, x int64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
encodeInt64(b, tag, x)
|
||||
}
|
||||
|
||||
func encodeInt64s(b *buffer, tag int, x []int64) {
|
||||
if len(x) > 2 {
|
||||
// Use packed encoding
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
encodeVarint(b, uint64(u))
|
||||
}
|
||||
n2 := len(b.data)
|
||||
encodeLength(b, tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
encodeInt64(b, tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeString(b *buffer, tag int, x string) {
|
||||
encodeLength(b, tag, len(x))
|
||||
b.data = append(b.data, x...)
|
||||
}
|
||||
|
||||
func encodeStrings(b *buffer, tag int, x []string) {
|
||||
for _, s := range x {
|
||||
encodeString(b, tag, s)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeBool(b *buffer, tag int, x bool) {
|
||||
if x {
|
||||
encodeUint64(b, tag, 1)
|
||||
} else {
|
||||
encodeUint64(b, tag, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeBoolOpt(b *buffer, tag int, x bool) {
|
||||
if !x {
|
||||
return
|
||||
}
|
||||
encodeBool(b, tag, x)
|
||||
}
|
||||
|
||||
func encodeMessage(b *buffer, tag int, m message) {
|
||||
n1 := len(b.data)
|
||||
m.encode(b)
|
||||
n2 := len(b.data)
|
||||
encodeLength(b, tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
}
|
||||
|
||||
func unmarshal(data []byte, m message) (err error) {
|
||||
b := buffer{data: data, typ: 2}
|
||||
return decodeMessage(&b, m)
|
||||
}
|
||||
|
||||
func le64(p []byte) uint64 {
|
||||
return uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56
|
||||
}
|
||||
|
||||
func le32(p []byte) uint32 {
|
||||
return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24
|
||||
}
|
||||
|
||||
func decodeVarint(data []byte) (uint64, []byte, error) {
|
||||
var i int
|
||||
var u uint64
|
||||
for i = 0; ; i++ {
|
||||
if i >= 10 || i >= len(data) {
|
||||
return 0, nil, errors.New("bad varint")
|
||||
}
|
||||
u |= uint64(data[i]&0x7F) << uint(7*i)
|
||||
if data[i]&0x80 == 0 {
|
||||
return u, data[i+1:], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeField(b *buffer, data []byte) ([]byte, error) {
|
||||
x, data, err := decodeVarint(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.field = int(x >> 3)
|
||||
b.typ = int(x & 7)
|
||||
b.data = nil
|
||||
b.u64 = 0
|
||||
switch b.typ {
|
||||
case 0:
|
||||
b.u64, data, err = decodeVarint(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 1:
|
||||
if len(data) < 8 {
|
||||
return nil, errors.New("not enough data")
|
||||
}
|
||||
b.u64 = le64(data[:8])
|
||||
data = data[8:]
|
||||
case 2:
|
||||
var n uint64
|
||||
n, data, err = decodeVarint(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n > uint64(len(data)) {
|
||||
return nil, errors.New("too much data")
|
||||
}
|
||||
b.data = data[:n]
|
||||
data = data[n:]
|
||||
case 5:
|
||||
if len(data) < 4 {
|
||||
return nil, errors.New("not enough data")
|
||||
}
|
||||
b.u64 = uint64(le32(data[:4]))
|
||||
data = data[4:]
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown wire type: %d", b.typ)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func checkType(b *buffer, typ int) error {
|
||||
if b.typ != typ {
|
||||
return errors.New("type mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeMessage(b *buffer, m message) error {
|
||||
if err := checkType(b, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
dec := m.decoder()
|
||||
data := b.data
|
||||
for len(data) > 0 {
|
||||
// pull varint field# + type
|
||||
var err error
|
||||
data, err = decodeField(b, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if b.field >= len(dec) || dec[b.field] == nil {
|
||||
continue
|
||||
}
|
||||
if err := dec[b.field](b, m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeInt64(b *buffer, x *int64) error {
|
||||
if err := checkType(b, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = int64(b.u64)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeInt64s(b *buffer, x *[]int64) error {
|
||||
if b.typ == 2 {
|
||||
// Packed encoding
|
||||
data := b.data
|
||||
for len(data) > 0 {
|
||||
var u uint64
|
||||
var err error
|
||||
|
||||
if u, data, err = decodeVarint(data); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = append(*x, int64(u))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var i int64
|
||||
if err := decodeInt64(b, &i); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = append(*x, i)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeUint64(b *buffer, x *uint64) error {
|
||||
if err := checkType(b, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = b.u64
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeUint64s(b *buffer, x *[]uint64) error {
|
||||
if b.typ == 2 {
|
||||
data := b.data
|
||||
// Packed encoding
|
||||
for len(data) > 0 {
|
||||
var u uint64
|
||||
var err error
|
||||
|
||||
if u, data, err = decodeVarint(data); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = append(*x, u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var u uint64
|
||||
if err := decodeUint64(b, &u); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = append(*x, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeString(b *buffer, x *string) error {
|
||||
if err := checkType(b, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = string(b.data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeStrings(b *buffer, x *[]string) error {
|
||||
var s string
|
||||
if err := decodeString(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
*x = append(*x, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeBool(b *buffer, x *bool) error {
|
||||
if err := checkType(b, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if int64(b.u64) == 0 {
|
||||
*x = false
|
||||
} else {
|
||||
*x = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
71
src/internal/profile/proto_test.go
Normal file
71
src/internal/profile/proto_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package profile
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPackedEncoding(t *testing.T) {
|
||||
|
||||
type testcase struct {
|
||||
uint64s []uint64
|
||||
int64s []int64
|
||||
encoded []byte
|
||||
}
|
||||
for i, tc := range []testcase{
|
||||
{
|
||||
[]uint64{0, 1, 10, 100, 1000, 10000},
|
||||
[]int64{1000, 0, 1000},
|
||||
[]byte{10, 8, 0, 1, 10, 100, 232, 7, 144, 78, 18, 5, 232, 7, 0, 232, 7},
|
||||
},
|
||||
{
|
||||
[]uint64{10000},
|
||||
nil,
|
||||
[]byte{8, 144, 78},
|
||||
},
|
||||
{
|
||||
nil,
|
||||
[]int64{-10000},
|
||||
[]byte{16, 240, 177, 255, 255, 255, 255, 255, 255, 255, 1},
|
||||
},
|
||||
} {
|
||||
source := &packedInts{tc.uint64s, tc.int64s}
|
||||
if got, want := marshal(source), tc.encoded; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("failed encode %d, got %v, want %v", i, got, want)
|
||||
}
|
||||
|
||||
dest := new(packedInts)
|
||||
if err := unmarshal(tc.encoded, dest); err != nil {
|
||||
t.Errorf("failed decode %d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if got, want := dest.uint64s, tc.uint64s; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("failed decode uint64s %d, got %v, want %v", i, got, want)
|
||||
}
|
||||
if got, want := dest.int64s, tc.int64s; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("failed decode int64s %d, got %v, want %v", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type packedInts struct {
|
||||
uint64s []uint64
|
||||
int64s []int64
|
||||
}
|
||||
|
||||
func (u *packedInts) decoder() []decoder {
|
||||
return []decoder{
|
||||
nil,
|
||||
func(b *buffer, m message) error { return decodeUint64s(b, &m.(*packedInts).uint64s) },
|
||||
func(b *buffer, m message) error { return decodeInt64s(b, &m.(*packedInts).int64s) },
|
||||
}
|
||||
}
|
||||
|
||||
func (u *packedInts) encode(b *buffer) {
|
||||
encodeUint64s(b, 1, u.uint64s)
|
||||
encodeInt64s(b, 2, u.int64s)
|
||||
}
|
||||
97
src/internal/profile/prune.go
Normal file
97
src/internal/profile/prune.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Implements methods to remove frames from profiles.
|
||||
|
||||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Prune removes all nodes beneath a node matching dropRx, and not
|
||||
// matching keepRx. If the root node of a Sample matches, the sample
|
||||
// will have an empty stack.
|
||||
func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) {
|
||||
prune := make(map[uint64]bool)
|
||||
pruneBeneath := make(map[uint64]bool)
|
||||
|
||||
for _, loc := range p.Location {
|
||||
var i int
|
||||
for i = len(loc.Line) - 1; i >= 0; i-- {
|
||||
if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
|
||||
funcName := fn.Name
|
||||
// Account for leading '.' on the PPC ELF v1 ABI.
|
||||
if funcName[0] == '.' {
|
||||
funcName = funcName[1:]
|
||||
}
|
||||
if dropRx.MatchString(funcName) {
|
||||
if keepRx == nil || !keepRx.MatchString(funcName) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i >= 0 {
|
||||
// Found matching entry to prune.
|
||||
pruneBeneath[loc.ID] = true
|
||||
|
||||
// Remove the matching location.
|
||||
if i == len(loc.Line)-1 {
|
||||
// Matched the top entry: prune the whole location.
|
||||
prune[loc.ID] = true
|
||||
} else {
|
||||
loc.Line = loc.Line[i+1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prune locs from each Sample
|
||||
for _, sample := range p.Sample {
|
||||
// Scan from the root to the leaves to find the prune location.
|
||||
// Do not prune frames before the first user frame, to avoid
|
||||
// pruning everything.
|
||||
foundUser := false
|
||||
for i := len(sample.Location) - 1; i >= 0; i-- {
|
||||
id := sample.Location[i].ID
|
||||
if !prune[id] && !pruneBeneath[id] {
|
||||
foundUser = true
|
||||
continue
|
||||
}
|
||||
if !foundUser {
|
||||
continue
|
||||
}
|
||||
if prune[id] {
|
||||
sample.Location = sample.Location[i+1:]
|
||||
break
|
||||
}
|
||||
if pruneBeneath[id] {
|
||||
sample.Location = sample.Location[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveUninteresting prunes and elides profiles using built-in
|
||||
// tables of uninteresting function names.
|
||||
func (p *Profile) RemoveUninteresting() error {
|
||||
var keep, drop *regexp.Regexp
|
||||
var err error
|
||||
|
||||
if p.DropFrames != "" {
|
||||
if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil {
|
||||
return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err)
|
||||
}
|
||||
if p.KeepFrames != "" {
|
||||
if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil {
|
||||
return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err)
|
||||
}
|
||||
}
|
||||
p.Prune(drop, keep)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user