1
2
3
4
5
6
7
8 package profile
9
10 import (
11 "bytes"
12 "compress/gzip"
13 "fmt"
14 "io"
15 "strings"
16 "time"
17 )
18
19
20 type Profile struct {
21 SampleType []*ValueType
22 DefaultSampleType string
23 Sample []*Sample
24 Mapping []*Mapping
25 Location []*Location
26 Function []*Function
27 Comments []string
28
29 DropFrames string
30 KeepFrames string
31
32 TimeNanos int64
33 DurationNanos int64
34 PeriodType *ValueType
35 Period int64
36
37 commentX []int64
38 dropFramesX int64
39 keepFramesX int64
40 stringTable []string
41 defaultSampleTypeX int64
42 }
43
44
45 type ValueType struct {
46 Type string
47 Unit string
48
49 typeX int64
50 unitX int64
51 }
52
53
54 type Sample struct {
55 Location []*Location
56 Value []int64
57 Label map[string][]string
58 NumLabel map[string][]int64
59 NumUnit map[string][]string
60
61 locationIDX []uint64
62 labelX []Label
63 }
64
65
66 type Label struct {
67 keyX int64
68
69 strX int64
70 numX int64
71 }
72
73
74 type Mapping struct {
75 ID uint64
76 Start uint64
77 Limit uint64
78 Offset uint64
79 File string
80 BuildID string
81 HasFunctions bool
82 HasFilenames bool
83 HasLineNumbers bool
84 HasInlineFrames bool
85
86 fileX int64
87 buildIDX int64
88 }
89
90
91 type Location struct {
92 ID uint64
93 Mapping *Mapping
94 Address uint64
95 Line []Line
96 IsFolded bool
97
98 mappingIDX uint64
99 }
100
101
102 type Line struct {
103 Function *Function
104 Line int64
105
106 functionIDX uint64
107 }
108
109
110 type Function struct {
111 ID uint64
112 Name string
113 SystemName string
114 Filename string
115 StartLine int64
116
117 nameX int64
118 systemNameX int64
119 filenameX int64
120 }
121
122
123
124 func Parse(r io.Reader) (*Profile, error) {
125 orig, err := io.ReadAll(r)
126 if err != nil {
127 return nil, err
128 }
129
130 if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b {
131 gz, err := gzip.NewReader(bytes.NewBuffer(orig))
132 if err != nil {
133 return nil, fmt.Errorf("decompressing profile: %v", err)
134 }
135 data, err := io.ReadAll(gz)
136 if err != nil {
137 return nil, fmt.Errorf("decompressing profile: %v", err)
138 }
139 orig = data
140 }
141
142 p, err := parseUncompressed(orig)
143 if err != nil {
144 return nil, fmt.Errorf("parsing profile: %w", err)
145 }
146
147 if err := p.CheckValid(); err != nil {
148 return nil, fmt.Errorf("malformed profile: %v", err)
149 }
150 return p, nil
151 }
152
153 var errMalformed = fmt.Errorf("malformed profile format")
154 var ErrNoData = fmt.Errorf("empty input file")
155
156 func parseUncompressed(data []byte) (*Profile, error) {
157 if len(data) == 0 {
158 return nil, ErrNoData
159 }
160
161 p := &Profile{}
162 if err := unmarshal(data, p); err != nil {
163 return nil, err
164 }
165
166 if err := p.postDecode(); err != nil {
167 return nil, err
168 }
169
170 return p, nil
171 }
172
173
174 func (p *Profile) Write(w io.Writer) error {
175 p.preEncode()
176 b := marshal(p)
177 zw := gzip.NewWriter(w)
178 defer zw.Close()
179 _, err := zw.Write(b)
180 return err
181 }
182
183
184
185
186
187 func (p *Profile) CheckValid() error {
188
189 sampleLen := len(p.SampleType)
190 if sampleLen == 0 && len(p.Sample) != 0 {
191 return fmt.Errorf("missing sample type information")
192 }
193 for _, s := range p.Sample {
194 if len(s.Value) != sampleLen {
195 return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
196 }
197 }
198
199
200
201 mappings := make(map[uint64]*Mapping, len(p.Mapping))
202 for _, m := range p.Mapping {
203 if m.ID == 0 {
204 return fmt.Errorf("found mapping with reserved ID=0")
205 }
206 if mappings[m.ID] != nil {
207 return fmt.Errorf("multiple mappings with same id: %d", m.ID)
208 }
209 mappings[m.ID] = m
210 }
211 functions := make(map[uint64]*Function, len(p.Function))
212 for _, f := range p.Function {
213 if f.ID == 0 {
214 return fmt.Errorf("found function with reserved ID=0")
215 }
216 if functions[f.ID] != nil {
217 return fmt.Errorf("multiple functions with same id: %d", f.ID)
218 }
219 functions[f.ID] = f
220 }
221 locations := make(map[uint64]*Location, len(p.Location))
222 for _, l := range p.Location {
223 if l.ID == 0 {
224 return fmt.Errorf("found location with reserved id=0")
225 }
226 if locations[l.ID] != nil {
227 return fmt.Errorf("multiple locations with same id: %d", l.ID)
228 }
229 locations[l.ID] = l
230 if m := l.Mapping; m != nil {
231 if m.ID == 0 || mappings[m.ID] != m {
232 return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
233 }
234 }
235 for _, ln := range l.Line {
236 if f := ln.Function; f != nil {
237 if f.ID == 0 || functions[f.ID] != f {
238 return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
239 }
240 }
241 }
242 }
243 return nil
244 }
245
246
247
248
249 func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
250 for _, m := range p.Mapping {
251 m.HasInlineFrames = m.HasInlineFrames && inlineFrame
252 m.HasFunctions = m.HasFunctions && function
253 m.HasFilenames = m.HasFilenames && filename
254 m.HasLineNumbers = m.HasLineNumbers && linenumber
255 }
256
257
258 if !function || !filename {
259 for _, f := range p.Function {
260 if !function {
261 f.Name = ""
262 f.SystemName = ""
263 }
264 if !filename {
265 f.Filename = ""
266 }
267 }
268 }
269
270
271 if !inlineFrame || !address || !linenumber {
272 for _, l := range p.Location {
273 if !inlineFrame && len(l.Line) > 1 {
274 l.Line = l.Line[len(l.Line)-1:]
275 }
276 if !linenumber {
277 for i := range l.Line {
278 l.Line[i].Line = 0
279 }
280 }
281 if !address {
282 l.Address = 0
283 }
284 }
285 }
286
287 return p.CheckValid()
288 }
289
290
291
292 func (p *Profile) String() string {
293
294 ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
295 if pt := p.PeriodType; pt != nil {
296 ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
297 }
298 ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
299 if p.TimeNanos != 0 {
300 ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
301 }
302 if p.DurationNanos != 0 {
303 ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos)))
304 }
305
306 ss = append(ss, "Samples:")
307 var sh1 string
308 for _, s := range p.SampleType {
309 sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit)
310 }
311 ss = append(ss, strings.TrimSpace(sh1))
312 for _, s := range p.Sample {
313 var sv string
314 for _, v := range s.Value {
315 sv = fmt.Sprintf("%s %10d", sv, v)
316 }
317 sv = sv + ": "
318 for _, l := range s.Location {
319 sv = sv + fmt.Sprintf("%d ", l.ID)
320 }
321 ss = append(ss, sv)
322 const labelHeader = " "
323 if len(s.Label) > 0 {
324 ls := labelHeader
325 for k, v := range s.Label {
326 ls = ls + fmt.Sprintf("%s:%v ", k, v)
327 }
328 ss = append(ss, ls)
329 }
330 if len(s.NumLabel) > 0 {
331 ls := labelHeader
332 for k, v := range s.NumLabel {
333 ls = ls + fmt.Sprintf("%s:%v ", k, v)
334 }
335 ss = append(ss, ls)
336 }
337 }
338
339 ss = append(ss, "Locations")
340 for _, l := range p.Location {
341 locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
342 if m := l.Mapping; m != nil {
343 locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
344 }
345 if len(l.Line) == 0 {
346 ss = append(ss, locStr)
347 }
348 for li := range l.Line {
349 lnStr := "??"
350 if fn := l.Line[li].Function; fn != nil {
351 lnStr = fmt.Sprintf("%s %s:%d s=%d",
352 fn.Name,
353 fn.Filename,
354 l.Line[li].Line,
355 fn.StartLine)
356 if fn.Name != fn.SystemName {
357 lnStr = lnStr + "(" + fn.SystemName + ")"
358 }
359 }
360 ss = append(ss, locStr+lnStr)
361
362 locStr = " "
363 }
364 }
365
366 ss = append(ss, "Mappings")
367 for _, m := range p.Mapping {
368 bits := ""
369 if m.HasFunctions {
370 bits += "[FN]"
371 }
372 if m.HasFilenames {
373 bits += "[FL]"
374 }
375 if m.HasLineNumbers {
376 bits += "[LN]"
377 }
378 if m.HasInlineFrames {
379 bits += "[IN]"
380 }
381 ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
382 m.ID,
383 m.Start, m.Limit, m.Offset,
384 m.File,
385 m.BuildID,
386 bits))
387 }
388
389 return strings.Join(ss, "\n") + "\n"
390 }
391
392
393
394
395
396 func (p *Profile) Merge(pb *Profile, r float64) error {
397 if err := p.Compatible(pb); err != nil {
398 return err
399 }
400
401 pb = pb.Copy()
402
403
404 if pb.Period > p.Period {
405 p.Period = pb.Period
406 }
407
408 p.DurationNanos += pb.DurationNanos
409
410 p.Mapping = append(p.Mapping, pb.Mapping...)
411 for i, m := range p.Mapping {
412 m.ID = uint64(i + 1)
413 }
414 p.Location = append(p.Location, pb.Location...)
415 for i, l := range p.Location {
416 l.ID = uint64(i + 1)
417 }
418 p.Function = append(p.Function, pb.Function...)
419 for i, f := range p.Function {
420 f.ID = uint64(i + 1)
421 }
422
423 if r != 1.0 {
424 for _, s := range pb.Sample {
425 for i, v := range s.Value {
426 s.Value[i] = int64((float64(v) * r))
427 }
428 }
429 }
430 p.Sample = append(p.Sample, pb.Sample...)
431 return p.CheckValid()
432 }
433
434
435
436
437 func (p *Profile) Compatible(pb *Profile) error {
438 if !compatibleValueTypes(p.PeriodType, pb.PeriodType) {
439 return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
440 }
441
442 if len(p.SampleType) != len(pb.SampleType) {
443 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
444 }
445
446 for i := range p.SampleType {
447 if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) {
448 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
449 }
450 }
451
452 return nil
453 }
454
455
456
457 func (p *Profile) HasFunctions() bool {
458 for _, l := range p.Location {
459 if l.Mapping == nil || !l.Mapping.HasFunctions {
460 return false
461 }
462 }
463 return true
464 }
465
466
467
468 func (p *Profile) HasFileLines() bool {
469 for _, l := range p.Location {
470 if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
471 return false
472 }
473 }
474 return true
475 }
476
477 func compatibleValueTypes(v1, v2 *ValueType) bool {
478 if v1 == nil || v2 == nil {
479 return true
480 }
481 return v1.Type == v2.Type && v1.Unit == v2.Unit
482 }
483
484
485 func (p *Profile) Copy() *Profile {
486 p.preEncode()
487 b := marshal(p)
488
489 pp := &Profile{}
490 if err := unmarshal(b, pp); err != nil {
491 panic(err)
492 }
493 if err := pp.postDecode(); err != nil {
494 panic(err)
495 }
496
497 return pp
498 }
499
500
501
502
503 type Demangler func(name []string) (map[string]string, error)
504
505
506
507
508 func (p *Profile) Demangle(d Demangler) error {
509
510 var names []string
511 for _, fn := range p.Function {
512 names = append(names, fn.SystemName)
513 }
514
515
516 demangled, err := d(names)
517 if err != nil {
518 return err
519 }
520 for _, fn := range p.Function {
521 if dd, ok := demangled[fn.SystemName]; ok {
522 fn.Name = dd
523 }
524 }
525 return nil
526 }
527
528
529 func (p *Profile) Empty() bool {
530 return len(p.Sample) == 0
531 }
532
533
534 func (p *Profile) Scale(ratio float64) {
535 if ratio == 1 {
536 return
537 }
538 ratios := make([]float64, len(p.SampleType))
539 for i := range p.SampleType {
540 ratios[i] = ratio
541 }
542 p.ScaleN(ratios)
543 }
544
545
546 func (p *Profile) ScaleN(ratios []float64) error {
547 if len(p.SampleType) != len(ratios) {
548 return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
549 }
550 allOnes := true
551 for _, r := range ratios {
552 if r != 1 {
553 allOnes = false
554 break
555 }
556 }
557 if allOnes {
558 return nil
559 }
560 for _, s := range p.Sample {
561 for i, v := range s.Value {
562 if ratios[i] != 1 {
563 s.Value[i] = int64(float64(v) * ratios[i])
564 }
565 }
566 }
567 return nil
568 }
569
View as plain text