package profile
import (
"fmt"
"sort"
"strconv"
"strings"
)
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 {
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 {
pm .mapMapping (src .Mapping [0 ])
}
for _ , s := range src .Sample {
if !isZeroSample (s ) {
pm .mapSample (s )
}
}
}
for _ , s := range p .Sample {
if isZeroSample (s ) {
return Merge ([]*Profile {p })
}
}
return p , nil
}
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
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 (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
}
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
}
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 )
}
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
}
func (l *Location ) key () locationKey {
key := locationKey {
addr : l .Address ,
isFolded : l .IsFolded ,
}
if l .Mapping != nil {
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
}
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 )
pm .mappings [mk ] = m
mi := mapInfo {m , 0 }
pm .mappingsByID [src .ID ] = mi
return mi
}
func (m *Mapping ) key () mappingKey {
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 :
}
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
}
func (f *Function ) key () functionKey {
return functionKey {
f .StartLine ,
f .Name ,
f .SystemName ,
f .Filename ,
}
}
type functionKey struct {
startLine int64
name, systemName, fileName string
}
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
}
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
}
func equalValueType(st1 , st2 *ValueType ) bool {
return st1 .Type == st2 .Type && st1 .Unit == st2 .Unit
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .