package profile
import (
)
func ( []*Profile) (*Profile, error) {
if len() == 0 {
return nil, fmt.Errorf("no profiles to merge")
}
, := combineHeaders()
if != nil {
return nil,
}
:= &profileMerger{
p: ,
samples: make(map[sampleKey]*Sample, len([0].Sample)),
locations: make(map[locationKey]*Location, len([0].Location)),
functions: make(map[functionKey]*Function, len([0].Function)),
mappings: make(map[mappingKey]*Mapping, len([0].Mapping)),
}
for , := range {
.locationsByID = make(map[uint64]*Location, len(.Location))
.functionsByID = make(map[uint64]*Function, len(.Function))
.mappingsByID = make(map[uint64]mapInfo, len(.Mapping))
if len(.mappings) == 0 && len(.Mapping) > 0 {
.mapMapping(.Mapping[0])
}
for , := range .Sample {
if !isZeroSample() {
.mapSample()
}
}
}
for , := range .Sample {
if isZeroSample() {
return ([]*Profile{})
}
}
return , nil
}
func ( *Profile) ( *Profile) error {
if := .compatible(); != nil {
return
}
:= make([]int64, len(.SampleType))
for , := range .Sample {
for , := range .Value {
[] +=
}
}
:= make([]int64, len(.SampleType))
for , := range .Sample {
for , := range .Value {
[] +=
}
}
:= make([]float64, len())
for := range {
if [] == 0 {
[] = 0.0
} else {
[] = float64([]) / float64([])
}
}
.ScaleN()
return nil
}
func ( *Sample) bool {
for , := range .Value {
if != 0 {
return false
}
}
return true
}
type profileMerger struct {
p *Profile
locationsByID map[uint64]*Location
functionsByID map[uint64]*Function
mappingsByID map[uint64]mapInfo
samples map[sampleKey]*Sample
locations map[locationKey]*Location
functions map[functionKey]*Function
mappings map[mappingKey]*Mapping
}
type mapInfo struct {
m *Mapping
offset int64
}
func ( *profileMerger) ( *Sample) *Sample {
:= &Sample{
Location: make([]*Location, len(.Location)),
Value: make([]int64, len(.Value)),
Label: make(map[string][]string, len(.Label)),
NumLabel: make(map[string][]int64, len(.NumLabel)),
NumUnit: make(map[string][]string, len(.NumLabel)),
}
for , := range .Location {
.Location[] = .mapLocation()
}
for , := range .Label {
:= make([]string, len())
copy(, )
.Label[] =
}
for , := range .NumLabel {
:= .NumUnit[]
:= make([]int64, len())
:= make([]string, len())
copy(, )
copy(, )
.NumLabel[] =
.NumUnit[] =
}
:= .key()
if , := .samples[]; {
for , := range .Value {
.Value[] +=
}
return
}
copy(.Value, .Value)
.samples[] =
.p.Sample = append(.p.Sample, )
return
}
func ( *Sample) () sampleKey {
:= make([]string, len(.Location))
for , := range .Location {
[] = strconv.FormatUint(.ID, 16)
}
:= make([]string, 0, len(.Label))
for , := range .Label {
= append(, fmt.Sprintf("%q%q", , ))
}
sort.Strings()
:= make([]string, 0, len(.NumLabel))
for , := range .NumLabel {
= append(, fmt.Sprintf("%q%x%x", , , .NumUnit[]))
}
sort.Strings()
return sampleKey{
strings.Join(, "|"),
strings.Join(, ""),
strings.Join(, ""),
}
}
type sampleKey struct {
locations string
labels string
numlabels string
}
func ( *profileMerger) ( *Location) *Location {
if == nil {
return nil
}
if , := .locationsByID[.ID]; {
.locationsByID[.ID] =
return
}
:= .mapMapping(.Mapping)
:= &Location{
ID: uint64(len(.p.Location) + 1),
Mapping: .m,
Address: uint64(int64(.Address) + .offset),
Line: make([]Line, len(.Line)),
IsFolded: .IsFolded,
}
for , := range .Line {
.Line[] = .mapLine()
}
:= .key()
if , := .locations[]; {
.locationsByID[.ID] =
return
}
.locationsByID[.ID] =
.locations[] =
.p.Location = append(.p.Location, )
return
}
func ( *Location) () locationKey {
:= locationKey{
addr: .Address,
isFolded: .IsFolded,
}
if .Mapping != nil {
.addr -= .Mapping.Start
.mappingID = .Mapping.ID
}
:= make([]string, len(.Line)*2)
for , := range .Line {
if .Function != nil {
[*2] = strconv.FormatUint(.Function.ID, 16)
}
[*2+1] = strconv.FormatInt(.Line, 16)
}
.lines = strings.Join(, "|")
return
}
type locationKey struct {
addr, mappingID uint64
lines string
isFolded bool
}
func ( *profileMerger) ( *Mapping) mapInfo {
if == nil {
return mapInfo{}
}
if , := .mappingsByID[.ID]; {
return
}
:= .key()
if , := .mappings[]; {
:= mapInfo{, int64(.Start) - int64(.Start)}
.mappingsByID[.ID] =
return
}
:= &Mapping{
ID: uint64(len(.p.Mapping) + 1),
Start: .Start,
Limit: .Limit,
Offset: .Offset,
File: .File,
BuildID: .BuildID,
HasFunctions: .HasFunctions,
HasFilenames: .HasFilenames,
HasLineNumbers: .HasLineNumbers,
HasInlineFrames: .HasInlineFrames,
}
.p.Mapping = append(.p.Mapping, )
.mappings[] =
:= mapInfo{, 0}
.mappingsByID[.ID] =
return
}
func ( *Mapping) () mappingKey {
const = 0x1000
:= .Limit - .Start
= + - 1
= - ( % )
:= mappingKey{
size: ,
offset: .Offset,
}
switch {
case .BuildID != "":
.buildIDOrFile = .BuildID
case .File != "":
.buildIDOrFile = .File
default:
}
return
}
type mappingKey struct {
size, offset uint64
buildIDOrFile string
}
func ( *profileMerger) ( Line) Line {
:= Line{
Function: .mapFunction(.Function),
Line: .Line,
}
return
}
func ( *profileMerger) ( *Function) *Function {
if == nil {
return nil
}
if , := .functionsByID[.ID]; {
return
}
:= .key()
if , := .functions[]; {
.functionsByID[.ID] =
return
}
:= &Function{
ID: uint64(len(.p.Function) + 1),
Name: .Name,
SystemName: .SystemName,
Filename: .Filename,
StartLine: .StartLine,
}
.functions[] =
.functionsByID[.ID] =
.p.Function = append(.p.Function, )
return
}
func ( *Function) () functionKey {
return functionKey{
.StartLine,
.Name,
.SystemName,
.Filename,
}
}
type functionKey struct {
startLine int64
name, systemName, fileName string
}
func ( []*Profile) (*Profile, error) {
for , := range [1:] {
if := [0].compatible(); != nil {
return nil,
}
}
var , , int64
var []string
:= map[string]bool{}
var string
for , := range {
if == 0 || .TimeNanos < {
= .TimeNanos
}
+= .DurationNanos
if == 0 || < .Period {
= .Period
}
for , := range .Comments {
if := []; ! {
= append(, )
[] = true
}
}
if == "" {
= .DefaultSampleType
}
}
:= &Profile{
SampleType: make([]*ValueType, len([0].SampleType)),
DropFrames: [0].DropFrames,
KeepFrames: [0].KeepFrames,
TimeNanos: ,
DurationNanos: ,
PeriodType: [0].PeriodType,
Period: ,
Comments: ,
DefaultSampleType: ,
}
copy(.SampleType, [0].SampleType)
return , nil
}
func ( *Profile) ( *Profile) error {
if !equalValueType(.PeriodType, .PeriodType) {
return fmt.Errorf("incompatible period types %v and %v", .PeriodType, .PeriodType)
}
if len(.SampleType) != len(.SampleType) {
return fmt.Errorf("incompatible sample types %v and %v", .SampleType, .SampleType)
}
for := range .SampleType {
if !equalValueType(.SampleType[], .SampleType[]) {
return fmt.Errorf("incompatible sample types %v and %v", .SampleType, .SampleType)
}
}
return nil
}
func (, *ValueType) bool {
return .Type == .Type && .Unit == .Unit
}