1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package profile
16
17 import (
18 "encoding/binary"
19 "fmt"
20 "slices"
21 "sort"
22 "strconv"
23 "strings"
24 )
25
26
27
28
29 func (p *Profile) Compact() *Profile {
30 p, _ = Merge([]*Profile{p})
31 return p
32 }
33
34
35
36
37
38
39
40
41
42
43
44 func Merge(srcs []*Profile) (*Profile, error) {
45 if len(srcs) == 0 {
46 return nil, fmt.Errorf("no profiles to merge")
47 }
48 p, err := combineHeaders(srcs)
49 if err != nil {
50 return nil, err
51 }
52
53 pm := &profileMerger{
54 p: p,
55 samples: make(map[sampleKey]*Sample, len(srcs[0].Sample)),
56 locations: make(map[locationKey]*Location, len(srcs[0].Location)),
57 functions: make(map[functionKey]*Function, len(srcs[0].Function)),
58 mappings: make(map[mappingKey]*Mapping, len(srcs[0].Mapping)),
59 }
60
61 for _, src := range srcs {
62
63 pm.locationsByID = makeLocationIDMap(len(src.Location))
64 pm.functionsByID = make(map[uint64]*Function, len(src.Function))
65 pm.mappingsByID = make(map[uint64]mapInfo, len(src.Mapping))
66
67 if len(pm.mappings) == 0 && len(src.Mapping) > 0 {
68
69
70
71
72 pm.mapMapping(src.Mapping[0])
73 }
74
75 for _, s := range src.Sample {
76 if !isZeroSample(s) {
77 pm.mapSample(s)
78 }
79 }
80 }
81
82 if slices.ContainsFunc(p.Sample, isZeroSample) {
83
84
85 return Merge([]*Profile{p})
86 }
87
88 return p, nil
89 }
90
91
92
93
94 func (p *Profile) Normalize(pb *Profile) error {
95
96 if err := p.compatible(pb); err != nil {
97 return err
98 }
99
100 baseVals := make([]int64, len(p.SampleType))
101 for _, s := range pb.Sample {
102 for i, v := range s.Value {
103 baseVals[i] += v
104 }
105 }
106
107 srcVals := make([]int64, len(p.SampleType))
108 for _, s := range p.Sample {
109 for i, v := range s.Value {
110 srcVals[i] += v
111 }
112 }
113
114 normScale := make([]float64, len(baseVals))
115 for i := range baseVals {
116 if srcVals[i] == 0 {
117 normScale[i] = 0.0
118 } else {
119 normScale[i] = float64(baseVals[i]) / float64(srcVals[i])
120 }
121 }
122 p.ScaleN(normScale)
123 return nil
124 }
125
126 func isZeroSample(s *Sample) bool {
127 for _, v := range s.Value {
128 if v != 0 {
129 return false
130 }
131 }
132 return true
133 }
134
135 type profileMerger struct {
136 p *Profile
137
138
139 locationsByID locationIDMap
140 functionsByID map[uint64]*Function
141 mappingsByID map[uint64]mapInfo
142
143
144 samples map[sampleKey]*Sample
145 locations map[locationKey]*Location
146 functions map[functionKey]*Function
147 mappings map[mappingKey]*Mapping
148 }
149
150 type mapInfo struct {
151 m *Mapping
152 offset int64
153 }
154
155 func (pm *profileMerger) mapSample(src *Sample) *Sample {
156
157 k := pm.sampleKey(src)
158 if ss, ok := pm.samples[k]; ok {
159 for i, v := range src.Value {
160 ss.Value[i] += v
161 }
162 return ss
163 }
164
165
166 s := &Sample{
167 Location: make([]*Location, len(src.Location)),
168 Value: make([]int64, len(src.Value)),
169 Label: make(map[string][]string, len(src.Label)),
170 NumLabel: make(map[string][]int64, len(src.NumLabel)),
171 NumUnit: make(map[string][]string, len(src.NumLabel)),
172 }
173 for i, l := range src.Location {
174 s.Location[i] = pm.mapLocation(l)
175 }
176 for k, v := range src.Label {
177 vv := make([]string, len(v))
178 copy(vv, v)
179 s.Label[k] = vv
180 }
181 for k, v := range src.NumLabel {
182 u := src.NumUnit[k]
183 vv := make([]int64, len(v))
184 uu := make([]string, len(u))
185 copy(vv, v)
186 copy(uu, u)
187 s.NumLabel[k] = vv
188 s.NumUnit[k] = uu
189 }
190 copy(s.Value, src.Value)
191 pm.samples[k] = s
192 pm.p.Sample = append(pm.p.Sample, s)
193 return s
194 }
195
196 func (pm *profileMerger) sampleKey(sample *Sample) sampleKey {
197
198 var buf strings.Builder
199 buf.Grow(64)
200
201
202 putNumber := func(v uint64) {
203 var num [binary.MaxVarintLen64]byte
204 n := binary.PutUvarint(num[:], v)
205 buf.Write(num[:n])
206 }
207
208
209 putDelimitedString := func(s string) {
210 putNumber(uint64(len(s)))
211 buf.WriteString(s)
212 }
213
214 for _, l := range sample.Location {
215
216 if loc := pm.mapLocation(l); loc != nil {
217 putNumber(loc.ID)
218 }
219 }
220 putNumber(0)
221
222 for _, l := range sortedKeys1(sample.Label) {
223 putDelimitedString(l)
224 values := sample.Label[l]
225 putNumber(uint64(len(values)))
226 for _, v := range values {
227 putDelimitedString(v)
228 }
229 }
230
231 for _, l := range sortedKeys2(sample.NumLabel) {
232 putDelimitedString(l)
233 values := sample.NumLabel[l]
234 putNumber(uint64(len(values)))
235 for _, v := range values {
236 putNumber(uint64(v))
237 }
238 units := sample.NumUnit[l]
239 putNumber(uint64(len(units)))
240 for _, v := range units {
241 putDelimitedString(v)
242 }
243 }
244
245 return sampleKey(buf.String())
246 }
247
248 type sampleKey string
249
250
251
252
253
254
255 func sortedKeys1(m map[string][]string) []string {
256 if len(m) == 0 {
257 return nil
258 }
259 keys := make([]string, 0, len(m))
260 for k := range m {
261 keys = append(keys, k)
262 }
263 sort.Strings(keys)
264 return keys
265 }
266
267
268
269
270
271
272 func sortedKeys2(m map[string][]int64) []string {
273 if len(m) == 0 {
274 return nil
275 }
276 keys := make([]string, 0, len(m))
277 for k := range m {
278 keys = append(keys, k)
279 }
280 sort.Strings(keys)
281 return keys
282 }
283
284 func (pm *profileMerger) mapLocation(src *Location) *Location {
285 if src == nil {
286 return nil
287 }
288
289 if l := pm.locationsByID.get(src.ID); l != nil {
290 return l
291 }
292
293 mi := pm.mapMapping(src.Mapping)
294 l := &Location{
295 ID: uint64(len(pm.p.Location) + 1),
296 Mapping: mi.m,
297 Address: uint64(int64(src.Address) + mi.offset),
298 Line: make([]Line, len(src.Line)),
299 IsFolded: src.IsFolded,
300 }
301 for i, ln := range src.Line {
302 l.Line[i] = pm.mapLine(ln)
303 }
304
305
306 k := l.key()
307 if ll, ok := pm.locations[k]; ok {
308 pm.locationsByID.set(src.ID, ll)
309 return ll
310 }
311 pm.locationsByID.set(src.ID, l)
312 pm.locations[k] = l
313 pm.p.Location = append(pm.p.Location, l)
314 return l
315 }
316
317
318 func (l *Location) key() locationKey {
319 key := locationKey{
320 addr: l.Address,
321 isFolded: l.IsFolded,
322 }
323 if l.Mapping != nil {
324
325 key.addr -= l.Mapping.Start
326 key.mappingID = l.Mapping.ID
327 }
328 lines := make([]string, len(l.Line)*3)
329 for i, line := range l.Line {
330 if line.Function != nil {
331 lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
332 }
333 lines[i*2+1] = strconv.FormatInt(line.Line, 16)
334 lines[i*2+2] = strconv.FormatInt(line.Column, 16)
335 }
336 key.lines = strings.Join(lines, "|")
337 return key
338 }
339
340 type locationKey struct {
341 addr, mappingID uint64
342 lines string
343 isFolded bool
344 }
345
346 func (pm *profileMerger) mapMapping(src *Mapping) mapInfo {
347 if src == nil {
348 return mapInfo{}
349 }
350
351 if mi, ok := pm.mappingsByID[src.ID]; ok {
352 return mi
353 }
354
355
356 mk := src.key()
357 if m, ok := pm.mappings[mk]; ok {
358 mi := mapInfo{m, int64(m.Start) - int64(src.Start)}
359 pm.mappingsByID[src.ID] = mi
360 return mi
361 }
362 m := &Mapping{
363 ID: uint64(len(pm.p.Mapping) + 1),
364 Start: src.Start,
365 Limit: src.Limit,
366 Offset: src.Offset,
367 File: src.File,
368 KernelRelocationSymbol: src.KernelRelocationSymbol,
369 BuildID: src.BuildID,
370 HasFunctions: src.HasFunctions,
371 HasFilenames: src.HasFilenames,
372 HasLineNumbers: src.HasLineNumbers,
373 HasInlineFrames: src.HasInlineFrames,
374 }
375 pm.p.Mapping = append(pm.p.Mapping, m)
376
377
378 pm.mappings[mk] = m
379 mi := mapInfo{m, 0}
380 pm.mappingsByID[src.ID] = mi
381 return mi
382 }
383
384
385
386 func (m *Mapping) key() mappingKey {
387
388
389 const mapsizeRounding = 0x1000
390
391 size := m.Limit - m.Start
392 size = size + mapsizeRounding - 1
393 size = size - (size % mapsizeRounding)
394 key := mappingKey{
395 size: size,
396 offset: m.Offset,
397 }
398
399 switch {
400 case m.BuildID != "":
401 key.buildIDOrFile = m.BuildID
402 case m.File != "":
403 key.buildIDOrFile = m.File
404 default:
405
406
407
408 }
409 return key
410 }
411
412 type mappingKey struct {
413 size, offset uint64
414 buildIDOrFile string
415 }
416
417 func (pm *profileMerger) mapLine(src Line) Line {
418 ln := Line{
419 Function: pm.mapFunction(src.Function),
420 Line: src.Line,
421 Column: src.Column,
422 }
423 return ln
424 }
425
426 func (pm *profileMerger) mapFunction(src *Function) *Function {
427 if src == nil {
428 return nil
429 }
430 if f, ok := pm.functionsByID[src.ID]; ok {
431 return f
432 }
433 k := src.key()
434 if f, ok := pm.functions[k]; ok {
435 pm.functionsByID[src.ID] = f
436 return f
437 }
438 f := &Function{
439 ID: uint64(len(pm.p.Function) + 1),
440 Name: src.Name,
441 SystemName: src.SystemName,
442 Filename: src.Filename,
443 StartLine: src.StartLine,
444 }
445 pm.functions[k] = f
446 pm.functionsByID[src.ID] = f
447 pm.p.Function = append(pm.p.Function, f)
448 return f
449 }
450
451
452 func (f *Function) key() functionKey {
453 return functionKey{
454 f.StartLine,
455 f.Name,
456 f.SystemName,
457 f.Filename,
458 }
459 }
460
461 type functionKey struct {
462 startLine int64
463 name, systemName, fileName string
464 }
465
466
467
468 func combineHeaders(srcs []*Profile) (*Profile, error) {
469 for _, s := range srcs[1:] {
470 if err := srcs[0].compatible(s); err != nil {
471 return nil, err
472 }
473 }
474
475 var timeNanos, durationNanos, period int64
476 var comments []string
477 seenComments := map[string]bool{}
478 var docURL string
479 var defaultSampleType string
480 for _, s := range srcs {
481 if timeNanos == 0 || s.TimeNanos < timeNanos {
482 timeNanos = s.TimeNanos
483 }
484 durationNanos += s.DurationNanos
485 if period == 0 || period < s.Period {
486 period = s.Period
487 }
488 for _, c := range s.Comments {
489 if seen := seenComments[c]; !seen {
490 comments = append(comments, c)
491 seenComments[c] = true
492 }
493 }
494 if defaultSampleType == "" {
495 defaultSampleType = s.DefaultSampleType
496 }
497 if docURL == "" {
498 docURL = s.DocURL
499 }
500 }
501
502 p := &Profile{
503 SampleType: make([]*ValueType, len(srcs[0].SampleType)),
504
505 DropFrames: srcs[0].DropFrames,
506 KeepFrames: srcs[0].KeepFrames,
507
508 TimeNanos: timeNanos,
509 DurationNanos: durationNanos,
510 PeriodType: srcs[0].PeriodType,
511 Period: period,
512
513 Comments: comments,
514 DefaultSampleType: defaultSampleType,
515 DocURL: docURL,
516 }
517 copy(p.SampleType, srcs[0].SampleType)
518 return p, nil
519 }
520
521
522
523
524 func (p *Profile) compatible(pb *Profile) error {
525 if !equalValueType(p.PeriodType, pb.PeriodType) {
526 return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
527 }
528
529 if len(p.SampleType) != len(pb.SampleType) {
530 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
531 }
532
533 for i := range p.SampleType {
534 if !equalValueType(p.SampleType[i], pb.SampleType[i]) {
535 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
536 }
537 }
538 return nil
539 }
540
541
542
543 func equalValueType(st1, st2 *ValueType) bool {
544 return st1.Type == st2.Type && st1.Unit == st2.Unit
545 }
546
547
548
549 type locationIDMap struct {
550 dense []*Location
551 sparse map[uint64]*Location
552 }
553
554 func makeLocationIDMap(n int) locationIDMap {
555 return locationIDMap{
556 dense: make([]*Location, n),
557 sparse: map[uint64]*Location{},
558 }
559 }
560
561 func (lm locationIDMap) get(id uint64) *Location {
562 if id < uint64(len(lm.dense)) {
563 return lm.dense[int(id)]
564 }
565 return lm.sparse[id]
566 }
567
568 func (lm locationIDMap) set(id uint64, loc *Location) {
569 if id < uint64(len(lm.dense)) {
570 lm.dense[id] = loc
571 return
572 }
573 lm.sparse[id] = loc
574 }
575
576
577
578
579
580
581
582
583
584
585
586 func CompatibilizeSampleTypes(ps []*Profile) error {
587 sTypes := commonSampleTypes(ps)
588 if len(sTypes) == 0 {
589 return fmt.Errorf("profiles have empty common sample type list")
590 }
591 for _, p := range ps {
592 if err := compatibilizeSampleTypes(p, sTypes); err != nil {
593 return err
594 }
595 }
596 return nil
597 }
598
599
600
601 func commonSampleTypes(ps []*Profile) []string {
602 if len(ps) == 0 {
603 return nil
604 }
605 sTypes := map[string]int{}
606 for _, p := range ps {
607 for _, st := range p.SampleType {
608 sTypes[st.Type]++
609 }
610 }
611 var res []string
612 for _, st := range ps[0].SampleType {
613 if sTypes[st.Type] == len(ps) {
614 res = append(res, st.Type)
615 }
616 }
617 return res
618 }
619
620
621
622
623
624
625
626
627 func compatibilizeSampleTypes(p *Profile, sTypes []string) error {
628 if len(sTypes) == 0 {
629 return fmt.Errorf("sample type list is empty")
630 }
631 defaultSampleType := sTypes[0]
632 reMap, needToModify := make([]int, len(sTypes)), false
633 for i, st := range sTypes {
634 if st == p.DefaultSampleType {
635 defaultSampleType = p.DefaultSampleType
636 }
637 idx := searchValueType(p.SampleType, st)
638 if idx < 0 {
639 return fmt.Errorf("%q sample type is not found in profile", st)
640 }
641 reMap[i] = idx
642 if idx != i {
643 needToModify = true
644 }
645 }
646 if !needToModify && len(sTypes) == len(p.SampleType) {
647 return nil
648 }
649 p.DefaultSampleType = defaultSampleType
650 oldSampleTypes := p.SampleType
651 p.SampleType = make([]*ValueType, len(sTypes))
652 for i, idx := range reMap {
653 p.SampleType[i] = oldSampleTypes[idx]
654 }
655 values := make([]int64, len(sTypes))
656 for _, s := range p.Sample {
657 for i, idx := range reMap {
658 values[i] = s.Value[idx]
659 }
660 s.Value = s.Value[:len(values)]
661 copy(s.Value, values)
662 }
663 return nil
664 }
665
666 func searchValueType(vts []*ValueType, s string) int {
667 for i, vt := range vts {
668 if vt.Type == s {
669 return i
670 }
671 }
672 return -1
673 }
674
View as plain text