1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package profile
16
17 import (
18 "bytes"
19 "flag"
20 "fmt"
21 "io"
22 "os"
23 "path/filepath"
24 "reflect"
25 "strings"
26 "sync"
27 "testing"
28
29 "github.com/google/pprof/internal/proftest"
30 )
31
32 var update = flag.Bool("update", false, "Update the golden files")
33
34 func TestParse(t *testing.T) {
35 const path = "testdata/"
36
37 for _, source := range []string{
38 "go.crc32.cpu",
39 "go.godoc.thread",
40 "gobench.cpu",
41 "gobench.heap",
42 "cppbench.cpu",
43 "cppbench.heap",
44 "cppbench.contention",
45 "cppbench.growth",
46 "cppbench.thread",
47 "cppbench.thread.all",
48 "cppbench.thread.none",
49 "java.cpu",
50 "java.heap",
51 "java.contention",
52 } {
53 inbytes, err := os.ReadFile(filepath.Join(path, source))
54 if err != nil {
55 t.Fatal(err)
56 }
57 p, err := Parse(bytes.NewBuffer(inbytes))
58 if err != nil {
59 t.Fatalf("%s: %s", source, err)
60 }
61
62 js := p.String()
63 goldFilename := path + source + ".string"
64 if *update {
65 err := os.WriteFile(goldFilename, []byte(js), 0644)
66 if err != nil {
67 t.Errorf("failed to update the golden file file %q: %v", goldFilename, err)
68 }
69 }
70 gold, err := os.ReadFile(goldFilename)
71 if err != nil {
72 t.Fatalf("%s: %v", source, err)
73 }
74
75 if js != string(gold) {
76 t.Errorf("diff %s %s", source, goldFilename)
77 d, err := proftest.Diff(gold, []byte(js))
78 if err != nil {
79 t.Fatalf("%s: %v", source, err)
80 }
81 t.Error(source + "\n" + string(d) + "\n" + "new profile at:\n" + leaveTempfile([]byte(js)))
82 }
83
84
85 var bw bytes.Buffer
86 if err := p.Write(&bw); err != nil {
87 t.Fatalf("%s: %v", source, err)
88 }
89 if p, err = Parse(&bw); err != nil {
90 t.Fatalf("%s: %v", source, err)
91 }
92 js2 := p.String()
93 if js2 != string(gold) {
94 d, err := proftest.Diff(gold, []byte(js2))
95 if err != nil {
96 t.Fatalf("%s: %v", source, err)
97 }
98 t.Error(source + "\n" + string(d) + "\n" + "gold:\n" + goldFilename +
99 "\nnew profile at:\n" + leaveTempfile([]byte(js)))
100 }
101 }
102 }
103
104 func TestParseError(t *testing.T) {
105 testcases := []string{
106 "",
107 "garbage text",
108 "\x1f\x8b",
109 "\x1f\x8b\x08\x08\xbe\xe9\x20\x58\x00\x03\x65\x6d\x70\x74\x79\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
110 }
111
112 for i, input := range testcases {
113 _, err := Parse(strings.NewReader(input))
114 if err == nil {
115 t.Errorf("got nil, want error for input #%d", i)
116 }
117 }
118 }
119
120 func TestParseConcatentated(t *testing.T) {
121 prof := testProfile1.Copy()
122
123 var buf bytes.Buffer
124 prof.Write(&buf)
125 prof.Write(&buf)
126 _, err := Parse(&buf)
127 if err == nil {
128 t.Fatalf("got nil, want error")
129 }
130 if got, want := err.Error(), "parsing profile: concatenated profiles detected"; want != got {
131 t.Fatalf("got error %q, want error %q", got, want)
132 }
133 }
134
135 func TestCheckValid(t *testing.T) {
136 const path = "testdata/java.cpu"
137
138 inbytes, err := os.ReadFile(path)
139 if err != nil {
140 t.Fatalf("failed to read profile file %q: %v", path, err)
141 }
142 p, err := Parse(bytes.NewBuffer(inbytes))
143 if err != nil {
144 t.Fatalf("failed to parse profile %q: %s", path, err)
145 }
146
147 for _, tc := range []struct {
148 mutateFn func(*Profile)
149 wantErr string
150 }{
151 {
152 mutateFn: func(p *Profile) { p.SampleType = nil },
153 wantErr: "missing sample type information",
154 },
155 {
156 mutateFn: func(p *Profile) { p.Sample[0] = nil },
157 wantErr: "profile has nil sample",
158 },
159 {
160 mutateFn: func(p *Profile) { p.Sample[0].Value = append(p.Sample[0].Value, 0) },
161 wantErr: "sample has 3 values vs. 2 types",
162 },
163 {
164 mutateFn: func(p *Profile) { p.Sample[0].Location[0] = nil },
165 wantErr: "sample has nil location",
166 },
167 {
168 mutateFn: func(p *Profile) { p.Location[0] = nil },
169 wantErr: "profile has nil location",
170 },
171 {
172 mutateFn: func(p *Profile) { p.Mapping = append(p.Mapping, nil) },
173 wantErr: "profile has nil mapping",
174 },
175 {
176 mutateFn: func(p *Profile) { p.Function[0] = nil },
177 wantErr: "profile has nil function",
178 },
179 {
180 mutateFn: func(p *Profile) { p.Location[0].Line = append(p.Location[0].Line, Line{}) },
181 wantErr: "has a line with nil function",
182 },
183 } {
184 t.Run(tc.wantErr, func(t *testing.T) {
185 p := p.Copy()
186 tc.mutateFn(p)
187 if err := p.CheckValid(); err == nil {
188 t.Errorf("CheckValid(): got no error, want error %q", tc.wantErr)
189 } else if !strings.Contains(err.Error(), tc.wantErr) {
190 t.Errorf("CheckValid(): got error %v, want error %q", err, tc.wantErr)
191 }
192 })
193 }
194 }
195
196
197
198
199 func leaveTempfile(b []byte) string {
200 f1, err := os.CreateTemp("", "profile_test")
201 if err != nil {
202 panic(err)
203 }
204 if _, err := f1.Write(b); err != nil {
205 panic(err)
206 }
207 return f1.Name()
208 }
209
210 const mainBinary = "/bin/main"
211
212 var cpuM = []*Mapping{
213 {
214 ID: 1,
215 Start: 0x10000,
216 Limit: 0x40000,
217 File: mainBinary,
218 HasFunctions: true,
219 HasFilenames: true,
220 HasLineNumbers: true,
221 HasInlineFrames: true,
222 },
223 {
224 ID: 2,
225 Start: 0x1000,
226 Limit: 0x4000,
227 File: "/lib/lib.so",
228 HasFunctions: true,
229 HasFilenames: true,
230 HasLineNumbers: true,
231 HasInlineFrames: true,
232 },
233 {
234 ID: 3,
235 Start: 0x4000,
236 Limit: 0x5000,
237 File: "/lib/lib2_c.so.6",
238 HasFunctions: true,
239 HasFilenames: true,
240 HasLineNumbers: true,
241 HasInlineFrames: true,
242 },
243 {
244 ID: 4,
245 Start: 0x5000,
246 Limit: 0x9000,
247 File: "/lib/lib.so_6 (deleted)",
248 HasFunctions: true,
249 HasFilenames: true,
250 HasLineNumbers: true,
251 HasInlineFrames: true,
252 },
253 {
254 ID: 5,
255 Start: 0xffff000010080000,
256 Limit: 0xffffffffffffffff,
257 File: "[kernel.kallsyms]_text",
258 HasFunctions: true,
259 HasFilenames: true,
260 HasLineNumbers: true,
261 HasInlineFrames: true,
262 },
263 }
264
265 var cpuF = []*Function{
266 {ID: 1, Name: "main", SystemName: "main", Filename: "main.c"},
267 {ID: 2, Name: "foo", SystemName: "foo", Filename: "foo.c"},
268 {ID: 3, Name: "foo_caller", SystemName: "foo_caller", Filename: "foo.c"},
269 }
270
271 var cpuL = []*Location{
272 {
273 ID: 1000,
274 Mapping: cpuM[1],
275 Address: 0x1000,
276 Line: []Line{
277 {Function: cpuF[0], Line: 1, Column: 1},
278 },
279 },
280 {
281 ID: 2000,
282 Mapping: cpuM[0],
283 Address: 0x2000,
284 Line: []Line{
285 {Function: cpuF[1], Line: 2, Column: 2},
286 {Function: cpuF[2], Line: 1, Column: 1},
287 },
288 },
289 {
290 ID: 3000,
291 Mapping: cpuM[0],
292 Address: 0x3000,
293 Line: []Line{
294 {Function: cpuF[1], Line: 2, Column: 2},
295 {Function: cpuF[2], Line: 1, Column: 1},
296 },
297 },
298 {
299 ID: 3001,
300 Mapping: cpuM[0],
301 Address: 0x3001,
302 Line: []Line{
303 {Function: cpuF[2], Line: 2, Column: 2},
304 },
305 },
306 {
307 ID: 3002,
308 Mapping: cpuM[0],
309 Address: 0x3002,
310 Line: []Line{
311 {Function: cpuF[2], Line: 3, Column: 3},
312 },
313 },
314
315 {
316 ID: 1001,
317 Mapping: cpuM[1],
318 Address: 0x1001,
319 Line: []Line{
320 {Function: cpuF[0], Line: 1, Column: 2},
321 },
322 },
323 }
324
325 var testProfile1 = &Profile{
326 TimeNanos: 10000,
327 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
328 Period: 1,
329 DurationNanos: 10e9,
330 SampleType: []*ValueType{
331 {Type: "samples", Unit: "count"},
332 {Type: "cpu", Unit: "milliseconds"},
333 },
334 Sample: []*Sample{
335 {
336 Location: []*Location{cpuL[0]},
337 Value: []int64{1000, 1000},
338 Label: map[string][]string{
339 "key1": {"tag1"},
340 "key2": {"tag1"},
341 },
342 },
343 {
344 Location: []*Location{cpuL[1], cpuL[0]},
345 Value: []int64{100, 100},
346 Label: map[string][]string{
347 "key1": {"tag2"},
348 "key3": {"tag2"},
349 },
350 },
351 {
352 Location: []*Location{cpuL[2], cpuL[0]},
353 Value: []int64{10, 10},
354 Label: map[string][]string{
355 "key1": {"tag3"},
356 "key2": {"tag2"},
357 },
358 },
359 {
360 Location: []*Location{cpuL[3], cpuL[0]},
361 Value: []int64{10000, 10000},
362 Label: map[string][]string{
363 "key1": {"tag4"},
364 "key2": {"tag1"},
365 },
366 },
367 {
368 Location: []*Location{cpuL[4], cpuL[0]},
369 Value: []int64{1, 1},
370 Label: map[string][]string{
371 "key1": {"tag4"},
372 "key2": {"tag1"},
373 },
374 },
375 },
376 Location: cpuL,
377 Function: cpuF,
378 Mapping: cpuM,
379 }
380
381 var testProfile1NoMapping = &Profile{
382 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
383 Period: 1,
384 DurationNanos: 10e9,
385 SampleType: []*ValueType{
386 {Type: "samples", Unit: "count"},
387 {Type: "cpu", Unit: "milliseconds"},
388 },
389 Sample: []*Sample{
390 {
391 Location: []*Location{cpuL[0]},
392 Value: []int64{1000, 1000},
393 Label: map[string][]string{
394 "key1": {"tag1"},
395 "key2": {"tag1"},
396 },
397 },
398 {
399 Location: []*Location{cpuL[1], cpuL[0]},
400 Value: []int64{100, 100},
401 Label: map[string][]string{
402 "key1": {"tag2"},
403 "key3": {"tag2"},
404 },
405 },
406 {
407 Location: []*Location{cpuL[2], cpuL[0]},
408 Value: []int64{10, 10},
409 Label: map[string][]string{
410 "key1": {"tag3"},
411 "key2": {"tag2"},
412 },
413 },
414 {
415 Location: []*Location{cpuL[3], cpuL[0]},
416 Value: []int64{10000, 10000},
417 Label: map[string][]string{
418 "key1": {"tag4"},
419 "key2": {"tag1"},
420 },
421 },
422 {
423 Location: []*Location{cpuL[4], cpuL[0]},
424 Value: []int64{1, 1},
425 Label: map[string][]string{
426 "key1": {"tag4"},
427 "key2": {"tag1"},
428 },
429 },
430 },
431 Location: cpuL,
432 Function: cpuF,
433 }
434
435 var testProfile2 = &Profile{
436 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
437 Period: 1,
438 DurationNanos: 10e9,
439 SampleType: []*ValueType{
440 {Type: "samples", Unit: "count"},
441 {Type: "cpu", Unit: "milliseconds"},
442 },
443 Sample: []*Sample{
444 {
445 Location: []*Location{cpuL[0]},
446 Value: []int64{70, 1000},
447 Label: map[string][]string{
448 "key1": {"tag1"},
449 "key2": {"tag1"},
450 },
451 },
452 {
453 Location: []*Location{cpuL[1], cpuL[0]},
454 Value: []int64{60, 100},
455 Label: map[string][]string{
456 "key1": {"tag2"},
457 "key3": {"tag2"},
458 },
459 },
460 {
461 Location: []*Location{cpuL[2], cpuL[0]},
462 Value: []int64{50, 10},
463 Label: map[string][]string{
464 "key1": {"tag3"},
465 "key2": {"tag2"},
466 },
467 },
468 {
469 Location: []*Location{cpuL[3], cpuL[0]},
470 Value: []int64{40, 10000},
471 Label: map[string][]string{
472 "key1": {"tag4"},
473 "key2": {"tag1"},
474 },
475 },
476 {
477 Location: []*Location{cpuL[4], cpuL[0]},
478 Value: []int64{1, 1},
479 Label: map[string][]string{
480 "key1": {"tag4"},
481 "key2": {"tag1"},
482 },
483 },
484 },
485 Location: cpuL,
486 Function: cpuF,
487 Mapping: cpuM,
488 }
489
490 var testProfile3 = &Profile{
491 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
492 Period: 1,
493 DurationNanos: 10e9,
494 SampleType: []*ValueType{
495 {Type: "samples", Unit: "count"},
496 },
497 Sample: []*Sample{
498 {
499 Location: []*Location{cpuL[0]},
500 Value: []int64{1000},
501 Label: map[string][]string{
502 "key1": {"tag1"},
503 "key2": {"tag1"},
504 },
505 },
506 },
507 Location: cpuL,
508 Function: cpuF,
509 Mapping: cpuM,
510 }
511
512 var testProfile4 = &Profile{
513 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
514 Period: 1,
515 DurationNanos: 10e9,
516 SampleType: []*ValueType{
517 {Type: "samples", Unit: "count"},
518 },
519 Sample: []*Sample{
520 {
521 Location: []*Location{cpuL[0]},
522 Value: []int64{1000},
523 NumLabel: map[string][]int64{
524 "key1": {10},
525 "key2": {30},
526 },
527 NumUnit: map[string][]string{
528 "key1": {"bytes"},
529 "key2": {"bytes"},
530 },
531 },
532 },
533 Location: cpuL,
534 Function: cpuF,
535 Mapping: cpuM,
536 }
537
538 var testProfile5 = &Profile{
539 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
540 Period: 1,
541 DurationNanos: 10e9,
542 SampleType: []*ValueType{
543 {Type: "samples", Unit: "count"},
544 },
545 Sample: []*Sample{
546 {
547 Location: []*Location{cpuL[0]},
548 Value: []int64{1000},
549 NumLabel: map[string][]int64{
550 "key1": {10},
551 "key2": {30},
552 },
553 NumUnit: map[string][]string{
554 "key1": {"bytes"},
555 "key2": {"bytes"},
556 },
557 },
558 {
559 Location: []*Location{cpuL[0]},
560 Value: []int64{1000},
561 NumLabel: map[string][]int64{
562 "key1": {10},
563 "key2": {30},
564 },
565 NumUnit: map[string][]string{
566 "key1": {"kilobytes"},
567 "key2": {"kilobytes"},
568 },
569 },
570 },
571 Location: cpuL,
572 Function: cpuF,
573 Mapping: cpuM,
574 }
575
576 var testProfile6 = &Profile{
577 TimeNanos: 10000,
578 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
579 Period: 1,
580 DurationNanos: 10e9,
581 SampleType: []*ValueType{
582 {Type: "samples", Unit: "count"},
583 {Type: "cpu", Unit: "milliseconds"},
584 },
585 Sample: []*Sample{
586 {
587 Location: []*Location{cpuL[0]},
588 Value: []int64{1000, 1000},
589 Label: map[string][]string{
590 "key1": {"tag1"},
591 "key2": {"tag1"},
592 },
593 },
594 {
595 Location: []*Location{cpuL[1], cpuL[0]},
596 Value: []int64{100, 100},
597 Label: map[string][]string{
598 "key1": {"tag2"},
599 "key3": {"tag2"},
600 },
601 },
602 {
603 Location: []*Location{cpuL[2], cpuL[0]},
604 Value: []int64{10, 10},
605 Label: map[string][]string{
606 "key1": {"tag3"},
607 "key2": {"tag2"},
608 },
609 },
610 {
611 Location: []*Location{cpuL[3], cpuL[0]},
612 Value: []int64{10000, 10000},
613 Label: map[string][]string{
614 "key1": {"tag4"},
615 "key2": {"tag1"},
616 },
617 },
618 {
619 Location: []*Location{cpuL[4], cpuL[0]},
620 Value: []int64{1, 1},
621 Label: map[string][]string{
622 "key1": {"tag4"},
623 "key2": {"tag1"},
624 },
625 },
626 {
627 Location: []*Location{cpuL[5]},
628 Value: []int64{1, 1},
629 Label: map[string][]string{
630 "key1": {"tag5"},
631 "key2": {"tag1"},
632 },
633 },
634 },
635 Location: cpuL,
636 Function: cpuF,
637 Mapping: cpuM,
638 }
639
640 var aggTests = map[string]aggTest{
641 "precise": {true, true, true, true, true, 6},
642 "columns": {false, true, true, true, true, 5},
643 "fileline": {false, true, true, false, true, 4},
644 "inline_function": {false, true, false, false, true, 3},
645 "function": {false, true, false, false, false, 2},
646 }
647
648 type aggTest struct {
649 precise, function, fileline, column, inlineFrame bool
650 rows int
651 }
652
653
654 const totalSamples = int64(11112)
655
656 func TestAggregation(t *testing.T) {
657 prof := testProfile6.Copy()
658 for _, resolution := range []string{"precise", "columns", "fileline", "inline_function", "function"} {
659 a := aggTests[resolution]
660 if !a.precise {
661 if err := prof.Aggregate(a.inlineFrame, a.function, a.fileline, a.fileline, a.column, false); err != nil {
662 t.Error("aggregating to " + resolution + ":" + err.Error())
663 }
664 }
665 if err := checkAggregation(prof, &a); err != nil {
666 t.Error("failed aggregation to " + resolution + ": " + err.Error())
667 }
668 }
669 }
670
671
672
673 func checkAggregation(prof *Profile, a *aggTest) error {
674
675 total := int64(0)
676
677 samples := make(map[string]bool)
678 for _, sample := range prof.Sample {
679 tb := locationHash(sample)
680 samples[tb] = true
681 total += sample.Value[0]
682 }
683
684 if total != totalSamples {
685 return fmt.Errorf("sample total %d, want %d", total, totalSamples)
686 }
687
688
689 if a.rows != len(samples) {
690 return fmt.Errorf("number of samples %d, want %d", len(samples), a.rows)
691 }
692
693
694 for _, m := range prof.Mapping {
695 if m.HasFunctions != a.function {
696 return fmt.Errorf("unexpected mapping.HasFunctions %v, want %v", m.HasFunctions, a.function)
697 }
698 if m.HasFilenames != a.fileline {
699 return fmt.Errorf("unexpected mapping.HasFilenames %v, want %v", m.HasFilenames, a.fileline)
700 }
701 if m.HasLineNumbers != a.fileline {
702 return fmt.Errorf("unexpected mapping.HasLineNumbers %v, want %v", m.HasLineNumbers, a.fileline)
703 }
704 if m.HasInlineFrames != a.inlineFrame {
705 return fmt.Errorf("unexpected mapping.HasInlineFrames %v, want %v", m.HasInlineFrames, a.inlineFrame)
706 }
707 }
708
709
710 for _, l := range prof.Location {
711 if !a.inlineFrame && len(l.Line) > 1 {
712 return fmt.Errorf("found %d lines on location %d, want 1", len(l.Line), l.ID)
713 }
714
715 for _, ln := range l.Line {
716 if !a.column && ln.Column != 0 {
717 return fmt.Errorf("found column %d on location %d, want:0", ln.Column, l.ID)
718 }
719 if !a.fileline && (ln.Function.Filename != "" || ln.Line != 0) {
720 return fmt.Errorf("found line %s:%d on location %d, want :0",
721 ln.Function.Filename, ln.Line, l.ID)
722 }
723 if !a.function && (ln.Function.Name != "") {
724 return fmt.Errorf(`found file %s location %d, want ""`,
725 ln.Function.Name, l.ID)
726 }
727 }
728 }
729
730 return nil
731 }
732
733
734
735 func TestScale(t *testing.T) {
736 for _, tc := range []struct {
737 desc string
738 ratio float64
739 p *Profile
740 wantSamples [][]int64
741 }{
742 {
743 desc: "scale by 1",
744 ratio: 1.0,
745 p: testProfile1.Copy(),
746 wantSamples: [][]int64{
747 {1000, 1000},
748 {100, 100},
749 {10, 10},
750 {10000, 10000},
751 {1, 1},
752 },
753 },
754 {
755 desc: "sample values will be rounded up",
756 ratio: .66666,
757 p: testProfile1.Copy(),
758 wantSamples: [][]int64{
759 {667, 667},
760 {67, 67},
761 {7, 7},
762 {6667, 6667},
763 {1, 1},
764 },
765 },
766 {
767 desc: "sample values will be rounded down",
768 ratio: .33333,
769 p: testProfile1.Copy(),
770 wantSamples: [][]int64{
771 {333, 333},
772 {33, 33},
773 {3, 3},
774 {3333, 3333},
775 },
776 },
777 {
778 desc: "all sample values will be dropped",
779 ratio: 0.00001,
780 p: testProfile1.Copy(),
781 wantSamples: [][]int64{},
782 },
783 } {
784 t.Run(tc.desc, func(t *testing.T) {
785 tc.p.Scale(tc.ratio)
786 if got, want := len(tc.p.Sample), len(tc.wantSamples); got != want {
787 t.Fatalf("got %d samples, want %d", got, want)
788 }
789 for i, s := range tc.p.Sample {
790 for j, got := range s.Value {
791 want := tc.wantSamples[i][j]
792 if want != got {
793 t.Errorf("For value %d of sample %d, got %d want %d", j, i, got, want)
794 }
795 }
796 }
797 })
798 }
799 }
800
801
802 func TestMergeMain(t *testing.T) {
803 prof := testProfile1.Copy()
804 p1, err := Merge([]*Profile{prof})
805 if err != nil {
806 t.Fatalf("merge error: %v", err)
807 }
808 if cpuM[0].File != p1.Mapping[0].File {
809 t.Errorf("want Mapping[0]=%s got %s", cpuM[0].File, p1.Mapping[0].File)
810 }
811 }
812
813 func TestMerge(t *testing.T) {
814
815
816
817
818 prof := testProfile1.Copy()
819 prof.Comments = []string{"comment1"}
820 p1, err := Merge([]*Profile{prof, prof})
821 if err != nil {
822 t.Errorf("merge error: %v", err)
823 }
824 prof.Scale(-2)
825 prof, err = Merge([]*Profile{p1, prof})
826 if err != nil {
827 t.Errorf("merge error: %v", err)
828 }
829 if got, want := len(prof.Comments), 1; got != want {
830 t.Errorf("len(prof.Comments) = %d, want %d", got, want)
831 }
832
833
834 if err := prof.Aggregate(false, true, false, false, false, false); err != nil {
835 t.Errorf("aggregating after merge: %v", err)
836 }
837
838 samples := make(map[string]int64)
839 for _, s := range prof.Sample {
840 tb := locationHash(s)
841 samples[tb] = samples[tb] + s.Value[0]
842 }
843 for s, v := range samples {
844 if v != 0 {
845 t.Errorf("nonzero value for sample %s: %d", s, v)
846 }
847 }
848 }
849
850 func TestMergeAll(t *testing.T) {
851
852 profs := make([]*Profile, 10)
853 for i := 0; i < 10; i++ {
854 profs[i] = testProfile1.Copy()
855 }
856 prof, err := Merge(profs)
857 if err != nil {
858 t.Errorf("merge error: %v", err)
859 }
860 samples := make(map[string]int64)
861 for _, s := range prof.Sample {
862 tb := locationHash(s)
863 samples[tb] = samples[tb] + s.Value[0]
864 }
865 for _, s := range testProfile1.Sample {
866 tb := locationHash(s)
867 if samples[tb] != s.Value[0]*10 {
868 t.Errorf("merge got wrong value at %s : %d instead of %d", tb, samples[tb], s.Value[0]*10)
869 }
870 }
871 }
872
873 func TestIsFoldedMerge(t *testing.T) {
874 testProfile1Folded := testProfile1.Copy()
875 testProfile1Folded.Location[0].IsFolded = true
876 testProfile1Folded.Location[1].IsFolded = true
877
878 for _, tc := range []struct {
879 name string
880 profs []*Profile
881 wantLocationLen int
882 }{
883 {
884 name: "folded and non-folded locations not merged",
885 profs: []*Profile{testProfile1.Copy(), testProfile1Folded.Copy()},
886 wantLocationLen: 7,
887 },
888 {
889 name: "identical folded locations are merged",
890 profs: []*Profile{testProfile1Folded.Copy(), testProfile1Folded.Copy()},
891 wantLocationLen: 5,
892 },
893 } {
894 t.Run(tc.name, func(t *testing.T) {
895 prof, err := Merge(tc.profs)
896 if err != nil {
897 t.Fatalf("merge error: %v", err)
898 }
899 if got, want := len(prof.Location), tc.wantLocationLen; got != want {
900 t.Fatalf("got %d locations, want %d locations", got, want)
901 }
902 })
903 }
904 }
905
906 func TestNumLabelMerge(t *testing.T) {
907 for _, tc := range []struct {
908 name string
909 profs []*Profile
910 wantNumLabels []map[string][]int64
911 wantNumUnits []map[string][]string
912 }{
913 {
914 name: "different label units not merged",
915 profs: []*Profile{testProfile4.Copy(), testProfile5.Copy()},
916 wantNumLabels: []map[string][]int64{
917 {
918 "key1": {10},
919 "key2": {30},
920 },
921 {
922 "key1": {10},
923 "key2": {30},
924 },
925 },
926 wantNumUnits: []map[string][]string{
927 {
928 "key1": {"bytes"},
929 "key2": {"bytes"},
930 },
931 {
932 "key1": {"kilobytes"},
933 "key2": {"kilobytes"},
934 },
935 },
936 },
937 } {
938 t.Run(tc.name, func(t *testing.T) {
939 prof, err := Merge(tc.profs)
940 if err != nil {
941 t.Errorf("merge error: %v", err)
942 }
943
944 if want, got := len(tc.wantNumLabels), len(prof.Sample); want != got {
945 t.Fatalf("got %d samples, want %d samples", got, want)
946 }
947 for i, wantLabels := range tc.wantNumLabels {
948 numLabels := prof.Sample[i].NumLabel
949 if !reflect.DeepEqual(wantLabels, numLabels) {
950 t.Errorf("got numeric labels %v, want %v", numLabels, wantLabels)
951 }
952
953 wantUnits := tc.wantNumUnits[i]
954 numUnits := prof.Sample[i].NumUnit
955 if !reflect.DeepEqual(wantUnits, numUnits) {
956 t.Errorf("got numeric labels %v, want %v", numUnits, wantUnits)
957 }
958 }
959 })
960 }
961 }
962
963 func TestEmptyMappingMerge(t *testing.T) {
964
965
966
967
968 prof1 := testProfile1.Copy()
969 prof2 := testProfile1NoMapping.Copy()
970 p1, err := Merge([]*Profile{prof2, prof1})
971 if err != nil {
972 t.Errorf("merge error: %v", err)
973 }
974 prof2.Scale(-2)
975 prof, err := Merge([]*Profile{p1, prof2})
976 if err != nil {
977 t.Errorf("merge error: %v", err)
978 }
979
980
981 if err := prof.Aggregate(false, true, false, false, false, false); err != nil {
982 t.Errorf("aggregating after merge: %v", err)
983 }
984
985 samples := make(map[string]int64)
986 for _, s := range prof.Sample {
987 tb := locationHash(s)
988 samples[tb] = samples[tb] + s.Value[0]
989 }
990 for s, v := range samples {
991 if v != 0 {
992 t.Errorf("nonzero value for sample %s: %d", s, v)
993 }
994 }
995 }
996
997 func TestNormalizeBySameProfile(t *testing.T) {
998 pb := testProfile1.Copy()
999 p := testProfile1.Copy()
1000
1001 if err := p.Normalize(pb); err != nil {
1002 t.Fatal(err)
1003 }
1004
1005 for i, s := range p.Sample {
1006 for j, v := range s.Value {
1007 expectedSampleValue := testProfile1.Sample[i].Value[j]
1008 if v != expectedSampleValue {
1009 t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValue, v)
1010 }
1011 }
1012 }
1013 }
1014
1015 func TestNormalizeByDifferentProfile(t *testing.T) {
1016 p := testProfile1.Copy()
1017 pb := testProfile2.Copy()
1018
1019 if err := p.Normalize(pb); err != nil {
1020 t.Fatal(err)
1021 }
1022
1023 expectedSampleValues := [][]int64{
1024 {20, 1000},
1025 {2, 100},
1026 {199, 10000},
1027 {0, 1},
1028 }
1029
1030 for i, s := range p.Sample {
1031 for j, v := range s.Value {
1032 if v != expectedSampleValues[i][j] {
1033 t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValues[i][j], v)
1034 }
1035 }
1036 }
1037 }
1038
1039 func TestNormalizeByMultipleOfSameProfile(t *testing.T) {
1040 pb := testProfile1.Copy()
1041 for i, s := range pb.Sample {
1042 for j, v := range s.Value {
1043 pb.Sample[i].Value[j] = 10 * v
1044 }
1045 }
1046
1047 p := testProfile1.Copy()
1048
1049 err := p.Normalize(pb)
1050 if err != nil {
1051 t.Fatal(err)
1052 }
1053
1054 for i, s := range p.Sample {
1055 for j, v := range s.Value {
1056 expectedSampleValue := 10 * testProfile1.Sample[i].Value[j]
1057 if v != expectedSampleValue {
1058 t.Errorf("For sample %d, value %d, want %d got %d", i, j, expectedSampleValue, v)
1059 }
1060 }
1061 }
1062 }
1063
1064 func TestNormalizeIncompatibleProfiles(t *testing.T) {
1065 p := testProfile1.Copy()
1066 pb := testProfile3.Copy()
1067
1068 if err := p.Normalize(pb); err == nil {
1069 t.Errorf("Expected an error")
1070 }
1071 }
1072
1073
1074 func locationHash(s *Sample) string {
1075 var tb string
1076 for _, l := range s.Location {
1077 for _, ln := range l.Line {
1078 tb = tb + fmt.Sprintf("%s:%d:%d@%d ", ln.Function.Name, ln.Line, ln.Column, l.Address)
1079 }
1080 }
1081 return tb
1082 }
1083
1084 func TestHasLabel(t *testing.T) {
1085 var testcases = []struct {
1086 desc string
1087 labels map[string][]string
1088 key string
1089 value string
1090 wantHasLabel bool
1091 }{
1092 {
1093 desc: "empty label does not have label",
1094 labels: map[string][]string{},
1095 key: "key",
1096 value: "value",
1097 wantHasLabel: false,
1098 },
1099 {
1100 desc: "label with one key and value has label",
1101 labels: map[string][]string{"key": {"value"}},
1102 key: "key",
1103 value: "value",
1104 wantHasLabel: true,
1105 },
1106 {
1107 desc: "label with one key and value does not have label",
1108 labels: map[string][]string{"key": {"value"}},
1109 key: "key1",
1110 value: "value1",
1111 wantHasLabel: false,
1112 },
1113 {
1114 desc: "label with many keys and values has label",
1115 labels: map[string][]string{
1116 "key1": {"value2", "value1"},
1117 "key2": {"value1", "value2", "value2"},
1118 "key3": {"value1", "value2", "value2"},
1119 },
1120 key: "key1",
1121 value: "value1",
1122 wantHasLabel: true,
1123 },
1124 {
1125 desc: "label with many keys and values does not have label",
1126 labels: map[string][]string{
1127 "key1": {"value2", "value1"},
1128 "key2": {"value1", "value2", "value2"},
1129 "key3": {"value1", "value2", "value2"},
1130 },
1131 key: "key5",
1132 value: "value5",
1133 wantHasLabel: false,
1134 },
1135 }
1136
1137 for _, tc := range testcases {
1138 t.Run(tc.desc, func(t *testing.T) {
1139 sample := &Sample{
1140 Label: tc.labels,
1141 }
1142 if gotHasLabel := sample.HasLabel(tc.key, tc.value); gotHasLabel != tc.wantHasLabel {
1143 t.Errorf("sample.HasLabel(%q, %q) got %v, want %v", tc.key, tc.value, gotHasLabel, tc.wantHasLabel)
1144 }
1145 })
1146 }
1147 }
1148
1149 func TestDiffBaseSample(t *testing.T) {
1150 var testcases = []struct {
1151 desc string
1152 labels map[string][]string
1153 wantDiffBaseSample bool
1154 }{
1155 {
1156 desc: "empty label does not have label",
1157 labels: map[string][]string{},
1158 wantDiffBaseSample: false,
1159 },
1160 {
1161 desc: "label with one key and value, including diff base label",
1162 labels: map[string][]string{"pprof::base": {"true"}},
1163 wantDiffBaseSample: true,
1164 },
1165 {
1166 desc: "label with one key and value, not including diff base label",
1167 labels: map[string][]string{"key": {"value"}},
1168 wantDiffBaseSample: false,
1169 },
1170 {
1171 desc: "label with many keys and values, including diff base label",
1172 labels: map[string][]string{
1173 "pprof::base": {"value2", "true"},
1174 "key2": {"true", "value2", "value2"},
1175 "key3": {"true", "value2", "value2"},
1176 },
1177 wantDiffBaseSample: true,
1178 },
1179 {
1180 desc: "label with many keys and values, not including diff base label",
1181 labels: map[string][]string{
1182 "key1": {"value2", "value1"},
1183 "key2": {"value1", "value2", "value2"},
1184 "key3": {"value1", "value2", "value2"},
1185 },
1186 wantDiffBaseSample: false,
1187 },
1188 }
1189
1190 for _, tc := range testcases {
1191 t.Run(tc.desc, func(t *testing.T) {
1192 sample := &Sample{
1193 Label: tc.labels,
1194 }
1195 if gotHasLabel := sample.DiffBaseSample(); gotHasLabel != tc.wantDiffBaseSample {
1196 t.Errorf("sample.DiffBaseSample() got %v, want %v", gotHasLabel, tc.wantDiffBaseSample)
1197 }
1198 })
1199 }
1200 }
1201
1202 func TestRemove(t *testing.T) {
1203 var testcases = []struct {
1204 desc string
1205 samples []*Sample
1206 removeKey string
1207 wantLabels []map[string][]string
1208 }{
1209 {
1210 desc: "some samples have label already",
1211 samples: []*Sample{
1212 {
1213 Location: []*Location{cpuL[0]},
1214 Value: []int64{1000},
1215 },
1216 {
1217 Location: []*Location{cpuL[0]},
1218 Value: []int64{1000},
1219 Label: map[string][]string{
1220 "key1": {"value1", "value2", "value3"},
1221 "key2": {"value1"},
1222 },
1223 },
1224 {
1225 Location: []*Location{cpuL[0]},
1226 Value: []int64{1000},
1227 Label: map[string][]string{
1228 "key1": {"value2"},
1229 },
1230 },
1231 },
1232 removeKey: "key1",
1233 wantLabels: []map[string][]string{
1234 {},
1235 {"key2": {"value1"}},
1236 {},
1237 },
1238 },
1239 }
1240
1241 for _, tc := range testcases {
1242 t.Run(tc.desc, func(t *testing.T) {
1243 profile := testProfile1.Copy()
1244 profile.Sample = tc.samples
1245 profile.RemoveLabel(tc.removeKey)
1246 if got, want := len(profile.Sample), len(tc.wantLabels); got != want {
1247 t.Fatalf("got %v samples, want %v samples", got, want)
1248 }
1249 for i, sample := range profile.Sample {
1250 wantLabels := tc.wantLabels[i]
1251 if got, want := len(sample.Label), len(wantLabels); got != want {
1252 t.Errorf("got %v label keys for sample %v, want %v", got, i, want)
1253 continue
1254 }
1255 for wantKey, wantValues := range wantLabels {
1256 if gotValues, ok := sample.Label[wantKey]; ok {
1257 if !reflect.DeepEqual(gotValues, wantValues) {
1258 t.Errorf("for key %s, got values %v, want values %v", wantKey, gotValues, wantValues)
1259 }
1260 } else {
1261 t.Errorf("for key %s got no values, want %v", wantKey, wantValues)
1262 }
1263 }
1264 }
1265 })
1266 }
1267 }
1268
1269 func TestSetLabel(t *testing.T) {
1270 var testcases = []struct {
1271 desc string
1272 samples []*Sample
1273 setKey string
1274 setVal []string
1275 wantLabels []map[string][]string
1276 }{
1277 {
1278 desc: "some samples have label already",
1279 samples: []*Sample{
1280 {
1281 Location: []*Location{cpuL[0]},
1282 Value: []int64{1000},
1283 },
1284 {
1285 Location: []*Location{cpuL[0]},
1286 Value: []int64{1000},
1287 Label: map[string][]string{
1288 "key1": {"value1", "value2", "value3"},
1289 "key2": {"value1"},
1290 },
1291 },
1292 {
1293 Location: []*Location{cpuL[0]},
1294 Value: []int64{1000},
1295 Label: map[string][]string{
1296 "key1": {"value2"},
1297 },
1298 },
1299 },
1300 setKey: "key1",
1301 setVal: []string{"value1"},
1302 wantLabels: []map[string][]string{
1303 {"key1": {"value1"}},
1304 {"key1": {"value1"}, "key2": {"value1"}},
1305 {"key1": {"value1"}},
1306 },
1307 },
1308 {
1309 desc: "no samples have labels",
1310 samples: []*Sample{
1311 {
1312 Location: []*Location{cpuL[0]},
1313 Value: []int64{1000},
1314 },
1315 },
1316 setKey: "key1",
1317 setVal: []string{"value1"},
1318 wantLabels: []map[string][]string{
1319 {"key1": {"value1"}},
1320 },
1321 },
1322 {
1323 desc: "all samples have some labels, but not key being added",
1324 samples: []*Sample{
1325 {
1326 Location: []*Location{cpuL[0]},
1327 Value: []int64{1000},
1328 Label: map[string][]string{
1329 "key2": {"value2"},
1330 },
1331 },
1332 {
1333 Location: []*Location{cpuL[0]},
1334 Value: []int64{1000},
1335 Label: map[string][]string{
1336 "key3": {"value3"},
1337 },
1338 },
1339 },
1340 setKey: "key1",
1341 setVal: []string{"value1"},
1342 wantLabels: []map[string][]string{
1343 {"key1": {"value1"}, "key2": {"value2"}},
1344 {"key1": {"value1"}, "key3": {"value3"}},
1345 },
1346 },
1347 {
1348 desc: "all samples have key being added",
1349 samples: []*Sample{
1350 {
1351 Location: []*Location{cpuL[0]},
1352 Value: []int64{1000},
1353 Label: map[string][]string{
1354 "key1": {"value1"},
1355 },
1356 },
1357 {
1358 Location: []*Location{cpuL[0]},
1359 Value: []int64{1000},
1360 Label: map[string][]string{
1361 "key1": {"value1"},
1362 },
1363 },
1364 },
1365 setKey: "key1",
1366 setVal: []string{"value1"},
1367 wantLabels: []map[string][]string{
1368 {"key1": {"value1"}},
1369 {"key1": {"value1"}},
1370 },
1371 },
1372 }
1373
1374 for _, tc := range testcases {
1375 t.Run(tc.desc, func(t *testing.T) {
1376 profile := testProfile1.Copy()
1377 profile.Sample = tc.samples
1378 profile.SetLabel(tc.setKey, tc.setVal)
1379 if got, want := len(profile.Sample), len(tc.wantLabels); got != want {
1380 t.Fatalf("got %v samples, want %v samples", got, want)
1381 }
1382 for i, sample := range profile.Sample {
1383 wantLabels := tc.wantLabels[i]
1384 if got, want := len(sample.Label), len(wantLabels); got != want {
1385 t.Errorf("got %v label keys for sample %v, want %v", got, i, want)
1386 continue
1387 }
1388 for wantKey, wantValues := range wantLabels {
1389 if gotValues, ok := sample.Label[wantKey]; ok {
1390 if !reflect.DeepEqual(gotValues, wantValues) {
1391 t.Errorf("for key %s, got values %v, want values %v", wantKey, gotValues, wantValues)
1392 }
1393 } else {
1394 t.Errorf("for key %s got no values, want %v", wantKey, wantValues)
1395 }
1396 }
1397 }
1398 })
1399 }
1400 }
1401
1402 func TestSetNumLabel(t *testing.T) {
1403 var testcases = []struct {
1404 desc string
1405 samples []*Sample
1406 setKey string
1407 setVal []int64
1408 setUnit []string
1409 wantValues []map[string][]int64
1410 wantUnits []map[string][]string
1411 }{
1412 {
1413 desc: "some samples have label already",
1414 samples: []*Sample{
1415 {
1416 Location: []*Location{cpuL[0]},
1417 Value: []int64{1000},
1418 },
1419 {
1420 Location: []*Location{cpuL[0]},
1421 Value: []int64{1000},
1422 NumLabel: map[string][]int64{
1423 "key1": {1, 2, 3},
1424 "key2": {1},
1425 },
1426 NumUnit: map[string][]string{
1427 "key1": {"bytes", "bytes", "bytes"},
1428 "key2": {"gallons"},
1429 },
1430 },
1431 {
1432 Location: []*Location{cpuL[0]},
1433 Value: []int64{1000},
1434 NumLabel: map[string][]int64{
1435 "key1": {2},
1436 },
1437 NumUnit: map[string][]string{
1438 "key1": {"volts"},
1439 },
1440 },
1441 },
1442 setKey: "key1",
1443 setVal: []int64{1},
1444 setUnit: []string{"bytes"},
1445 wantValues: []map[string][]int64{
1446 {"key1": {1}},
1447 {"key1": {1}, "key2": {1}},
1448 {"key1": {1}},
1449 },
1450 wantUnits: []map[string][]string{
1451 {"key1": {"bytes"}},
1452 {"key1": {"bytes"}, "key2": {"gallons"}},
1453 {"key1": {"bytes"}},
1454 },
1455 },
1456 {
1457 desc: "no samples have labels",
1458 samples: []*Sample{
1459 {
1460 Location: []*Location{cpuL[0]},
1461 Value: []int64{1000},
1462 },
1463 },
1464 setKey: "key1",
1465 setVal: []int64{1},
1466 setUnit: []string{"bytes"},
1467 wantValues: []map[string][]int64{
1468 {"key1": {1}},
1469 },
1470 wantUnits: []map[string][]string{
1471 {"key1": {"bytes"}},
1472 },
1473 },
1474 {
1475 desc: "all samples have some labels, but not key being added",
1476 samples: []*Sample{
1477 {
1478 Location: []*Location{cpuL[0]},
1479 Value: []int64{1000},
1480 NumLabel: map[string][]int64{
1481 "key2": {2},
1482 },
1483 NumUnit: map[string][]string{
1484 "key2": {"joules"},
1485 },
1486 },
1487 {
1488 Location: []*Location{cpuL[0]},
1489 Value: []int64{1000},
1490 NumLabel: map[string][]int64{
1491 "key3": {3},
1492 },
1493 NumUnit: map[string][]string{
1494 "key3": {"meters"},
1495 },
1496 },
1497 },
1498 setKey: "key1",
1499 setVal: []int64{1},
1500 setUnit: []string{"seconds"},
1501 wantValues: []map[string][]int64{
1502 {"key1": {1}, "key2": {2}},
1503 {"key1": {1}, "key3": {3}},
1504 },
1505 wantUnits: []map[string][]string{
1506 {"key1": {"seconds"}, "key2": {"joules"}},
1507 {"key1": {"seconds"}, "key3": {"meters"}},
1508 },
1509 },
1510 {
1511 desc: "all samples have key being added",
1512 samples: []*Sample{
1513 {
1514 Location: []*Location{cpuL[0]},
1515 Value: []int64{1000},
1516 NumLabel: map[string][]int64{
1517 "key1": {1},
1518 },
1519 NumUnit: map[string][]string{
1520 "key1": {"exabytes"},
1521 },
1522 },
1523 {
1524 Location: []*Location{cpuL[0]},
1525 Value: []int64{1000},
1526 NumLabel: map[string][]int64{
1527 "key1": {1},
1528 },
1529 NumUnit: map[string][]string{
1530 "key1": {"petabytes"},
1531 },
1532 },
1533 },
1534 setKey: "key1",
1535 setVal: []int64{1, 2},
1536 setUnit: []string{"daltons", ""},
1537 wantValues: []map[string][]int64{
1538 {"key1": {1, 2}},
1539 {"key1": {1, 2}},
1540 },
1541 wantUnits: []map[string][]string{
1542 {"key1": {"daltons", ""}},
1543 {"key1": {"daltons", ""}},
1544 },
1545 },
1546 }
1547
1548 for _, tc := range testcases {
1549 t.Run(tc.desc, func(t *testing.T) {
1550 profile := testProfile1.Copy()
1551 profile.Sample = tc.samples
1552 profile.SetNumLabel(tc.setKey, tc.setVal, tc.setUnit)
1553 if got, want := len(profile.Sample), len(tc.wantValues); got != want {
1554 t.Fatalf("got %v samples, want %v samples", got, want)
1555 }
1556 if got, want := len(profile.Sample), len(tc.wantUnits); got != want {
1557 t.Fatalf("got %v samples, want %v samples", got, want)
1558 }
1559 for i, sample := range profile.Sample {
1560 wantValues := tc.wantValues[i]
1561 if got, want := len(sample.NumLabel), len(wantValues); got != want {
1562 t.Errorf("got %v label values for sample %v, want %v", got, i, want)
1563 continue
1564 }
1565 for key, values := range wantValues {
1566 if gotValues, ok := sample.NumLabel[key]; ok {
1567 if !reflect.DeepEqual(gotValues, values) {
1568 t.Errorf("for key %s, got values %v, want values %v", key, gotValues, values)
1569 }
1570 } else {
1571 t.Errorf("for key %s got no values, want %v", key, values)
1572 }
1573 }
1574
1575 wantUnits := tc.wantUnits[i]
1576 if got, want := len(sample.NumUnit), len(wantUnits); got != want {
1577 t.Errorf("got %v label units for sample %v, want %v", got, i, want)
1578 continue
1579 }
1580 for key, units := range wantUnits {
1581 if gotUnits, ok := sample.NumUnit[key]; ok {
1582 if !reflect.DeepEqual(gotUnits, units) {
1583 t.Errorf("for key %s, got units %v, want units %v", key, gotUnits, units)
1584 }
1585 } else {
1586 t.Errorf("for key %s got no units, want %v", key, units)
1587 }
1588 }
1589 }
1590 })
1591 }
1592 }
1593
1594 func TestRemoveNumLabel(t *testing.T) {
1595 var testcases = []struct {
1596 desc string
1597 samples []*Sample
1598 removeKey string
1599 wantValues []map[string][]int64
1600 wantUnits []map[string][]string
1601 }{
1602 {
1603 desc: "some samples have label already",
1604 samples: []*Sample{
1605 {
1606 Location: []*Location{cpuL[0]},
1607 Value: []int64{1000},
1608 },
1609 {
1610 Location: []*Location{cpuL[0]},
1611 Value: []int64{1000},
1612 NumLabel: map[string][]int64{
1613 "key1": {1, 2, 3},
1614 "key2": {1},
1615 },
1616 NumUnit: map[string][]string{
1617 "key1": {"foo", "bar", "baz"},
1618 "key2": {"seconds"},
1619 },
1620 },
1621 {
1622 Location: []*Location{cpuL[0]},
1623 Value: []int64{1000},
1624 NumLabel: map[string][]int64{
1625 "key1": {2},
1626 },
1627 NumUnit: map[string][]string{
1628 "key1": {"seconds"},
1629 },
1630 },
1631 },
1632 removeKey: "key1",
1633 wantValues: []map[string][]int64{
1634 {},
1635 {"key2": {1}},
1636 {},
1637 },
1638 wantUnits: []map[string][]string{
1639 {},
1640 {"key2": {"seconds"}},
1641 {},
1642 },
1643 },
1644 {
1645 desc: "no samples have label",
1646 samples: []*Sample{
1647 {
1648 Location: []*Location{cpuL[0]},
1649 Value: []int64{1000},
1650 },
1651 },
1652 removeKey: "key1",
1653 wantValues: []map[string][]int64{
1654 {},
1655 },
1656 wantUnits: []map[string][]string{
1657 {},
1658 },
1659 },
1660 {
1661 desc: "all samples have some labels, but not key being removed",
1662 samples: []*Sample{
1663 {
1664 Location: []*Location{cpuL[0]},
1665 Value: []int64{1000},
1666 NumLabel: map[string][]int64{
1667 "key2": {2},
1668 },
1669 NumUnit: map[string][]string{
1670 "key2": {"terabytes"},
1671 },
1672 },
1673 {
1674 Location: []*Location{cpuL[0]},
1675 Value: []int64{1000},
1676 NumLabel: map[string][]int64{
1677 "key3": {3},
1678 },
1679 NumUnit: map[string][]string{
1680 "key3": {""},
1681 },
1682 },
1683 },
1684 removeKey: "key1",
1685 wantValues: []map[string][]int64{
1686 {"key2": {2}},
1687 {"key3": {3}},
1688 },
1689 wantUnits: []map[string][]string{
1690 {"key2": {"terabytes"}},
1691 {"key3": {""}},
1692 },
1693 },
1694 }
1695
1696 for _, tc := range testcases {
1697 t.Run(tc.desc, func(t *testing.T) {
1698 profile := testProfile1.Copy()
1699 profile.Sample = tc.samples
1700 profile.RemoveNumLabel(tc.removeKey)
1701 if got, want := len(profile.Sample), len(tc.wantValues); got != want {
1702 t.Fatalf("got %v samples, want %v values", got, want)
1703 }
1704 if got, want := len(profile.Sample), len(tc.wantUnits); got != want {
1705 t.Fatalf("got %v samples, want %v units", got, want)
1706 }
1707 for i, sample := range profile.Sample {
1708 wantValues := tc.wantValues[i]
1709 if got, want := len(sample.NumLabel), len(wantValues); got != want {
1710 t.Errorf("got %v label values for sample %v, want %v", got, i, want)
1711 continue
1712 }
1713 for key, values := range wantValues {
1714 if gotValues, ok := sample.NumLabel[key]; ok {
1715 if !reflect.DeepEqual(gotValues, values) {
1716 t.Errorf("for key %s, got values %v, want values %v", key, gotValues, values)
1717 }
1718 } else {
1719 t.Errorf("for key %s got no values, want %v", key, values)
1720 }
1721 }
1722 wantUnits := tc.wantUnits[i]
1723 if got, want := len(sample.NumLabel), len(wantUnits); got != want {
1724 t.Errorf("got %v label values for sample %v, want %v", got, i, want)
1725 continue
1726 }
1727 for key, units := range wantUnits {
1728 if gotUnits, ok := sample.NumUnit[key]; ok {
1729 if !reflect.DeepEqual(gotUnits, units) {
1730 t.Errorf("for key %s, got units %v, want units %v", key, gotUnits, units)
1731 }
1732 } else {
1733 t.Errorf("for key %s got no units, want %v", key, units)
1734 }
1735 }
1736 }
1737 })
1738 }
1739 }
1740
1741 func TestNumLabelUnits(t *testing.T) {
1742 var tagFilterTests = []struct {
1743 desc string
1744 tagVals []map[string][]int64
1745 tagUnits []map[string][]string
1746 wantUnits map[string]string
1747 wantIgnoredUnits map[string][]string
1748 }{
1749 {
1750 "One sample, multiple keys, different specified units",
1751 []map[string][]int64{{"key1": {131072}, "key2": {128}}},
1752 []map[string][]string{{"key1": {"bytes"}, "key2": {"kilobytes"}}},
1753 map[string]string{"key1": "bytes", "key2": "kilobytes"},
1754 map[string][]string{},
1755 },
1756 {
1757 "One sample, one key with one value, unit specified",
1758 []map[string][]int64{{"key1": {8}}},
1759 []map[string][]string{{"key1": {"bytes"}}},
1760 map[string]string{"key1": "bytes"},
1761 map[string][]string{},
1762 },
1763 {
1764 "One sample, one key with one value, empty unit specified",
1765 []map[string][]int64{{"key1": {8}}},
1766 []map[string][]string{{"key1": {""}}},
1767 map[string]string{"key1": "key1"},
1768 map[string][]string{},
1769 },
1770 {
1771 "Key bytes, unit not specified",
1772 []map[string][]int64{{"bytes": {8}}},
1773 []map[string][]string{nil},
1774 map[string]string{"bytes": "bytes"},
1775 map[string][]string{},
1776 },
1777 {
1778 "One sample, one key with one value, unit not specified",
1779 []map[string][]int64{{"kilobytes": {8}}},
1780 []map[string][]string{nil},
1781 map[string]string{"kilobytes": "kilobytes"},
1782 map[string][]string{},
1783 },
1784 {
1785 "Key request, unit not specified",
1786 []map[string][]int64{{"request": {8}}},
1787 []map[string][]string{nil},
1788 map[string]string{"request": "bytes"},
1789 map[string][]string{},
1790 },
1791 {
1792 "Key alignment, unit not specified",
1793 []map[string][]int64{{"alignment": {8}}},
1794 []map[string][]string{nil},
1795 map[string]string{"alignment": "bytes"},
1796 map[string][]string{},
1797 },
1798 {
1799 "One sample, one key with multiple values and two different units",
1800 []map[string][]int64{{"key1": {8, 8}}},
1801 []map[string][]string{{"key1": {"bytes", "kilobytes"}}},
1802 map[string]string{"key1": "bytes"},
1803 map[string][]string{"key1": {"kilobytes"}},
1804 },
1805 {
1806 "One sample, one key with multiple values and three different units",
1807 []map[string][]int64{{"key1": {8, 8}}},
1808 []map[string][]string{{"key1": {"bytes", "megabytes", "kilobytes"}}},
1809 map[string]string{"key1": "bytes"},
1810 map[string][]string{"key1": {"kilobytes", "megabytes"}},
1811 },
1812 {
1813 "Two samples, one key, different units specified",
1814 []map[string][]int64{{"key1": {8}}, {"key1": {8}}},
1815 []map[string][]string{{"key1": {"bytes"}}, {"key1": {"kilobytes"}}},
1816 map[string]string{"key1": "bytes"},
1817 map[string][]string{"key1": {"kilobytes"}},
1818 },
1819 {
1820 "Keys alignment, request, and bytes have units specified",
1821 []map[string][]int64{{
1822 "alignment": {8},
1823 "request": {8},
1824 "bytes": {8},
1825 }},
1826 []map[string][]string{{
1827 "alignment": {"seconds"},
1828 "request": {"minutes"},
1829 "bytes": {"hours"},
1830 }},
1831 map[string]string{
1832 "alignment": "seconds",
1833 "request": "minutes",
1834 "bytes": "hours",
1835 },
1836 map[string][]string{},
1837 },
1838 }
1839 for _, test := range tagFilterTests {
1840 p := &Profile{Sample: make([]*Sample, len(test.tagVals))}
1841 for i, numLabel := range test.tagVals {
1842 s := Sample{
1843 NumLabel: numLabel,
1844 NumUnit: test.tagUnits[i],
1845 }
1846 p.Sample[i] = &s
1847 }
1848 units, ignoredUnits := p.NumLabelUnits()
1849 if !reflect.DeepEqual(test.wantUnits, units) {
1850 t.Errorf("%s: got %v units, want %v", test.desc, units, test.wantUnits)
1851 }
1852 if !reflect.DeepEqual(test.wantIgnoredUnits, ignoredUnits) {
1853 t.Errorf("%s: got %v ignored units, want %v", test.desc, ignoredUnits, test.wantIgnoredUnits)
1854 }
1855 }
1856 }
1857
1858 func TestSetMain(t *testing.T) {
1859 testProfile1.massageMappings()
1860 if testProfile1.Mapping[0].File != mainBinary {
1861 t.Errorf("got %s for main", testProfile1.Mapping[0].File)
1862 }
1863 }
1864
1865 func TestParseKernelRelocation(t *testing.T) {
1866 src := testProfile1.Copy()
1867 if src.Mapping[len(src.Mapping)-1].KernelRelocationSymbol != "_text" {
1868 t.Errorf("got %s for Mapping.KernelRelocationSymbol", src.Mapping[0].KernelRelocationSymbol)
1869 }
1870 }
1871
1872
1873 func parallel(n int, fn func()) {
1874 var wg sync.WaitGroup
1875 wg.Add(n)
1876 for i := 0; i < n; i++ {
1877 go func() {
1878 fn()
1879 wg.Done()
1880 }()
1881 }
1882 wg.Wait()
1883 }
1884
1885 func TestThreadSafety(t *testing.T) {
1886 src := testProfile1.Copy()
1887 parallel(4, func() { src.Copy() })
1888 parallel(4, func() {
1889 var b bytes.Buffer
1890 src.WriteUncompressed(&b)
1891 })
1892 parallel(4, func() {
1893 var b bytes.Buffer
1894 src.Write(&b)
1895 })
1896 }
1897
1898 func BenchmarkParse(b *testing.B) {
1899 data := proftest.LargeProfile(b)
1900 b.ResetTimer()
1901 for i := 0; i < b.N; i++ {
1902 _, err := Parse(bytes.NewBuffer(data))
1903 if err != nil {
1904 b.Fatal(err)
1905 }
1906 }
1907 }
1908
1909 func BenchmarkWrite(b *testing.B) {
1910 p, err := Parse(bytes.NewBuffer(proftest.LargeProfile(b)))
1911 if err != nil {
1912 b.Fatal(err)
1913 }
1914 b.ResetTimer()
1915 for i := 0; i < b.N; i++ {
1916 if err := p.WriteUncompressed(io.Discard); err != nil {
1917 b.Fatal(err)
1918 }
1919 }
1920 }
1921
View as plain text