1
2
3
4
5 package profile
6
7 import (
8 "fmt"
9 "sort"
10 "strconv"
11 "strings"
12 )
13
14
15
16
17
18
19
20
21 func Merge(srcs []*Profile) (*Profile, error) {
22 if len(srcs) == 0 {
23 return nil, fmt.Errorf("no profiles to merge")
24 }
25 p, err := combineHeaders(srcs)
26 if err != nil {
27 return nil, err
28 }
29
30 pm := &profileMerger{
31 p: p,
32 samples: make(map[sampleKey]*Sample, len(srcs[0].Sample)),
33 locations: make(map[locationKey]*Location, len(srcs[0].Location)),
34 functions: make(map[functionKey]*Function, len(srcs[0].Function)),
35 mappings: make(map[mappingKey]*Mapping, len(srcs[0].Mapping)),
36 }
37
38 for _, src := range srcs {
39
40 pm.locationsByID = make(map[uint64]*Location, len(src.Location))
41 pm.functionsByID = make(map[uint64]*Function, len(src.Function))
42 pm.mappingsByID = make(map[uint64]mapInfo, len(src.Mapping))
43
44 if len(pm.mappings) == 0 && len(src.Mapping) > 0 {
45
46
47
48
49 pm.mapMapping(src.Mapping[0])
50 }
51
52 for _, s := range src.Sample {
53 if !isZeroSample(s) {
54 pm.mapSample(s)
55 }
56 }
57 }
58
59 for _, s := range p.Sample {
60 if isZeroSample(s) {
61
62
63 return Merge([]*Profile{p})
64 }
65 }
66
67 return p, nil
68 }
69
70
71
72
73 func (p *Profile) Normalize(pb *Profile) error {
74
75 if err := p.compatible(pb); err != nil {
76 return err
77 }
78
79 baseVals := make([]int64, len(p.SampleType))
80 for _, s := range pb.Sample {
81 for i, v := range s.Value {
82 baseVals[i] += v
83 }
84 }
85
86 srcVals := make([]int64, len(p.SampleType))
87 for _, s := range p.Sample {
88 for i, v := range s.Value {
89 srcVals[i] += v
90 }
91 }
92
93 normScale := make([]float64, len(baseVals))
94 for i := range baseVals {
95 if srcVals[i] == 0 {
96 normScale[i] = 0.0
97 } else {
98 normScale[i] = float64(baseVals[i]) / float64(srcVals[i])
99 }
100 }
101 p.ScaleN(normScale)
102 return nil
103 }
104
105 func isZeroSample(s *Sample) bool {
106 for _, v := range s.Value {
107 if v != 0 {
108 return false
109 }
110 }
111 return true
112 }
113
114 type profileMerger struct {
115 p *Profile
116
117
118 locationsByID map[uint64]*Location
119 functionsByID map[uint64]*Function
120 mappingsByID map[uint64]mapInfo
121
122
123 samples map[sampleKey]*Sample
124 locations map[locationKey]*Location
125 functions map[functionKey]*Function
126 mappings map[mappingKey]*Mapping
127 }
128
129 type mapInfo struct {
130 m *Mapping
131 offset int64
132 }
133
134 func (pm *profileMerger) mapSample(src *Sample) *Sample {
135 s := &Sample{
136 Location: make([]*Location, len(src.Location)),
137 Value: make([]int64, len(src.Value)),
138 Label: make(map[string][]string, len(src.Label)),
139 NumLabel: make(map[string][]int64, len(src.NumLabel)),
140 NumUnit: make(map[string][]string, len(src.NumLabel)),
141 }
142 for i, l := range src.Location {
143 s.Location[i] = pm.mapLocation(l)
144 }
145 for k, v := range src.Label {
146 vv := make([]string, len(v))
147 copy(vv, v)
148 s.Label[k] = vv
149 }
150 for k, v := range src.NumLabel {
151 u := src.NumUnit[k]
152 vv := make([]int64, len(v))
153 uu := make([]string, len(u))
154 copy(vv, v)
155 copy(uu, u)
156 s.NumLabel[k] = vv
157 s.NumUnit[k] = uu
158 }
159
160
161
162 k := s.key()
163 if ss, ok := pm.samples[k]; ok {
164 for i, v := range src.Value {
165 ss.Value[i] += v
166 }
167 return ss
168 }
169 copy(s.Value, src.Value)
170 pm.samples[k] = s
171 pm.p.Sample = append(pm.p.Sample, s)
172 return s
173 }
174
175
176 func (sample *Sample) key() sampleKey {
177 ids := make([]string, len(sample.Location))
178 for i, l := range sample.Location {
179 ids[i] = strconv.FormatUint(l.ID, 16)
180 }
181
182 labels := make([]string, 0, len(sample.Label))
183 for k, v := range sample.Label {
184 labels = append(labels, fmt.Sprintf("%q%q", k, v))
185 }
186 sort.Strings(labels)
187
188 numlabels := make([]string, 0, len(sample.NumLabel))
189 for k, v := range sample.NumLabel {
190 numlabels = append(numlabels, fmt.Sprintf("%q%x%x", k, v, sample.NumUnit[k]))
191 }
192 sort.Strings(numlabels)
193
194 return sampleKey{
195 strings.Join(ids, "|"),
196 strings.Join(labels, ""),
197 strings.Join(numlabels, ""),
198 }
199 }
200
201 type sampleKey struct {
202 locations string
203 labels string
204 numlabels string
205 }
206
207 func (pm *profileMerger) mapLocation(src *Location) *Location {
208 if src == nil {
209 return nil
210 }
211
212 if l, ok := pm.locationsByID[src.ID]; ok {
213 pm.locationsByID[src.ID] = l
214 return l
215 }
216
217 mi := pm.mapMapping(src.Mapping)
218 l := &Location{
219 ID: uint64(len(pm.p.Location) + 1),
220 Mapping: mi.m,
221 Address: uint64(int64(src.Address) + mi.offset),
222 Line: make([]Line, len(src.Line)),
223 IsFolded: src.IsFolded,
224 }
225 for i, ln := range src.Line {
226 l.Line[i] = pm.mapLine(ln)
227 }
228
229
230 k := l.key()
231 if ll, ok := pm.locations[k]; ok {
232 pm.locationsByID[src.ID] = ll
233 return ll
234 }
235 pm.locationsByID[src.ID] = l
236 pm.locations[k] = l
237 pm.p.Location = append(pm.p.Location, l)
238 return l
239 }
240
241
242 func (l *Location) key() locationKey {
243 key := locationKey{
244 addr: l.Address,
245 isFolded: l.IsFolded,
246 }
247 if l.Mapping != nil {
248
249 key.addr -= l.Mapping.Start
250 key.mappingID = l.Mapping.ID
251 }
252 lines := make([]string, len(l.Line)*2)
253 for i, line := range l.Line {
254 if line.Function != nil {
255 lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
256 }
257 lines[i*2+1] = strconv.FormatInt(line.Line, 16)
258 }
259 key.lines = strings.Join(lines, "|")
260 return key
261 }
262
263 type locationKey struct {
264 addr, mappingID uint64
265 lines string
266 isFolded bool
267 }
268
269 func (pm *profileMerger) mapMapping(src *Mapping) mapInfo {
270 if src == nil {
271 return mapInfo{}
272 }
273
274 if mi, ok := pm.mappingsByID[src.ID]; ok {
275 return mi
276 }
277
278
279 mk := src.key()
280 if m, ok := pm.mappings[mk]; ok {
281 mi := mapInfo{m, int64(m.Start) - int64(src.Start)}
282 pm.mappingsByID[src.ID] = mi
283 return mi
284 }
285 m := &Mapping{
286 ID: uint64(len(pm.p.Mapping) + 1),
287 Start: src.Start,
288 Limit: src.Limit,
289 Offset: src.Offset,
290 File: src.File,
291 BuildID: src.BuildID,
292 HasFunctions: src.HasFunctions,
293 HasFilenames: src.HasFilenames,
294 HasLineNumbers: src.HasLineNumbers,
295 HasInlineFrames: src.HasInlineFrames,
296 }
297 pm.p.Mapping = append(pm.p.Mapping, m)
298
299
300 pm.mappings[mk] = m
301 mi := mapInfo{m, 0}
302 pm.mappingsByID[src.ID] = mi
303 return mi
304 }
305
306
307
308 func (m *Mapping) key() mappingKey {
309
310
311 const mapsizeRounding = 0x1000
312
313 size := m.Limit - m.Start
314 size = size + mapsizeRounding - 1
315 size = size - (size % mapsizeRounding)
316 key := mappingKey{
317 size: size,
318 offset: m.Offset,
319 }
320
321 switch {
322 case m.BuildID != "":
323 key.buildIDOrFile = m.BuildID
324 case m.File != "":
325 key.buildIDOrFile = m.File
326 default:
327
328
329
330 }
331 return key
332 }
333
334 type mappingKey struct {
335 size, offset uint64
336 buildIDOrFile string
337 }
338
339 func (pm *profileMerger) mapLine(src Line) Line {
340 ln := Line{
341 Function: pm.mapFunction(src.Function),
342 Line: src.Line,
343 }
344 return ln
345 }
346
347 func (pm *profileMerger) mapFunction(src *Function) *Function {
348 if src == nil {
349 return nil
350 }
351 if f, ok := pm.functionsByID[src.ID]; ok {
352 return f
353 }
354 k := src.key()
355 if f, ok := pm.functions[k]; ok {
356 pm.functionsByID[src.ID] = f
357 return f
358 }
359 f := &Function{
360 ID: uint64(len(pm.p.Function) + 1),
361 Name: src.Name,
362 SystemName: src.SystemName,
363 Filename: src.Filename,
364 StartLine: src.StartLine,
365 }
366 pm.functions[k] = f
367 pm.functionsByID[src.ID] = f
368 pm.p.Function = append(pm.p.Function, f)
369 return f
370 }
371
372
373 func (f *Function) key() functionKey {
374 return functionKey{
375 f.StartLine,
376 f.Name,
377 f.SystemName,
378 f.Filename,
379 }
380 }
381
382 type functionKey struct {
383 startLine int64
384 name, systemName, fileName string
385 }
386
387
388
389 func combineHeaders(srcs []*Profile) (*Profile, error) {
390 for _, s := range srcs[1:] {
391 if err := srcs[0].compatible(s); err != nil {
392 return nil, err
393 }
394 }
395
396 var timeNanos, durationNanos, period int64
397 var comments []string
398 seenComments := map[string]bool{}
399 var defaultSampleType string
400 for _, s := range srcs {
401 if timeNanos == 0 || s.TimeNanos < timeNanos {
402 timeNanos = s.TimeNanos
403 }
404 durationNanos += s.DurationNanos
405 if period == 0 || period < s.Period {
406 period = s.Period
407 }
408 for _, c := range s.Comments {
409 if seen := seenComments[c]; !seen {
410 comments = append(comments, c)
411 seenComments[c] = true
412 }
413 }
414 if defaultSampleType == "" {
415 defaultSampleType = s.DefaultSampleType
416 }
417 }
418
419 p := &Profile{
420 SampleType: make([]*ValueType, len(srcs[0].SampleType)),
421
422 DropFrames: srcs[0].DropFrames,
423 KeepFrames: srcs[0].KeepFrames,
424
425 TimeNanos: timeNanos,
426 DurationNanos: durationNanos,
427 PeriodType: srcs[0].PeriodType,
428 Period: period,
429
430 Comments: comments,
431 DefaultSampleType: defaultSampleType,
432 }
433 copy(p.SampleType, srcs[0].SampleType)
434 return p, nil
435 }
436
437
438
439
440 func (p *Profile) compatible(pb *Profile) error {
441 if !equalValueType(p.PeriodType, pb.PeriodType) {
442 return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
443 }
444
445 if len(p.SampleType) != len(pb.SampleType) {
446 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
447 }
448
449 for i := range p.SampleType {
450 if !equalValueType(p.SampleType[i], pb.SampleType[i]) {
451 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
452 }
453 }
454 return nil
455 }
456
457
458
459 func equalValueType(st1, st2 *ValueType) bool {
460 return st1.Type == st2.Type && st1.Unit == st2.Unit
461 }
462
View as plain text