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