1
// Copyright 2014 Google Inc. All Rights Reserved.
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
24
// Compact performs garbage collection on a profile to remove any
25
// unreferenced fields. This is useful to reduce the size of a profile
26
// after samples or locations have been removed.
27
func (p *Profile) Compact() *Profile {
28
p, _ = Merge([]*Profile{p})
32
// Merge merges all the profiles in profs into a single Profile.
33
// Returns a new profile independent of the input profiles. The merged
34
// profile is compacted to eliminate unused samples, locations,
35
// functions and mappings. Profiles must have identical profile sample
36
// and period types or the merge will fail. profile.Period of the
37
// resulting profile will be the maximum of all profiles, and
38
// profile.TimeNanos will be the earliest nonzero one. Merges are
39
// associative with the caveat of the first profile having some
40
// specialization in how headers are combined. There may be other
41
// subtleties now or in the future regarding associativity.
42
func Merge(srcs []*Profile) (*Profile, error) {
44
return nil, fmt.Errorf("no profiles to merge")
46
p, err := combineHeaders(srcs)
53
samples: make(map[sampleKey]*Sample, len(srcs[0].Sample)),
54
locations: make(map[locationKey]*Location, len(srcs[0].Location)),
55
functions: make(map[functionKey]*Function, len(srcs[0].Function)),
56
mappings: make(map[mappingKey]*Mapping, len(srcs[0].Mapping)),
59
for _, src := range srcs {
60
// Clear the profile-specific hash tables
61
pm.locationsByID = make(map[uint64]*Location, len(src.Location))
62
pm.functionsByID = make(map[uint64]*Function, len(src.Function))
63
pm.mappingsByID = make(map[uint64]mapInfo, len(src.Mapping))
65
if len(pm.mappings) == 0 && len(src.Mapping) > 0 {
66
// The Mapping list has the property that the first mapping
67
// represents the main binary. Take the first Mapping we see,
68
// otherwise the operations below will add mappings in an
70
pm.mapMapping(src.Mapping[0])
73
for _, s := range src.Sample {
80
for _, s := range p.Sample {
82
// If there are any zero samples, re-merge the profile to GC
84
return Merge([]*Profile{p})
91
// Normalize normalizes the source profile by multiplying each value in profile by the
92
// ratio of the sum of the base profile's values of that sample type to the sum of the
93
// source profile's value of that sample type.
94
func (p *Profile) Normalize(pb *Profile) error {
96
if err := p.compatible(pb); err != nil {
100
baseVals := make([]int64, len(p.SampleType))
101
for _, s := range pb.Sample {
102
for i, v := range s.Value {
107
srcVals := make([]int64, len(p.SampleType))
108
for _, s := range p.Sample {
109
for i, v := range s.Value {
114
normScale := make([]float64, len(baseVals))
115
for i := range baseVals {
119
normScale[i] = float64(baseVals[i]) / float64(srcVals[i])
126
func isZeroSample(s *Sample) bool {
127
for _, v := range s.Value {
135
type profileMerger struct {
138
// Memoization tables within a profile.
139
locationsByID map[uint64]*Location
140
functionsByID map[uint64]*Function
141
mappingsByID map[uint64]mapInfo
143
// Memoization tables for profile entities.
144
samples map[sampleKey]*Sample
145
locations map[locationKey]*Location
146
functions map[functionKey]*Function
147
mappings map[mappingKey]*Mapping
155
func (pm *profileMerger) mapSample(src *Sample) *Sample {
157
Location: make([]*Location, len(src.Location)),
158
Value: make([]int64, len(src.Value)),
159
Label: make(map[string][]string, len(src.Label)),
160
NumLabel: make(map[string][]int64, len(src.NumLabel)),
161
NumUnit: make(map[string][]string, len(src.NumLabel)),
163
for i, l := range src.Location {
164
s.Location[i] = pm.mapLocation(l)
166
for k, v := range src.Label {
167
vv := make([]string, len(v))
171
for k, v := range src.NumLabel {
173
vv := make([]int64, len(v))
174
uu := make([]string, len(u))
180
// Check memoization table. Must be done on the remapped location to
181
// account for the remapped mapping. Add current values to the
184
if ss, ok := pm.samples[k]; ok {
185
for i, v := range src.Value {
190
copy(s.Value, src.Value)
192
pm.p.Sample = append(pm.p.Sample, s)
196
// key generates sampleKey to be used as a key for maps.
197
func (sample *Sample) key() sampleKey {
198
ids := make([]string, len(sample.Location))
199
for i, l := range sample.Location {
200
ids[i] = strconv.FormatUint(l.ID, 16)
203
labels := make([]string, 0, len(sample.Label))
204
for k, v := range sample.Label {
205
labels = append(labels, fmt.Sprintf("%q%q", k, v))
209
numlabels := make([]string, 0, len(sample.NumLabel))
210
for k, v := range sample.NumLabel {
211
numlabels = append(numlabels, fmt.Sprintf("%q%x%x", k, v, sample.NumUnit[k]))
213
sort.Strings(numlabels)
216
strings.Join(ids, "|"),
217
strings.Join(labels, ""),
218
strings.Join(numlabels, ""),
222
type sampleKey struct {
228
func (pm *profileMerger) mapLocation(src *Location) *Location {
233
if l, ok := pm.locationsByID[src.ID]; ok {
237
mi := pm.mapMapping(src.Mapping)
239
ID: uint64(len(pm.p.Location) + 1),
241
Address: uint64(int64(src.Address) + mi.offset),
242
Line: make([]Line, len(src.Line)),
243
IsFolded: src.IsFolded,
245
for i, ln := range src.Line {
246
l.Line[i] = pm.mapLine(ln)
248
// Check memoization table. Must be done on the remapped location to
249
// account for the remapped mapping ID.
251
if ll, ok := pm.locations[k]; ok {
252
pm.locationsByID[src.ID] = ll
255
pm.locationsByID[src.ID] = l
257
pm.p.Location = append(pm.p.Location, l)
261
// key generates locationKey to be used as a key for maps.
262
func (l *Location) key() locationKey {
265
isFolded: l.IsFolded,
267
if l.Mapping != nil {
268
// Normalizes address to handle address space randomization.
269
key.addr -= l.Mapping.Start
270
key.mappingID = l.Mapping.ID
272
lines := make([]string, len(l.Line)*2)
273
for i, line := range l.Line {
274
if line.Function != nil {
275
lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
277
lines[i*2+1] = strconv.FormatInt(line.Line, 16)
279
key.lines = strings.Join(lines, "|")
283
type locationKey struct {
284
addr, mappingID uint64
289
func (pm *profileMerger) mapMapping(src *Mapping) mapInfo {
294
if mi, ok := pm.mappingsByID[src.ID]; ok {
298
// Check memoization tables.
300
if m, ok := pm.mappings[mk]; ok {
301
mi := mapInfo{m, int64(m.Start) - int64(src.Start)}
302
pm.mappingsByID[src.ID] = mi
306
ID: uint64(len(pm.p.Mapping) + 1),
311
BuildID: src.BuildID,
312
HasFunctions: src.HasFunctions,
313
HasFilenames: src.HasFilenames,
314
HasLineNumbers: src.HasLineNumbers,
315
HasInlineFrames: src.HasInlineFrames,
317
pm.p.Mapping = append(pm.p.Mapping, m)
319
// Update memoization tables.
322
pm.mappingsByID[src.ID] = mi
326
// key generates encoded strings of Mapping to be used as a key for
328
func (m *Mapping) key() mappingKey {
329
// Normalize addresses to handle address space randomization.
330
// Round up to next 4K boundary to avoid minor discrepancies.
331
const mapsizeRounding = 0x1000
333
size := m.Limit - m.Start
334
size = size + mapsizeRounding - 1
335
size = size - (size % mapsizeRounding)
342
case m.BuildID != "":
343
key.buildIDOrFile = m.BuildID
345
key.buildIDOrFile = m.File
347
// A mapping containing neither build ID nor file name is a fake mapping. A
348
// key with empty buildIDOrFile is used for fake mappings so that they are
349
// treated as the same mapping during merging.
354
type mappingKey struct {
359
func (pm *profileMerger) mapLine(src Line) Line {
361
Function: pm.mapFunction(src.Function),
367
func (pm *profileMerger) mapFunction(src *Function) *Function {
371
if f, ok := pm.functionsByID[src.ID]; ok {
375
if f, ok := pm.functions[k]; ok {
376
pm.functionsByID[src.ID] = f
380
ID: uint64(len(pm.p.Function) + 1),
382
SystemName: src.SystemName,
383
Filename: src.Filename,
384
StartLine: src.StartLine,
387
pm.functionsByID[src.ID] = f
388
pm.p.Function = append(pm.p.Function, f)
392
// key generates a struct to be used as a key for maps.
393
func (f *Function) key() functionKey {
402
type functionKey struct {
404
name, systemName, fileName string
407
// combineHeaders checks that all profiles can be merged and returns
408
// their combined profile.
409
func combineHeaders(srcs []*Profile) (*Profile, error) {
410
for _, s := range srcs[1:] {
411
if err := srcs[0].compatible(s); err != nil {
416
var timeNanos, durationNanos, period int64
417
var comments []string
418
seenComments := map[string]bool{}
419
var defaultSampleType string
420
for _, s := range srcs {
421
if timeNanos == 0 || s.TimeNanos < timeNanos {
422
timeNanos = s.TimeNanos
424
durationNanos += s.DurationNanos
425
if period == 0 || period < s.Period {
428
for _, c := range s.Comments {
429
if seen := seenComments[c]; !seen {
430
comments = append(comments, c)
431
seenComments[c] = true
434
if defaultSampleType == "" {
435
defaultSampleType = s.DefaultSampleType
440
SampleType: make([]*ValueType, len(srcs[0].SampleType)),
442
DropFrames: srcs[0].DropFrames,
443
KeepFrames: srcs[0].KeepFrames,
445
TimeNanos: timeNanos,
446
DurationNanos: durationNanos,
447
PeriodType: srcs[0].PeriodType,
451
DefaultSampleType: defaultSampleType,
453
copy(p.SampleType, srcs[0].SampleType)
457
// compatible determines if two profiles can be compared/merged.
458
// returns nil if the profiles are compatible; otherwise an error with
459
// details on the incompatibility.
460
func (p *Profile) compatible(pb *Profile) error {
461
if !equalValueType(p.PeriodType, pb.PeriodType) {
462
return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
465
if len(p.SampleType) != len(pb.SampleType) {
466
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
469
for i := range p.SampleType {
470
if !equalValueType(p.SampleType[i], pb.SampleType[i]) {
471
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
477
// equalValueType returns true if the two value types are semantically
478
// equal. It ignores the internal fields used during encode/decode.
479
func equalValueType(st1, st2 *ValueType) bool {
480
return st1.Type == st2.Type && st1.Unit == st2.Unit