1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package driver
16
17 import (
18 "bytes"
19 "flag"
20 "fmt"
21 "net"
22 _ "net/http/pprof"
23 "os"
24 "reflect"
25 "regexp"
26 "runtime"
27 "strconv"
28 "strings"
29 "testing"
30 "time"
31
32 "github.com/google/pprof/internal/plugin"
33 "github.com/google/pprof/internal/proftest"
34 "github.com/google/pprof/internal/symbolz"
35 "github.com/google/pprof/profile"
36 )
37
38 var updateFlag = flag.Bool("update", false, "Update the golden files")
39
40 func TestParse(t *testing.T) {
41
42 pprofCommands["weblist"].postProcess = nil
43
44
45
46
47
48 savePath := os.Getenv("PPROF_BINARY_PATH")
49 os.Setenv("PPROF_BINARY_PATH", "/path/to")
50 defer os.Setenv("PPROF_BINARY_PATH", savePath)
51 testcase := []struct {
52 flags, source string
53 }{
54 {"text,functions,flat", "cpu"},
55 {"text,functions,noinlines,flat", "cpu"},
56 {"text,filefunctions,noinlines,flat", "cpu"},
57 {"text,addresses,noinlines,flat", "cpu"},
58 {"tree,addresses,flat,nodecount=4", "cpusmall"},
59 {"text,functions,flat,nodecount=5,call_tree", "unknown"},
60 {"text,alloc_objects,flat", "heap_alloc"},
61 {"text,files,flat", "heap"},
62 {"text,files,flat,focus=[12]00,taghide=[X3]00", "heap"},
63 {"text,inuse_objects,flat", "heap"},
64 {"text,lines,cum,hide=line[X3]0", "cpu"},
65 {"text,lines,cum,show=[12]00", "cpu"},
66 {"text,lines,cum,hide=line[X3]0,focus=[12]00", "cpu"},
67 {"topproto,lines,cum,hide=mangled[X3]0", "cpu"},
68 {"topproto,lines", "cpu"},
69 {"tree,lines,cum,focus=[24]00", "heap"},
70 {"tree,relative_percentages,cum,focus=[24]00", "heap"},
71 {"tree,lines,cum,show_from=line2", "cpu"},
72 {"callgrind", "cpu"},
73 {"callgrind,call_tree", "cpu"},
74 {"callgrind", "heap"},
75 {"dot,functions,flat", "cpu"},
76 {"dot,functions,flat,call_tree", "cpu"},
77 {"dot,lines,flat,focus=[12]00", "heap"},
78 {"dot,unit=minimum", "heap_sizetags"},
79 {"dot,addresses,flat,ignore=[X3]002,focus=[X1]000", "contention"},
80 {"dot,files,cum", "contention"},
81 {"comments,add_comment=some-comment", "cpu"},
82 {"comments", "heap"},
83 {"tags", "cpu"},
84 {"tags,tagignore=tag[13],tagfocus=key[12]", "cpu"},
85 {"tags", "heap"},
86 {"tags,unit=bytes", "heap"},
87 {"traces", "cpu"},
88 {"traces,addresses", "cpu"},
89 {"traces", "heap_tags"},
90 {"dot,alloc_space,flat,focus=[234]00", "heap_alloc"},
91 {"dot,alloc_space,flat,tagshow=[2]00", "heap_alloc"},
92 {"dot,alloc_space,flat,hide=line.*1?23?", "heap_alloc"},
93 {"dot,inuse_space,flat,tagfocus=1mb:2gb", "heap"},
94 {"dot,inuse_space,flat,tagfocus=30kb:,tagignore=1mb:2mb", "heap"},
95 {"disasm=line[13],addresses,flat", "cpu"},
96 {"peek=line.*01", "cpu"},
97 {"weblist=line(1000|3000)$,addresses,flat", "cpu"},
98 {"tags,tagfocus=400kb:", "heap_request"},
99 {"tags,tagfocus=+400kb:", "heap_request"},
100 {"dot", "long_name_funcs"},
101 {"text", "long_name_funcs"},
102 }
103
104 baseConfig := currentConfig()
105 defer setCurrentConfig(baseConfig)
106 for _, tc := range testcase {
107 t.Run(tc.flags+":"+tc.source, func(t *testing.T) {
108
109 setCurrentConfig(baseConfig)
110
111 testUI := &proftest.TestUI{T: t, AllowRx: "Generating report in|Ignoring local file|expression matched no samples|Interpreted .* as range, not regexp"}
112
113 f := baseFlags()
114 f.args = []string{tc.source}
115
116 flags := strings.Split(tc.flags, ",")
117
118
119 protoTempFile, err := os.CreateTemp("", "profile_proto")
120 if err != nil {
121 t.Errorf("cannot create tempfile: %v", err)
122 }
123 defer os.Remove(protoTempFile.Name())
124 defer protoTempFile.Close()
125 f.strings["output"] = protoTempFile.Name()
126
127 if flags[0] == "topproto" {
128 f.bools["proto"] = false
129 f.bools["topproto"] = true
130 f.bools["addresses"] = true
131 }
132
133
134
135
136 o1 := setDefaults(&plugin.Options{Flagset: f})
137 o1.Fetch = testFetcher{}
138 o1.Sym = testSymbolizer{}
139 o1.UI = testUI
140 if err := PProf(o1); err != nil {
141 t.Fatalf("%s %q: %v", tc.source, tc.flags, err)
142 }
143
144 setCurrentConfig(baseConfig)
145
146
147 outputTempFile, err := os.CreateTemp("", "profile_output")
148 if err != nil {
149 t.Errorf("cannot create tempfile: %v", err)
150 }
151 defer os.Remove(outputTempFile.Name())
152 defer outputTempFile.Close()
153
154 f = baseFlags()
155 f.strings["output"] = outputTempFile.Name()
156 f.args = []string{protoTempFile.Name()}
157
158 delete(f.bools, "proto")
159 addFlags(&f, flags)
160 solution := solutionFilename(tc.source, &f)
161
162
163 if flags[0] == "topproto" {
164 addFlags(&f, flags)
165 solution = solutionFilename(tc.source, &f)
166 delete(f.bools, "topproto")
167 f.bools["text"] = true
168 }
169
170
171
172
173
174 o2 := setDefaults(&plugin.Options{Flagset: f})
175 o2.Sym = testSymbolizeDemangler{}
176 o2.Obj = new(mockObjTool)
177 o2.UI = testUI
178
179 if err := PProf(o2); err != nil {
180 t.Errorf("%s: %v", tc.source, err)
181 }
182 b, err := os.ReadFile(outputTempFile.Name())
183 if err != nil {
184 t.Errorf("Failed to read profile %s: %v", outputTempFile.Name(), err)
185 }
186
187
188 solution = "testdata/" + solution
189 sbuf, err := os.ReadFile(solution)
190 if err != nil {
191 t.Fatalf("reading solution file %s: %v", solution, err)
192 }
193 if runtime.GOOS == "windows" {
194 if flags[0] == "dot" {
195
196 sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte(`testdata\\`), -1)
197 sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte(`\\path\\to\\`), -1)
198 } else {
199 sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte(`testdata\`), -1)
200 sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte(`\path\to\`), -1)
201 }
202 }
203
204 if flags[0] == "svg" {
205 b = removeScripts(b)
206 sbuf = removeScripts(sbuf)
207 }
208
209 if string(b) != string(sbuf) {
210 t.Errorf("diff %s %s", solution, tc.source)
211 d, err := proftest.Diff(sbuf, b)
212 if err != nil {
213 t.Fatalf("diff %s %v", solution, err)
214 }
215 t.Errorf("%s\n%s\n", solution, d)
216 if *updateFlag {
217 err := os.WriteFile(solution, b, 0644)
218 if err != nil {
219 t.Errorf("failed to update the solution file %q: %v", solution, err)
220 }
221 }
222 }
223 })
224 }
225 }
226
227
228 func removeScripts(in []byte) []byte {
229 beginMarker := []byte("<script")
230 endMarker := []byte("</script>")
231
232 if begin := bytes.Index(in, beginMarker); begin > 0 {
233 if end := bytes.Index(in[begin:], endMarker); end > 0 {
234 in = append(in[:begin], removeScripts(in[begin+end+len(endMarker):])...)
235 }
236 }
237 return in
238 }
239
240
241 func addFlags(f *testFlags, flags []string) {
242 for _, flag := range flags {
243 fields := strings.SplitN(flag, "=", 2)
244 switch len(fields) {
245 case 1:
246 f.bools[fields[0]] = true
247 case 2:
248 if i, err := strconv.Atoi(fields[1]); err == nil {
249 f.ints[fields[0]] = i
250 } else {
251 f.strings[fields[0]] = fields[1]
252 }
253 }
254 }
255 }
256
257 func testSourceURL(port int) string {
258 return fmt.Sprintf("http://%s/", net.JoinHostPort(testSourceAddress, strconv.Itoa(port)))
259 }
260
261
262 func solutionFilename(source string, f *testFlags) string {
263 name := []string{"pprof", strings.TrimPrefix(source, testSourceURL(8000))}
264 name = addString(name, f, []string{"flat", "cum"})
265 name = addString(name, f, []string{"functions", "filefunctions", "files", "lines", "addresses"})
266 name = addString(name, f, []string{"noinlines"})
267 name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"})
268 name = addString(name, f, []string{"relative_percentages"})
269 name = addString(name, f, []string{"seconds"})
270 name = addString(name, f, []string{"call_tree"})
271 name = addString(name, f, []string{"text", "tree", "callgrind", "dot", "svg", "tags", "dot", "traces", "disasm", "peek", "weblist", "topproto", "comments"})
272 if f.strings["focus"] != "" || f.strings["tagfocus"] != "" {
273 name = append(name, "focus")
274 }
275 if f.strings["ignore"] != "" || f.strings["tagignore"] != "" {
276 name = append(name, "ignore")
277 }
278 if f.strings["show_from"] != "" {
279 name = append(name, "show_from")
280 }
281 name = addString(name, f, []string{"hide", "show"})
282 if f.strings["unit"] != "minimum" {
283 name = addString(name, f, []string{"unit"})
284 }
285 return strings.Join(name, ".")
286 }
287
288 func addString(name []string, f *testFlags, components []string) []string {
289 for _, c := range components {
290 if f.bools[c] || f.strings[c] != "" || f.ints[c] != 0 {
291 return append(name, c)
292 }
293 }
294 return name
295 }
296
297
298 type testFlags struct {
299 bools map[string]bool
300 ints map[string]int
301 floats map[string]float64
302 strings map[string]string
303 args []string
304 stringLists map[string][]string
305 }
306
307 func (testFlags) ExtraUsage() string { return "" }
308
309 func (testFlags) AddExtraUsage(eu string) {}
310
311 func (f testFlags) Bool(s string, d bool, c string) *bool {
312 if b, ok := f.bools[s]; ok {
313 return &b
314 }
315 return &d
316 }
317
318 func (f testFlags) Int(s string, d int, c string) *int {
319 if i, ok := f.ints[s]; ok {
320 return &i
321 }
322 return &d
323 }
324
325 func (f testFlags) Float64(s string, d float64, c string) *float64 {
326 if g, ok := f.floats[s]; ok {
327 return &g
328 }
329 return &d
330 }
331
332 func (f testFlags) String(s, d, c string) *string {
333 if t, ok := f.strings[s]; ok {
334 return &t
335 }
336 return &d
337 }
338
339 func (f testFlags) StringList(s, d, c string) *[]*string {
340 if t, ok := f.stringLists[s]; ok {
341
342 tp := make([]*string, len(t))
343 for i, v := range t {
344 tp[i] = &v
345 }
346 return &tp
347 }
348 return &[]*string{}
349 }
350
351 func (f testFlags) Parse(func()) []string {
352 return f.args
353 }
354
355 func baseFlags() testFlags {
356 return testFlags{
357 bools: map[string]bool{
358 "proto": true,
359 "trim": true,
360 "compact_labels": true,
361 },
362 ints: map[string]int{
363 "nodecount": 20,
364 },
365 floats: map[string]float64{
366 "nodefraction": 0.05,
367 "edgefraction": 0.01,
368 "divide_by": 1.0,
369 },
370 strings: map[string]string{
371 "unit": "minimum",
372 },
373 }
374 }
375
376 const testStart = 0x1000
377 const testOffset = 0x5000
378
379 type testFetcher struct{}
380
381 func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {
382 var p *profile.Profile
383 switch s {
384 case "cpu", "unknown":
385 p = cpuProfile()
386 case "cpusmall":
387 p = cpuProfileSmall()
388 case "heap":
389 p = heapProfile()
390 case "heap_alloc":
391 p = heapProfile()
392 p.SampleType = []*profile.ValueType{
393 {Type: "alloc_objects", Unit: "count"},
394 {Type: "alloc_space", Unit: "bytes"},
395 }
396 case "heap_request":
397 p = heapProfile()
398 for _, s := range p.Sample {
399 s.NumLabel["request"] = s.NumLabel["bytes"]
400 }
401 case "heap_sizetags":
402 p = heapProfile()
403 tags := []int64{2, 4, 8, 16, 32, 64, 128, 256}
404 for _, s := range p.Sample {
405 numValues := append(s.NumLabel["bytes"], tags...)
406 s.NumLabel["bytes"] = numValues
407 }
408 case "heap_tags":
409 p = heapProfile()
410 for i := 0; i < len(p.Sample); i += 2 {
411 s := p.Sample[i]
412 if s.Label == nil {
413 s.Label = make(map[string][]string)
414 }
415 s.NumLabel["request"] = s.NumLabel["bytes"]
416 s.Label["key1"] = []string{"tag"}
417 }
418 case "contention":
419 p = contentionProfile()
420 case "symbolz":
421 p = symzProfile()
422 case "long_name_funcs":
423 p = longNameFuncsProfile()
424 default:
425 return nil, "", fmt.Errorf("unexpected source: %s", s)
426 }
427 return p, testSourceURL(8000) + s, nil
428 }
429
430 type testSymbolizer struct{}
431
432 func (testSymbolizer) Symbolize(_ string, _ plugin.MappingSources, _ *profile.Profile) error {
433 return nil
434 }
435
436 type testSymbolizeDemangler struct{}
437
438 func (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *profile.Profile) error {
439 for _, fn := range p.Function {
440 if fn.Name == "" || fn.SystemName == fn.Name {
441 fn.Name = fakeDemangler(fn.SystemName)
442 }
443 }
444 return nil
445 }
446
447 func testFetchSymbols(source, post string) ([]byte, error) {
448 var buf bytes.Buffer
449
450 switch source {
451 case testSourceURL(8000) + "symbolz":
452 for _, address := range strings.Split(post, "+") {
453 a, _ := strconv.ParseInt(address, 0, 64)
454 fmt.Fprintf(&buf, "%v\t", address)
455 if a-testStart > testOffset {
456 fmt.Fprintf(&buf, "wrong_source_%v_", address)
457 continue
458 }
459 fmt.Fprintf(&buf, "%#x\n", a-testStart)
460 }
461 return buf.Bytes(), nil
462 case testSourceURL(8001) + "symbolz":
463 for _, address := range strings.Split(post, "+") {
464 a, _ := strconv.ParseInt(address, 0, 64)
465 fmt.Fprintf(&buf, "%v\t", address)
466 if a-testStart < testOffset {
467 fmt.Fprintf(&buf, "wrong_source_%v_", address)
468 continue
469 }
470 fmt.Fprintf(&buf, "%#x\n", a-testStart-testOffset)
471 }
472 return buf.Bytes(), nil
473 default:
474 return nil, fmt.Errorf("unexpected source: %s", source)
475 }
476 }
477
478 type testSymbolzSymbolizer struct{}
479
480 func (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error {
481 return symbolz.Symbolize(p, false, sources, testFetchSymbols, nil)
482 }
483
484 func fakeDemangler(name string) string {
485 switch name {
486 case "mangled1000":
487 return "line1000"
488 case "mangled2000":
489 return "line2000"
490 case "mangled2001":
491 return "line2001"
492 case "mangled3000":
493 return "line3000"
494 case "mangled3001":
495 return "line3001"
496 case "mangled3002":
497 return "line3002"
498 case "mangledNEW":
499 return "operator new"
500 case "mangledMALLOC":
501 return "malloc"
502 default:
503 return name
504 }
505 }
506
507
508
509 func longNameFuncsProfile() *profile.Profile {
510 var longNameFuncsM = []*profile.Mapping{
511 {
512 ID: 1,
513 Start: 0x1000,
514 Limit: 0x4000,
515 File: "/path/to/testbinary",
516 HasFunctions: true,
517 HasFilenames: true,
518 HasLineNumbers: true,
519 HasInlineFrames: true,
520 },
521 }
522
523 var longNameFuncsF = []*profile.Function{
524 {ID: 1, Name: "path/to/package1.object.function1", SystemName: "path/to/package1.object.function1", Filename: "path/to/package1.go"},
525 {ID: 2, Name: "(anonymous namespace)::Bar::Foo", SystemName: "(anonymous namespace)::Bar::Foo", Filename: "a/long/path/to/package2.cc"},
526 {ID: 3, Name: "java.bar.foo.FooBar.run(java.lang.Runnable)", SystemName: "java.bar.foo.FooBar.run(java.lang.Runnable)", Filename: "FooBar.java"},
527 }
528
529 var longNameFuncsL = []*profile.Location{
530 {
531 ID: 1000,
532 Mapping: longNameFuncsM[0],
533 Address: 0x1000,
534 Line: []profile.Line{
535 {Function: longNameFuncsF[0], Line: 1},
536 },
537 },
538 {
539 ID: 2000,
540 Mapping: longNameFuncsM[0],
541 Address: 0x2000,
542 Line: []profile.Line{
543 {Function: longNameFuncsF[1], Line: 4},
544 },
545 },
546 {
547 ID: 3000,
548 Mapping: longNameFuncsM[0],
549 Address: 0x3000,
550 Line: []profile.Line{
551 {Function: longNameFuncsF[2], Line: 9},
552 },
553 },
554 }
555
556 return &profile.Profile{
557 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
558 Period: 1,
559 DurationNanos: 10e9,
560 SampleType: []*profile.ValueType{
561 {Type: "samples", Unit: "count"},
562 {Type: "cpu", Unit: "milliseconds"},
563 },
564 Sample: []*profile.Sample{
565 {
566 Location: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1], longNameFuncsL[2]},
567 Value: []int64{1000, 1000},
568 },
569 {
570 Location: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1]},
571 Value: []int64{100, 100},
572 },
573 {
574 Location: []*profile.Location{longNameFuncsL[2]},
575 Value: []int64{10, 10},
576 },
577 },
578 Location: longNameFuncsL,
579 Function: longNameFuncsF,
580 Mapping: longNameFuncsM,
581 }
582 }
583
584 func cpuProfile() *profile.Profile {
585 var cpuM = []*profile.Mapping{
586 {
587 ID: 1,
588 Start: 0x1000,
589 Limit: 0x4000,
590 File: "/path/to/testbinary",
591 HasFunctions: true,
592 HasFilenames: true,
593 HasLineNumbers: true,
594 HasInlineFrames: true,
595 },
596 }
597
598 var cpuF = []*profile.Function{
599 {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
600 {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
601 {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
602 {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
603 {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
604 {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
605 }
606
607 var cpuL = []*profile.Location{
608 {
609 ID: 1000,
610 Mapping: cpuM[0],
611 Address: 0x1000,
612 Line: []profile.Line{
613 {Function: cpuF[0], Line: 1},
614 },
615 },
616 {
617 ID: 2000,
618 Mapping: cpuM[0],
619 Address: 0x2000,
620 Line: []profile.Line{
621 {Function: cpuF[2], Line: 9},
622 {Function: cpuF[1], Line: 4},
623 },
624 },
625 {
626 ID: 3000,
627 Mapping: cpuM[0],
628 Address: 0x3000,
629 Line: []profile.Line{
630 {Function: cpuF[5], Line: 2},
631 {Function: cpuF[4], Line: 5},
632 {Function: cpuF[3], Line: 6},
633 },
634 },
635 {
636 ID: 3001,
637 Mapping: cpuM[0],
638 Address: 0x3001,
639 Line: []profile.Line{
640 {Function: cpuF[4], Line: 8},
641 {Function: cpuF[3], Line: 9},
642 },
643 },
644 {
645 ID: 3002,
646 Mapping: cpuM[0],
647 Address: 0x3002,
648 Line: []profile.Line{
649 {Function: cpuF[5], Line: 5},
650 {Function: cpuF[3], Line: 9},
651 },
652 },
653 }
654
655 return &profile.Profile{
656 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
657 Period: 1,
658 DurationNanos: 10e9,
659 SampleType: []*profile.ValueType{
660 {Type: "samples", Unit: "count"},
661 {Type: "cpu", Unit: "milliseconds"},
662 },
663 Sample: []*profile.Sample{
664 {
665 Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
666 Value: []int64{1000, 1000},
667 Label: map[string][]string{
668 "key1": {"tag1"},
669 "key2": {"tag1"},
670 },
671 },
672 {
673 Location: []*profile.Location{cpuL[0], cpuL[3]},
674 Value: []int64{100, 100},
675 Label: map[string][]string{
676 "key1": {"tag2"},
677 "key3": {"tag2"},
678 },
679 },
680 {
681 Location: []*profile.Location{cpuL[1], cpuL[4]},
682 Value: []int64{10, 10},
683 Label: map[string][]string{
684 "key1": {"tag3"},
685 "key2": {"tag2"},
686 },
687 },
688 {
689 Location: []*profile.Location{cpuL[2]},
690 Value: []int64{10, 10},
691 Label: map[string][]string{
692 "key1": {"tag4"},
693 "key2": {"tag1"},
694 },
695 },
696 },
697 Location: cpuL,
698 Function: cpuF,
699 Mapping: cpuM,
700 }
701 }
702
703 func cpuProfileSmall() *profile.Profile {
704 var cpuM = []*profile.Mapping{
705 {
706 ID: 1,
707 Start: 0x1000,
708 Limit: 0x4000,
709 File: "/path/to/testbinary",
710 HasFunctions: true,
711 HasFilenames: true,
712 HasLineNumbers: true,
713 HasInlineFrames: true,
714 },
715 }
716
717 var cpuL = []*profile.Location{
718 {
719 ID: 1000,
720 Mapping: cpuM[0],
721 Address: 0x1000,
722 },
723 {
724 ID: 2000,
725 Mapping: cpuM[0],
726 Address: 0x2000,
727 },
728 {
729 ID: 3000,
730 Mapping: cpuM[0],
731 Address: 0x3000,
732 },
733 {
734 ID: 4000,
735 Mapping: cpuM[0],
736 Address: 0x4000,
737 },
738 {
739 ID: 5000,
740 Mapping: cpuM[0],
741 Address: 0x5000,
742 },
743 }
744
745 return &profile.Profile{
746 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
747 Period: 1,
748 DurationNanos: 10e9,
749 SampleType: []*profile.ValueType{
750 {Type: "samples", Unit: "count"},
751 {Type: "cpu", Unit: "milliseconds"},
752 },
753 Sample: []*profile.Sample{
754 {
755 Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
756 Value: []int64{1000, 1000},
757 },
758 {
759 Location: []*profile.Location{cpuL[3], cpuL[1], cpuL[4]},
760 Value: []int64{1000, 1000},
761 },
762 {
763 Location: []*profile.Location{cpuL[2]},
764 Value: []int64{1000, 1000},
765 },
766 {
767 Location: []*profile.Location{cpuL[4]},
768 Value: []int64{1000, 1000},
769 },
770 },
771 Location: cpuL,
772 Function: nil,
773 Mapping: cpuM,
774 }
775 }
776
777 func heapProfile() *profile.Profile {
778 var heapM = []*profile.Mapping{
779 {
780 ID: 1,
781 BuildID: "buildid",
782 Start: 0x1000,
783 Limit: 0x4000,
784 HasFunctions: true,
785 HasFilenames: true,
786 HasLineNumbers: true,
787 HasInlineFrames: true,
788 },
789 }
790
791 var heapF = []*profile.Function{
792 {ID: 1, Name: "pruneme", SystemName: "pruneme", Filename: "prune.h"},
793 {ID: 2, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
794 {ID: 3, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
795 {ID: 4, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
796 {ID: 5, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
797 {ID: 6, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
798 {ID: 7, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
799 {ID: 8, Name: "mangledMALLOC", SystemName: "mangledMALLOC", Filename: "malloc.h"},
800 {ID: 9, Name: "mangledNEW", SystemName: "mangledNEW", Filename: "new.h"},
801 }
802
803 var heapL = []*profile.Location{
804 {
805 ID: 1000,
806 Mapping: heapM[0],
807 Address: 0x1000,
808 Line: []profile.Line{
809 {Function: heapF[0], Line: 100},
810 {Function: heapF[7], Line: 100},
811 {Function: heapF[1], Line: 1},
812 },
813 },
814 {
815 ID: 2000,
816 Mapping: heapM[0],
817 Address: 0x2000,
818 Line: []profile.Line{
819 {Function: heapF[8], Line: 100},
820 {Function: heapF[3], Line: 2},
821 {Function: heapF[2], Line: 3},
822 },
823 },
824 {
825 ID: 3000,
826 Mapping: heapM[0],
827 Address: 0x3000,
828 Line: []profile.Line{
829 {Function: heapF[8], Line: 100},
830 {Function: heapF[6], Line: 3},
831 {Function: heapF[5], Line: 2},
832 {Function: heapF[4], Line: 4},
833 },
834 },
835 {
836 ID: 3001,
837 Mapping: heapM[0],
838 Address: 0x3001,
839 Line: []profile.Line{
840 {Function: heapF[0], Line: 100},
841 {Function: heapF[8], Line: 100},
842 {Function: heapF[5], Line: 2},
843 {Function: heapF[4], Line: 4},
844 },
845 },
846 {
847 ID: 3002,
848 Mapping: heapM[0],
849 Address: 0x3002,
850 Line: []profile.Line{
851 {Function: heapF[6], Line: 3},
852 {Function: heapF[4], Line: 4},
853 },
854 },
855 }
856
857 return &profile.Profile{
858 Comments: []string{"comment", "#hidden comment"},
859 PeriodType: &profile.ValueType{Type: "allocations", Unit: "bytes"},
860 Period: 524288,
861 SampleType: []*profile.ValueType{
862 {Type: "inuse_objects", Unit: "count"},
863 {Type: "inuse_space", Unit: "bytes"},
864 },
865 Sample: []*profile.Sample{
866 {
867 Location: []*profile.Location{heapL[0], heapL[1], heapL[2]},
868 Value: []int64{10, 1024000},
869 NumLabel: map[string][]int64{"bytes": {102400}},
870 },
871 {
872 Location: []*profile.Location{heapL[0], heapL[3]},
873 Value: []int64{20, 4096000},
874 NumLabel: map[string][]int64{"bytes": {204800}},
875 },
876 {
877 Location: []*profile.Location{heapL[1], heapL[4]},
878 Value: []int64{40, 65536000},
879 NumLabel: map[string][]int64{"bytes": {1638400}},
880 },
881 {
882 Location: []*profile.Location{heapL[2]},
883 Value: []int64{80, 32768000},
884 NumLabel: map[string][]int64{"bytes": {409600}},
885 },
886 },
887 DropFrames: ".*operator new.*|malloc",
888 Location: heapL,
889 Function: heapF,
890 Mapping: heapM,
891 }
892 }
893
894 func contentionProfile() *profile.Profile {
895 var contentionM = []*profile.Mapping{
896 {
897 ID: 1,
898 BuildID: "buildid-contention",
899 Start: 0x1000,
900 Limit: 0x4000,
901 HasFunctions: true,
902 HasFilenames: true,
903 HasLineNumbers: true,
904 HasInlineFrames: true,
905 },
906 }
907
908 var contentionF = []*profile.Function{
909 {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
910 {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
911 {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
912 {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
913 {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
914 {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
915 }
916
917 var contentionL = []*profile.Location{
918 {
919 ID: 1000,
920 Mapping: contentionM[0],
921 Address: 0x1000,
922 Line: []profile.Line{
923 {Function: contentionF[0], Line: 1},
924 },
925 },
926 {
927 ID: 2000,
928 Mapping: contentionM[0],
929 Address: 0x2000,
930 Line: []profile.Line{
931 {Function: contentionF[2], Line: 2},
932 {Function: contentionF[1], Line: 3},
933 },
934 },
935 {
936 ID: 3000,
937 Mapping: contentionM[0],
938 Address: 0x3000,
939 Line: []profile.Line{
940 {Function: contentionF[5], Line: 2},
941 {Function: contentionF[4], Line: 3},
942 {Function: contentionF[3], Line: 5},
943 },
944 },
945 {
946 ID: 3001,
947 Mapping: contentionM[0],
948 Address: 0x3001,
949 Line: []profile.Line{
950 {Function: contentionF[4], Line: 3},
951 {Function: contentionF[3], Line: 5},
952 },
953 },
954 {
955 ID: 3002,
956 Mapping: contentionM[0],
957 Address: 0x3002,
958 Line: []profile.Line{
959 {Function: contentionF[5], Line: 4},
960 {Function: contentionF[3], Line: 3},
961 },
962 },
963 }
964
965 return &profile.Profile{
966 PeriodType: &profile.ValueType{Type: "contentions", Unit: "count"},
967 Period: 524288,
968 SampleType: []*profile.ValueType{
969 {Type: "contentions", Unit: "count"},
970 {Type: "delay", Unit: "nanoseconds"},
971 },
972 Sample: []*profile.Sample{
973 {
974 Location: []*profile.Location{contentionL[0], contentionL[1], contentionL[2]},
975 Value: []int64{10, 10240000},
976 },
977 {
978 Location: []*profile.Location{contentionL[0], contentionL[3]},
979 Value: []int64{20, 40960000},
980 },
981 {
982 Location: []*profile.Location{contentionL[1], contentionL[4]},
983 Value: []int64{40, 65536000},
984 },
985 {
986 Location: []*profile.Location{contentionL[2]},
987 Value: []int64{80, 32768000},
988 },
989 },
990 Location: contentionL,
991 Function: contentionF,
992 Mapping: contentionM,
993 Comments: []string{"Comment #1", "Comment #2"},
994 }
995 }
996
997 func symzProfile() *profile.Profile {
998 var symzM = []*profile.Mapping{
999 {
1000 ID: 1,
1001 Start: testStart,
1002 Limit: 0x4000,
1003 File: "/path/to/testbinary",
1004 },
1005 }
1006
1007 var symzL = []*profile.Location{
1008 {ID: 1, Mapping: symzM[0], Address: testStart},
1009 {ID: 2, Mapping: symzM[0], Address: testStart + 0x1000},
1010 {ID: 3, Mapping: symzM[0], Address: testStart + 0x2000},
1011 }
1012
1013 return &profile.Profile{
1014 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
1015 Period: 1,
1016 DurationNanos: 10e9,
1017 SampleType: []*profile.ValueType{
1018 {Type: "samples", Unit: "count"},
1019 {Type: "cpu", Unit: "milliseconds"},
1020 },
1021 Sample: []*profile.Sample{
1022 {
1023 Location: []*profile.Location{symzL[0], symzL[1], symzL[2]},
1024 Value: []int64{1, 1},
1025 },
1026 },
1027 Location: symzL,
1028 Mapping: symzM,
1029 }
1030 }
1031
1032 func largeProfile(tb testing.TB) *profile.Profile {
1033 tb.Helper()
1034 input := proftest.LargeProfile(tb)
1035 prof, err := profile.Parse(bytes.NewBuffer(input))
1036 if err != nil {
1037 tb.Fatal(err)
1038 }
1039 return prof
1040 }
1041
1042 var autoCompleteTests = []struct {
1043 in string
1044 out string
1045 }{
1046 {"", ""},
1047 {"xyz", "xyz"},
1048 {"dis", "disasm"},
1049 {"t", "t"},
1050 {"top abc", "top abc"},
1051 {"top mangledM", "top mangledMALLOC"},
1052 {"top cmd cmd mangledM", "top cmd cmd mangledMALLOC"},
1053 {"top mangled", "top mangled"},
1054 {"cmd mangledM", "cmd mangledM"},
1055 {"top mangledM cmd", "top mangledM cmd"},
1056 {"top edMA", "top mangledMALLOC"},
1057 {"top -mangledM", "top -mangledMALLOC"},
1058 {"lin", "lines"},
1059 {"EdGeF", "edgefraction"},
1060 {"help dis", "help disasm"},
1061 {"help relative_perc", "help relative_percentages"},
1062 {"help coMpa", "help compact_labels"},
1063 }
1064
1065 func TestAutoComplete(t *testing.T) {
1066 complete := newCompleter(functionNames(heapProfile()))
1067
1068 for _, test := range autoCompleteTests {
1069 if out := complete(test.in); out != test.out {
1070 t.Errorf("autoComplete(%s) = %s; want %s", test.in, out, test.out)
1071 }
1072 }
1073 }
1074
1075 func TestTagFilter(t *testing.T) {
1076 var tagFilterTests = []struct {
1077 desc, value string
1078 tags map[string][]string
1079 want bool
1080 }{
1081 {
1082 "1 key with 1 matching value",
1083 "tag2",
1084 map[string][]string{"value1": {"tag1", "tag2"}},
1085 true,
1086 },
1087 {
1088 "1 key with no matching values",
1089 "tag3",
1090 map[string][]string{"value1": {"tag1", "tag2"}},
1091 false,
1092 },
1093 {
1094 "two keys, each with value matching different one value in list",
1095 "tag1,tag3",
1096 map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}},
1097 true,
1098 },
1099 {"two keys, all value matching different regex value in list",
1100 "t..[12],t..3",
1101 map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}},
1102 true,
1103 },
1104 {
1105 "one key, not all values in list matched",
1106 "tag2,tag3",
1107 map[string][]string{"value1": {"tag1", "tag2"}},
1108 false,
1109 },
1110 {
1111 "key specified, list of tags where all tags in list matched",
1112 "key1=tag1,tag2",
1113 map[string][]string{"key1": {"tag1", "tag2"}},
1114 true,
1115 },
1116 {"key specified, list of tag values where not all are matched",
1117 "key1=tag1,tag2",
1118 map[string][]string{"key1": {"tag1"}},
1119 true,
1120 },
1121 {
1122 "key included for regex matching, list of values where all values in list matched",
1123 "key1:tag1,tag2",
1124 map[string][]string{"key1": {"tag1", "tag2"}},
1125 true,
1126 },
1127 {
1128 "key included for regex matching, list of values where not only second value matched",
1129 "key1:tag1,tag2",
1130 map[string][]string{"key1": {"tag2"}},
1131 false,
1132 },
1133 {
1134 "key included for regex matching, list of values where not only first value matched",
1135 "key1:tag1,tag2",
1136 map[string][]string{"key1": {"tag1"}},
1137 false,
1138 },
1139 }
1140 for _, test := range tagFilterTests {
1141 t.Run(test.desc, func(t *testing.T) {
1142 filter, err := compileTagFilter(test.desc, test.value, nil, &proftest.TestUI{T: t}, nil)
1143 if err != nil {
1144 t.Fatalf("tagFilter %s:%v", test.desc, err)
1145 }
1146 s := profile.Sample{
1147 Label: test.tags,
1148 }
1149 if got := filter(&s); got != test.want {
1150 t.Errorf("tagFilter %s: got %v, want %v", test.desc, got, test.want)
1151 }
1152 })
1153 }
1154 }
1155
1156 func TestIdentifyNumLabelUnits(t *testing.T) {
1157 var tagFilterTests = []struct {
1158 desc string
1159 tagVals []map[string][]int64
1160 tagUnits []map[string][]string
1161 wantUnits map[string]string
1162 allowedRx string
1163 wantIgnoreErrCount int
1164 }{
1165 {
1166 "Multiple keys, no units for all keys",
1167 []map[string][]int64{{"keyA": {131072}, "keyB": {128}}},
1168 []map[string][]string{{"keyA": {}, "keyB": {""}}},
1169 map[string]string{"keyA": "keyA", "keyB": "keyB"},
1170 "",
1171 0,
1172 },
1173 {
1174 "Multiple keys, different units for each key",
1175 []map[string][]int64{{"keyA": {131072}, "keyB": {128}}},
1176 []map[string][]string{{"keyA": {"bytes"}, "keyB": {"kilobytes"}}},
1177 map[string]string{"keyA": "bytes", "keyB": "kilobytes"},
1178 "",
1179 0,
1180 },
1181 {
1182 "Multiple keys with multiple values, different units for each key",
1183 []map[string][]int64{{"keyC": {131072, 1}, "keyD": {128, 252}}},
1184 []map[string][]string{{"keyC": {"bytes", "bytes"}, "keyD": {"kilobytes", "kilobytes"}}},
1185 map[string]string{"keyC": "bytes", "keyD": "kilobytes"},
1186 "",
1187 0,
1188 },
1189 {
1190 "Multiple keys with multiple values, some units missing",
1191 []map[string][]int64{{"key1": {131072, 1}, "A": {128, 252}, "key3": {128}, "key4": {1}}, {"key3": {128}, "key4": {1}}},
1192 []map[string][]string{{"key1": {"", "bytes"}, "A": {"kilobytes", ""}, "key3": {""}, "key4": {"hour"}}, {"key3": {"seconds"}, "key4": {""}}},
1193 map[string]string{"key1": "bytes", "A": "kilobytes", "key3": "seconds", "key4": "hour"},
1194 "",
1195 0,
1196 },
1197 {
1198 "One key with three units in same sample",
1199 []map[string][]int64{{"key": {8, 8, 16}}},
1200 []map[string][]string{{"key": {"bytes", "megabytes", "kilobytes"}}},
1201 map[string]string{"key": "bytes"},
1202 `(For tag key used unit bytes, also encountered unit\(s\) kilobytes, megabytes)`,
1203 1,
1204 },
1205 {
1206 "One key with four units in same sample",
1207 []map[string][]int64{{"key": {8, 8, 16, 32}}},
1208 []map[string][]string{{"key": {"bytes", "kilobytes", "a", "megabytes"}}},
1209 map[string]string{"key": "bytes"},
1210 `(For tag key used unit bytes, also encountered unit\(s\) a, kilobytes, megabytes)`,
1211 1,
1212 },
1213 {
1214 "One key with two units in same sample",
1215 []map[string][]int64{{"key": {8, 8}}},
1216 []map[string][]string{{"key": {"bytes", "seconds"}}},
1217 map[string]string{"key": "bytes"},
1218 `(For tag key used unit bytes, also encountered unit\(s\) seconds)`,
1219 1,
1220 },
1221 {
1222 "One key with different units in different samples",
1223 []map[string][]int64{{"key1": {8}}, {"key1": {8}}, {"key1": {8}}},
1224 []map[string][]string{{"key1": {"bytes"}}, {"key1": {"kilobytes"}}, {"key1": {"megabytes"}}},
1225 map[string]string{"key1": "bytes"},
1226 `(For tag key1 used unit bytes, also encountered unit\(s\) kilobytes, megabytes)`,
1227 1,
1228 },
1229 {
1230 "Key alignment, unit not specified",
1231 []map[string][]int64{{"alignment": {8}}},
1232 []map[string][]string{nil},
1233 map[string]string{"alignment": "bytes"},
1234 "",
1235 0,
1236 },
1237 {
1238 "Key request, unit not specified",
1239 []map[string][]int64{{"request": {8}}, {"request": {8, 8}}},
1240 []map[string][]string{nil, nil},
1241 map[string]string{"request": "bytes"},
1242 "",
1243 0,
1244 },
1245 {
1246 "Check units not over-written for keys with default units",
1247 []map[string][]int64{{
1248 "alignment": {8},
1249 "request": {8},
1250 "bytes": {8},
1251 }},
1252 []map[string][]string{{
1253 "alignment": {"seconds"},
1254 "request": {"minutes"},
1255 "bytes": {"hours"},
1256 }},
1257 map[string]string{
1258 "alignment": "seconds",
1259 "request": "minutes",
1260 "bytes": "hours",
1261 },
1262 "",
1263 0,
1264 },
1265 }
1266 for _, test := range tagFilterTests {
1267 t.Run(test.desc, func(t *testing.T) {
1268 p := profile.Profile{Sample: make([]*profile.Sample, len(test.tagVals))}
1269 for i, numLabel := range test.tagVals {
1270 s := profile.Sample{
1271 NumLabel: numLabel,
1272 NumUnit: test.tagUnits[i],
1273 }
1274 p.Sample[i] = &s
1275 }
1276 testUI := &proftest.TestUI{T: t, AllowRx: test.allowedRx}
1277 units := identifyNumLabelUnits(&p, testUI)
1278 if !reflect.DeepEqual(test.wantUnits, units) {
1279 t.Errorf("got %v units, want %v", units, test.wantUnits)
1280 }
1281 if got, want := testUI.NumAllowRxMatches, test.wantIgnoreErrCount; want != got {
1282 t.Errorf("got %d errors logged, want %d errors logged", got, want)
1283 }
1284 })
1285 }
1286 }
1287
1288 func TestNumericTagFilter(t *testing.T) {
1289 var tagFilterTests = []struct {
1290 desc, value string
1291 tags map[string][]int64
1292 identifiedUnits map[string]string
1293 want bool
1294 }{
1295 {
1296 "Match when unit conversion required",
1297 "128kb",
1298 map[string][]int64{"key1": {131072}, "key2": {128}},
1299 map[string]string{"key1": "bytes", "key2": "kilobytes"},
1300 true,
1301 },
1302 {
1303 "Match only when values equal after unit conversion",
1304 "512kb",
1305 map[string][]int64{"key1": {512}, "key2": {128}},
1306 map[string]string{"key1": "bytes", "key2": "kilobytes"},
1307 false,
1308 },
1309 {
1310 "Match when values and units initially equal",
1311 "10bytes",
1312 map[string][]int64{"key1": {10}, "key2": {128}},
1313 map[string]string{"key1": "bytes", "key2": "kilobytes"},
1314 true,
1315 },
1316 {
1317 "Match range without lower bound, no unit conversion required",
1318 ":10bytes",
1319 map[string][]int64{"key1": {8}},
1320 map[string]string{"key1": "bytes"},
1321 true,
1322 },
1323 {
1324 "Match range without lower bound, unit conversion required",
1325 ":10kb",
1326 map[string][]int64{"key1": {8}},
1327 map[string]string{"key1": "bytes"},
1328 true,
1329 },
1330 {
1331 "Match range without upper bound, unit conversion required",
1332 "10b:",
1333 map[string][]int64{"key1": {8}},
1334 map[string]string{"key1": "kilobytes"},
1335 true,
1336 },
1337 {
1338 "Match range without upper bound, no unit conversion required",
1339 "10b:",
1340 map[string][]int64{"key1": {12}},
1341 map[string]string{"key1": "bytes"},
1342 true,
1343 },
1344 {
1345 "Don't match range without upper bound, no unit conversion required",
1346 "10b:",
1347 map[string][]int64{"key1": {8}},
1348 map[string]string{"key1": "bytes"},
1349 false,
1350 },
1351 {
1352 "Multiple keys with different units, don't match range without upper bound",
1353 "10kb:",
1354 map[string][]int64{"key1": {8}},
1355 map[string]string{"key1": "bytes", "key2": "kilobytes"},
1356 false,
1357 },
1358 {
1359 "Match range without upper bound, unit conversion required",
1360 "10b:",
1361 map[string][]int64{"key1": {8}},
1362 map[string]string{"key1": "kilobytes"},
1363 true,
1364 },
1365 {
1366 "Don't match range without lower bound, no unit conversion required",
1367 ":10b",
1368 map[string][]int64{"key1": {12}},
1369 map[string]string{"key1": "bytes"},
1370 false,
1371 },
1372 {
1373 "Match specific key, key present, one of two values match",
1374 "bytes=5b",
1375 map[string][]int64{"bytes": {10, 5}},
1376 map[string]string{"bytes": "bytes"},
1377 true,
1378 },
1379 {
1380 "Match specific key, key present and value matches",
1381 "bytes=1024b",
1382 map[string][]int64{"bytes": {1024}},
1383 map[string]string{"bytes": "kilobytes"},
1384 false,
1385 },
1386 {
1387 "Match specific key, matching key present and value matches, also non-matching key",
1388 "bytes=1024b",
1389 map[string][]int64{"bytes": {1024}, "key2": {5}},
1390 map[string]string{"bytes": "bytes", "key2": "bytes"},
1391 true,
1392 },
1393 {
1394 "Match specific key and range of values, value matches",
1395 "bytes=512b:1024b",
1396 map[string][]int64{"bytes": {780}},
1397 map[string]string{"bytes": "bytes"},
1398 true,
1399 },
1400 {
1401 "Match specific key and range of values, value too large",
1402 "key1=1kb:2kb",
1403 map[string][]int64{"key1": {4096}},
1404 map[string]string{"key1": "bytes"},
1405 false,
1406 },
1407 {
1408 "Match specific key and range of values, value too small",
1409 "key1=1kb:2kb",
1410 map[string][]int64{"key1": {256}},
1411 map[string]string{"key1": "bytes"},
1412 false,
1413 },
1414 {
1415 "Match specific key and value, unit conversion required",
1416 "bytes=1024b",
1417 map[string][]int64{"bytes": {1}},
1418 map[string]string{"bytes": "kilobytes"},
1419 true,
1420 },
1421 {
1422 "Match specific key and value, key does not appear",
1423 "key2=256bytes",
1424 map[string][]int64{"key1": {256}},
1425 map[string]string{"key1": "bytes"},
1426 false,
1427 },
1428 {
1429 "Match negative key and range of values, value matches",
1430 "bytes=-512b:-128b",
1431 map[string][]int64{"bytes": {-256}},
1432 map[string]string{"bytes": "bytes"},
1433 true,
1434 },
1435 {
1436 "Match negative key and range of values, value outside range",
1437 "bytes=-512b:-128b",
1438 map[string][]int64{"bytes": {-2048}},
1439 map[string]string{"bytes": "bytes"},
1440 false,
1441 },
1442 {
1443 "Match exact value, unitless tag",
1444 "pid=123",
1445 map[string][]int64{"pid": {123}},
1446 nil,
1447 true,
1448 },
1449 {
1450 "Match range, unitless tag",
1451 "pid=123:123",
1452 map[string][]int64{"pid": {123}},
1453 nil,
1454 true,
1455 },
1456 {
1457 "Don't match range, unitless tag",
1458 "pid=124:124",
1459 map[string][]int64{"pid": {123}},
1460 nil,
1461 false,
1462 },
1463 {
1464 "Match range without upper bound, unitless tag",
1465 "pid=100:",
1466 map[string][]int64{"pid": {123}},
1467 nil,
1468 true,
1469 },
1470 {
1471 "Don't match range without upper bound, unitless tag",
1472 "pid=200:",
1473 map[string][]int64{"pid": {123}},
1474 nil,
1475 false,
1476 },
1477 {
1478 "Match range without lower bound, unitless tag",
1479 "pid=:200",
1480 map[string][]int64{"pid": {123}},
1481 nil,
1482 true,
1483 },
1484 {
1485 "Don't match range without lower bound, unitless tag",
1486 "pid=:100",
1487 map[string][]int64{"pid": {123}},
1488 nil,
1489 false,
1490 },
1491 }
1492 for _, test := range tagFilterTests {
1493 t.Run(test.desc, func(t *testing.T) {
1494 wantErrMsg := strings.Join([]string{"(", test.desc, ":Interpreted '", test.value[strings.Index(test.value, "=")+1:], "' as range, not regexp", ")"}, "")
1495 filter, err := compileTagFilter(test.desc, test.value, test.identifiedUnits, &proftest.TestUI{T: t,
1496 AllowRx: wantErrMsg}, nil)
1497 if err != nil {
1498 t.Fatalf("%v", err)
1499 }
1500 s := profile.Sample{
1501 NumLabel: test.tags,
1502 }
1503 if got := filter(&s); got != test.want {
1504 t.Fatalf("got %v, want %v", got, test.want)
1505 }
1506 })
1507 }
1508 }
1509
1510
1511
1512 func TestOptionsHaveHelp(t *testing.T) {
1513 for _, f := range configFields {
1514
1515 names := f.choices
1516 if len(names) == 0 {
1517 names = []string{f.name}
1518 }
1519 for _, name := range names {
1520 if _, ok := configHelp[name]; !ok {
1521 t.Errorf("missing help message for %q", name)
1522 }
1523 }
1524 }
1525 }
1526
1527 type testSymbolzMergeFetcher struct{}
1528
1529 func (testSymbolzMergeFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {
1530 var p *profile.Profile
1531 switch s {
1532 case testSourceURL(8000) + "symbolz":
1533 p = symzProfile()
1534 case testSourceURL(8001) + "symbolz":
1535 p = symzProfile()
1536 p.Mapping[0].Start += testOffset
1537 p.Mapping[0].Limit += testOffset
1538 for i := range p.Location {
1539 p.Location[i].Address += testOffset
1540 }
1541 default:
1542 return nil, "", fmt.Errorf("unexpected source: %s", s)
1543 }
1544 return p, s, nil
1545 }
1546
1547 func TestSymbolzAfterMerge(t *testing.T) {
1548 baseConfig := currentConfig()
1549 defer setCurrentConfig(baseConfig)
1550
1551 f := baseFlags()
1552 f.args = []string{
1553 testSourceURL(8000) + "symbolz",
1554 testSourceURL(8001) + "symbolz",
1555 }
1556
1557 o := setDefaults(nil)
1558 o.Flagset = f
1559 o.Obj = new(mockObjTool)
1560 src, cmd, err := parseFlags(o)
1561 if err != nil {
1562 t.Fatalf("parseFlags: %v", err)
1563 }
1564
1565 if len(cmd) != 1 || cmd[0] != "proto" {
1566 t.Fatalf("parseFlags returned command %v, want [proto]", cmd)
1567 }
1568
1569 o.Fetch = testSymbolzMergeFetcher{}
1570 o.Sym = testSymbolzSymbolizer{}
1571 p, err := fetchProfiles(src, o)
1572 if err != nil {
1573 t.Fatalf("fetchProfiles: %v", err)
1574 }
1575 if len(p.Location) != 3 {
1576 t.Errorf("Got %d locations after merge, want %d", len(p.Location), 3)
1577 }
1578 for i, l := range p.Location {
1579 if len(l.Line) != 1 {
1580 t.Errorf("Number of lines for symbolz %#x in iteration %d, got %d, want %d", l.Address, i, len(l.Line), 1)
1581 continue
1582 }
1583 address := l.Address - l.Mapping.Start
1584 if got, want := l.Line[0].Function.Name, fmt.Sprintf("%#x", address); got != want {
1585 t.Errorf("symbolz %#x, got %s, want %s", address, got, want)
1586 }
1587 }
1588 }
1589
1590 func TestProfileCopier(t *testing.T) {
1591 type testCase struct {
1592 name string
1593 prof *profile.Profile
1594 }
1595 for _, c := range []testCase{
1596 {"cpu", cpuProfile()},
1597 {"heap", heapProfile()},
1598 {"contention", contentionProfile()},
1599 {"symbolz", symzProfile()},
1600 {"long_name_funcs", longNameFuncsProfile()},
1601 {"large", largeProfile(t)},
1602 } {
1603 t.Run(c.name, func(t *testing.T) {
1604 copier := makeProfileCopier(c.prof)
1605
1606
1607 tmp := copier.newCopy()
1608 tmp.Sample = tmp.Sample[:0]
1609
1610
1611 want := c.prof.String()
1612 got := copier.newCopy().String()
1613 if got != want {
1614 t.Errorf("New copy is not same as original profile")
1615 diff, err := proftest.Diff([]byte(want), []byte(got))
1616 if err != nil {
1617 t.Fatalf("Diff: %v", err)
1618 }
1619 t.Logf("Diff:\n%s\n", string(diff))
1620 }
1621 })
1622 }
1623 }
1624
1625 type mockObjTool struct{}
1626
1627 func (*mockObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {
1628 return &mockFile{file, "abcdef", 0}, nil
1629 }
1630
1631 func (m *mockObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
1632 const fn1 = "line1000"
1633 const fn3 = "line3000"
1634 const file1 = "testdata/file1000.src"
1635 const file3 = "testdata/file3000.src"
1636 data := []plugin.Inst{
1637 {Addr: 0x1000, Text: "instruction one", Function: fn1, File: file1, Line: 1},
1638 {Addr: 0x1001, Text: "instruction two", Function: fn1, File: file1, Line: 1},
1639 {Addr: 0x1002, Text: "instruction three", Function: fn1, File: file1, Line: 2},
1640 {Addr: 0x1003, Text: "instruction four", Function: fn1, File: file1, Line: 1},
1641 {Addr: 0x3000, Text: "instruction one", Function: fn3, File: file3},
1642 {Addr: 0x3001, Text: "instruction two", Function: fn3, File: file3},
1643 {Addr: 0x3002, Text: "instruction three", Function: fn3, File: file3},
1644 {Addr: 0x3003, Text: "instruction four", Function: fn3, File: file3},
1645 {Addr: 0x3004, Text: "instruction five", Function: fn3, File: file3},
1646 }
1647 var result []plugin.Inst
1648 for _, inst := range data {
1649 if inst.Addr >= start && inst.Addr <= end {
1650 result = append(result, inst)
1651 }
1652 }
1653 return result, nil
1654 }
1655
1656 type mockFile struct {
1657 name, buildID string
1658 base uint64
1659 }
1660
1661
1662 func (m *mockFile) Name() string {
1663 return m.name
1664 }
1665
1666
1667 func (m *mockFile) ObjAddr(addr uint64) (uint64, error) {
1668 return addr - m.base, nil
1669 }
1670
1671
1672 func (m *mockFile) BuildID() string {
1673 return m.buildID
1674 }
1675
1676
1677
1678
1679
1680 func (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
1681
1682
1683 frame := func(fn, file string, num int) plugin.Frame {
1684
1685 return plugin.Frame{Func: fn, File: file, Line: num, Column: num}
1686 }
1687 switch addr {
1688 case 0x1000:
1689 return []plugin.Frame{
1690 frame("mangled1000", "testdata/file1000.src", 1),
1691 }, nil
1692 case 0x1001:
1693 return []plugin.Frame{
1694 frame("mangled1000", "testdata/file1000.src", 1),
1695 }, nil
1696 case 0x1002:
1697 return []plugin.Frame{
1698 frame("mangled1000", "testdata/file1000.src", 2),
1699 }, nil
1700 case 0x1003:
1701 return []plugin.Frame{
1702 frame("mangled1000", "testdata/file1000.src", 1),
1703 }, nil
1704 case 0x2000:
1705 return []plugin.Frame{
1706 frame("mangled2001", "testdata/file2000.src", 9),
1707 frame("mangled2000", "testdata/file2000.src", 4),
1708 }, nil
1709 case 0x3000:
1710 return []plugin.Frame{
1711 frame("mangled3002", "testdata/file3000.src", 2),
1712 frame("mangled3001", "testdata/file3000.src", 5),
1713 frame("mangled3000", "testdata/file3000.src", 6),
1714 }, nil
1715 case 0x3001:
1716 return []plugin.Frame{
1717 frame("mangled3001", "testdata/file3000.src", 8),
1718 frame("mangled3000", "testdata/file3000.src", 9),
1719 }, nil
1720 case 0x3002:
1721 return []plugin.Frame{
1722 frame("mangled3002", "testdata/file3000.src", 5),
1723 frame("mangled3000", "testdata/file3000.src", 9),
1724 }, nil
1725 }
1726
1727 return nil, nil
1728 }
1729
1730
1731
1732
1733
1734
1735 func (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
1736 switch r.String() {
1737 case "line[13]":
1738 return []*plugin.Sym{
1739 {
1740 Name: []string{"line1000"}, File: m.name,
1741 Start: 0x1000, End: 0x1003,
1742 },
1743 {
1744 Name: []string{"line3000"}, File: m.name,
1745 Start: 0x3000, End: 0x3004,
1746 },
1747 }, nil
1748 }
1749 return nil, fmt.Errorf("unimplemented")
1750 }
1751
1752
1753 func (*mockFile) Close() error {
1754 return nil
1755 }
1756
View as plain text