Pārlūkot izejas kodu

Add show_from profile filter. (#372)

lvng 7 gadus atpakaļ
vecāks
revīzija
a50cffb15f

+ 2
- 0
doc/README.md Parādīt failu

94
   *regex*.
94
   *regex*.
95
 * **-ignore= _regex_:** Do not include samples that include a report entry matching
95
 * **-ignore= _regex_:** Do not include samples that include a report entry matching
96
   *regex*.
96
   *regex*.
97
+* **-show\_from= _regex_:** Do not show entries above the first one that 
98
+  matches *regex*.
97
 * **-show= _regex_:** Only show entries that match *regex*.
99
 * **-show= _regex_:** Only show entries that match *regex*.
98
 * **-hide= _regex_:** Do not show entries that match *regex*.
100
 * **-hide= _regex_:** Do not show entries that match *regex*.
99
 
101
 

+ 4
- 0
internal/driver/commands.go Parādīt failu

186
 		"Only show nodes matching regexp",
186
 		"Only show nodes matching regexp",
187
 		"If set, only show nodes that match this location.",
187
 		"If set, only show nodes that match this location.",
188
 		"Matching includes the function name, filename or object name.")},
188
 		"Matching includes the function name, filename or object name.")},
189
+	"show_from": &variable{stringKind, "", "", helpText(
190
+		"Drops functions above the highest matched frame.",
191
+		"If set, all frames above the highest match are dropped from every sample.",
192
+		"Matching includes the function name, filename or object name.")},
189
 	"tagfocus": &variable{stringKind, "", "", helpText(
193
 	"tagfocus": &variable{stringKind, "", "", helpText(
190
 		"Restricts to samples with tags in range or matched by regexp",
194
 		"Restricts to samples with tags in range or matched by regexp",
191
 		"Use name=value syntax to limit the matching to a specific tag.",
195
 		"Use name=value syntax to limit the matching to a specific tag.",

+ 9
- 10
internal/driver/driver.go Parādīt failu

150
 }
150
 }
151
 
151
 
152
 func applyCommandOverrides(cmd []string, v variables) variables {
152
 func applyCommandOverrides(cmd []string, v variables) variables {
153
-	trim, focus, tagfocus, hide := v["trim"].boolValue(), true, true, true
153
+	trim, tagfilter, filter := v["trim"].boolValue(), true, true
154
 
154
 
155
 	switch cmd[0] {
155
 	switch cmd[0] {
156
 	case "proto", "raw":
156
 	case "proto", "raw":
157
-		trim, focus, tagfocus, hide = false, false, false, false
157
+		trim, tagfilter, filter = false, false, false
158
 		v.set("addresses", "t")
158
 		v.set("addresses", "t")
159
 	case "callgrind", "kcachegrind":
159
 	case "callgrind", "kcachegrind":
160
 		trim = false
160
 		trim = false
163
 		trim = false
163
 		trim = false
164
 		v.set("addressnoinlines", "t")
164
 		v.set("addressnoinlines", "t")
165
 	case "peek":
165
 	case "peek":
166
-		trim, focus, hide = false, false, false
166
+		trim, filter = false, false
167
 	case "list":
167
 	case "list":
168
 		v.set("nodecount", "0")
168
 		v.set("nodecount", "0")
169
 		v.set("lines", "t")
169
 		v.set("lines", "t")
181
 		v.set("nodefraction", "0")
181
 		v.set("nodefraction", "0")
182
 		v.set("edgefraction", "0")
182
 		v.set("edgefraction", "0")
183
 	}
183
 	}
184
-	if !focus {
185
-		v.set("focus", "")
186
-		v.set("ignore", "")
187
-	}
188
-	if !tagfocus {
184
+	if !tagfilter {
189
 		v.set("tagfocus", "")
185
 		v.set("tagfocus", "")
190
 		v.set("tagignore", "")
186
 		v.set("tagignore", "")
191
 	}
187
 	}
192
-	if !hide {
188
+	if !filter {
189
+		v.set("focus", "")
190
+		v.set("ignore", "")
193
 		v.set("hide", "")
191
 		v.set("hide", "")
194
 		v.set("show", "")
192
 		v.set("show", "")
193
+		v.set("show_from", "")
195
 	}
194
 	}
196
 	return v
195
 	return v
197
 }
196
 }
242
 	}
241
 	}
243
 
242
 
244
 	var filters []string
243
 	var filters []string
245
-	for _, k := range []string{"focus", "ignore", "hide", "show", "tagfocus", "tagignore", "tagshow", "taghide"} {
244
+	for _, k := range []string{"focus", "ignore", "hide", "show", "show_from", "tagfocus", "tagignore", "tagshow", "taghide"} {
246
 		v := vars[k].value
245
 		v := vars[k].value
247
 		if v != "" {
246
 		if v != "" {
248
 			filters = append(filters, k+"="+v)
247
 			filters = append(filters, k+"="+v)

+ 4
- 0
internal/driver/driver_focus.go Parādīt failu

33
 	ignore, err := compileRegexOption("ignore", v["ignore"].value, err)
33
 	ignore, err := compileRegexOption("ignore", v["ignore"].value, err)
34
 	hide, err := compileRegexOption("hide", v["hide"].value, err)
34
 	hide, err := compileRegexOption("hide", v["hide"].value, err)
35
 	show, err := compileRegexOption("show", v["show"].value, err)
35
 	show, err := compileRegexOption("show", v["show"].value, err)
36
+	showfrom, err := compileRegexOption("show_from", v["show_from"].value, err)
36
 	tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err)
37
 	tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err)
37
 	tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err)
38
 	tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err)
38
 	prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err)
39
 	prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err)
46
 	warnNoMatches(hide == nil || hm, "Hide", ui)
47
 	warnNoMatches(hide == nil || hm, "Hide", ui)
47
 	warnNoMatches(show == nil || hnm, "Show", ui)
48
 	warnNoMatches(show == nil || hnm, "Show", ui)
48
 
49
 
50
+	sfm := prof.ShowFrom(showfrom)
51
+	warnNoMatches(showfrom == nil || sfm, "ShowFrom", ui)
52
+
49
 	tfm, tim := prof.FilterSamplesByTag(tagfocus, tagignore)
53
 	tfm, tim := prof.FilterSamplesByTag(tagfocus, tagignore)
50
 	warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui)
54
 	warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui)
51
 	warnNoMatches(tagignore == nil || tim, "TagIgnore", ui)
55
 	warnNoMatches(tagignore == nil || tim, "TagIgnore", ui)

+ 4
- 0
internal/driver/driver_test.go Parādīt failu

65
 		{"topproto,lines,cum,hide=mangled[X3]0", "cpu"},
65
 		{"topproto,lines,cum,hide=mangled[X3]0", "cpu"},
66
 		{"tree,lines,cum,focus=[24]00", "heap"},
66
 		{"tree,lines,cum,focus=[24]00", "heap"},
67
 		{"tree,relative_percentages,cum,focus=[24]00", "heap"},
67
 		{"tree,relative_percentages,cum,focus=[24]00", "heap"},
68
+		{"tree,lines,cum,show_from=line2", "cpu"},
68
 		{"callgrind", "cpu"},
69
 		{"callgrind", "cpu"},
69
 		{"callgrind,call_tree", "cpu"},
70
 		{"callgrind,call_tree", "cpu"},
70
 		{"callgrind", "heap"},
71
 		{"callgrind", "heap"},
261
 	if f.strings["ignore"] != "" || f.strings["tagignore"] != "" {
262
 	if f.strings["ignore"] != "" || f.strings["tagignore"] != "" {
262
 		name = append(name, "ignore")
263
 		name = append(name, "ignore")
263
 	}
264
 	}
265
+	if f.strings["show_from"] != "" {
266
+		name = append(name, "show_from")
267
+	}
264
 	name = addString(name, f, []string{"hide", "show"})
268
 	name = addString(name, f, []string{"hide", "show"})
265
 	if f.strings["unit"] != "minimum" {
269
 	if f.strings["unit"] != "minimum" {
266
 		name = addString(name, f, []string{"unit"})
270
 		name = addString(name, f, []string{"unit"})

+ 16
- 0
internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from Parādīt failu

1
+Active filters:
2
+   show_from=line2
3
+Showing nodes accounting for 1.01s, 90.18% of 1.12s total
4
+----------------------------------------------------------+-------------
5
+      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 
6
+----------------------------------------------------------+-------------
7
+         0     0%     0%      1.01s 90.18%                | line2000 testdata/file2000.src:4
8
+                                             1.01s   100% |   line2001 testdata/file2000.src:9 (inline)
9
+----------------------------------------------------------+-------------
10
+                                             1.01s   100% |   line2000 testdata/file2000.src:4 (inline)
11
+     0.01s  0.89%  0.89%      1.01s 90.18%                | line2001 testdata/file2000.src:9
12
+                                                1s 99.01% |   line1000 testdata/file1000.src:1
13
+----------------------------------------------------------+-------------
14
+                                                1s   100% |   line2001 testdata/file2000.src:9
15
+        1s 89.29% 90.18%         1s 89.29%                | line1000 testdata/file1000.src:1
16
+----------------------------------------------------------+-------------

+ 65
- 0
profile/filter.go Parādīt failu

74
 	return
74
 	return
75
 }
75
 }
76
 
76
 
77
+// ShowFrom drops all stack frames above the highest matching frame and returns
78
+// whether a match was found. If showFrom is nil it returns false and does not
79
+// modify the profile.
80
+//
81
+// Example: consider a sample with frames [A, B, C, B], where A is the root.
82
+// ShowFrom(nil) returns false and has frames [A, B, C, B].
83
+// ShowFrom(A) returns true and has frames [A, B, C, B].
84
+// ShowFrom(B) returns true and has frames [B, C, B].
85
+// ShowFrom(C) returns true and has frames [C, B].
86
+// ShowFrom(D) returns false and drops the sample because no frames remain.
87
+func (p *Profile) ShowFrom(showFrom *regexp.Regexp) (matched bool) {
88
+	if showFrom == nil {
89
+		return false
90
+	}
91
+	// showFromLocs stores location IDs that matched ShowFrom.
92
+	showFromLocs := make(map[uint64]bool)
93
+	// Apply to locations.
94
+	for _, loc := range p.Location {
95
+		if filterShowFromLocation(loc, showFrom) {
96
+			showFromLocs[loc.ID] = true
97
+			matched = true
98
+		}
99
+	}
100
+	// For all samples, strip locations after the highest matching one.
101
+	s := make([]*Sample, 0, len(p.Sample))
102
+	for _, sample := range p.Sample {
103
+		for i := len(sample.Location) - 1; i >= 0; i-- {
104
+			if showFromLocs[sample.Location[i].ID] {
105
+				sample.Location = sample.Location[:i+1]
106
+				s = append(s, sample)
107
+				break
108
+			}
109
+		}
110
+	}
111
+	p.Sample = s
112
+	return matched
113
+}
114
+
115
+// filterShowFromLocation tests a showFrom regex against a location, removes
116
+// lines after the last match and returns whether a match was found. If the
117
+// mapping is matched, then all lines are kept.
118
+func filterShowFromLocation(loc *Location, showFrom *regexp.Regexp) bool {
119
+	if m := loc.Mapping; m != nil && showFrom.MatchString(m.File) {
120
+		return true
121
+	}
122
+	if i := loc.lastMatchedLineIndex(showFrom); i >= 0 {
123
+		loc.Line = loc.Line[:i+1]
124
+		return true
125
+	}
126
+	return false
127
+}
128
+
129
+// lastMatchedLineIndex returns the index of the last line that matches a regex,
130
+// or -1 if no match is found.
131
+func (loc *Location) lastMatchedLineIndex(re *regexp.Regexp) int {
132
+	for i := len(loc.Line) - 1; i >= 0; i-- {
133
+		if fn := loc.Line[i].Function; fn != nil {
134
+			if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
135
+				return i
136
+			}
137
+		}
138
+	}
139
+	return -1
140
+}
141
+
77
 // FilterTagsByName filters the tags in a profile and only keeps
142
 // FilterTagsByName filters the tags in a profile and only keeps
78
 // tags that match show and not hide.
143
 // tags that match show and not hide.
79
 func (p *Profile) FilterTagsByName(show, hide *regexp.Regexp) (sm, hm bool) {
144
 func (p *Profile) FilterTagsByName(show, hide *regexp.Regexp) (sm, hm bool) {

+ 147
- 1
profile/filter_test.go Parādīt failu

101
 	},
101
 	},
102
 }
102
 }
103
 
103
 
104
-func TestFilter(t *testing.T) {
104
+var emptyLinesLocs = []*Location{
105
+	{ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}},
106
+	{ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{}},
107
+	{ID: 3, Mapping: mappings[1], Address: 0x2000, Line: []Line{}},
108
+}
109
+
110
+var emptyLinesProfile = &Profile{
111
+	TimeNanos:     10000,
112
+	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
113
+	Period:        1,
114
+	DurationNanos: 10e9,
115
+	SampleType:    []*ValueType{{Type: "samples", Unit: "count"}},
116
+	Mapping:       mappings,
117
+	Function:      functions,
118
+	Location:      emptyLinesLocs,
119
+	Sample: []*Sample{
120
+		{Value: []int64{1}, Location: []*Location{emptyLinesLocs[0], emptyLinesLocs[1]}},
121
+		{Value: []int64{2}, Location: []*Location{emptyLinesLocs[2]}},
122
+		{Value: []int64{3}, Location: []*Location{}},
123
+	},
124
+}
125
+
126
+func TestFilterSamplesByName(t *testing.T) {
105
 	for _, tc := range []struct {
127
 	for _, tc := range []struct {
106
 		// name is the name of the test case.
128
 		// name is the name of the test case.
107
 		name string
129
 		name string
392
 	}
414
 	}
393
 }
415
 }
394
 
416
 
417
+func TestShowFrom(t *testing.T) {
418
+	for _, tc := range []struct {
419
+		name     string
420
+		profile  *Profile
421
+		showFrom *regexp.Regexp
422
+		// wantMatch is the expected return value.
423
+		wantMatch bool
424
+		// wantSampleFuncs contains expected stack functions and sample value after
425
+		// filtering, in the same order as in the profile. The format is as
426
+		// returned by sampleFuncs function below, which is "callee caller: <num>".
427
+		wantSampleFuncs []string
428
+	}{
429
+		{
430
+			name:            "nil showFrom keeps all frames",
431
+			profile:         noInlinesProfile,
432
+			wantMatch:       false,
433
+			wantSampleFuncs: allNoInlinesSampleFuncs,
434
+		},
435
+		{
436
+			name:      "showFrom with no matches drops all samples",
437
+			profile:   noInlinesProfile,
438
+			showFrom:  regexp.MustCompile("unknown"),
439
+			wantMatch: false,
440
+		},
441
+		{
442
+			name:      "showFrom matches function names",
443
+			profile:   noInlinesProfile,
444
+			showFrom:  regexp.MustCompile("fun1"),
445
+			wantMatch: true,
446
+			wantSampleFuncs: []string{
447
+				"fun0 fun1: 1",
448
+				"fun4 fun5 fun1: 2",
449
+				"fun9 fun4 fun10: 4",
450
+			},
451
+		},
452
+		{
453
+			name:      "showFrom matches file names",
454
+			profile:   noInlinesProfile,
455
+			showFrom:  regexp.MustCompile("file1"),
456
+			wantMatch: true,
457
+			wantSampleFuncs: []string{
458
+				"fun0 fun1: 1",
459
+				"fun4 fun5 fun1: 2",
460
+				"fun9 fun4 fun10: 4",
461
+			},
462
+		},
463
+		{
464
+			name:      "showFrom matches mapping names",
465
+			profile:   noInlinesProfile,
466
+			showFrom:  regexp.MustCompile("map1"),
467
+			wantMatch: true,
468
+			wantSampleFuncs: []string{
469
+				"fun9 fun4 fun10: 4",
470
+			},
471
+		},
472
+		{
473
+			name:      "showFrom drops frames above highest of multiple matches",
474
+			profile:   noInlinesProfile,
475
+			showFrom:  regexp.MustCompile("fun[12]"),
476
+			wantMatch: true,
477
+			wantSampleFuncs: []string{
478
+				"fun0 fun1 fun2: 1",
479
+				"fun4 fun5 fun1: 2",
480
+				"fun9 fun4 fun10: 4",
481
+			},
482
+		},
483
+		{
484
+			name:      "showFrom matches inline functions",
485
+			profile:   inlinesProfile,
486
+			showFrom:  regexp.MustCompile("fun0|fun5"),
487
+			wantMatch: true,
488
+			wantSampleFuncs: []string{
489
+				"fun0: 1",
490
+				"fun4 fun5: 2",
491
+			},
492
+		},
493
+		{
494
+			name:      "showFrom drops frames above highest of multiple inline matches",
495
+			profile:   inlinesProfile,
496
+			showFrom:  regexp.MustCompile("fun[1245]"),
497
+			wantMatch: true,
498
+			wantSampleFuncs: []string{
499
+				"fun0 fun1 fun2: 1",
500
+				"fun4 fun5: 2",
501
+			},
502
+		},
503
+		{
504
+			name:      "showFrom keeps all lines when matching mapping and function",
505
+			profile:   inlinesProfile,
506
+			showFrom:  regexp.MustCompile("map0|fun5"),
507
+			wantMatch: true,
508
+			wantSampleFuncs: []string{
509
+				"fun0 fun1 fun2 fun3: 1",
510
+				"fun4 fun5 fun6: 2",
511
+			},
512
+		},
513
+		{
514
+			name:      "showFrom matches location with empty lines",
515
+			profile:   emptyLinesProfile,
516
+			showFrom:  regexp.MustCompile("map1"),
517
+			wantMatch: true,
518
+			wantSampleFuncs: []string{
519
+				": 2",
520
+			},
521
+		},
522
+	} {
523
+		t.Run(tc.name, func(t *testing.T) {
524
+			p := tc.profile.Copy()
525
+
526
+			if gotMatch := p.ShowFrom(tc.showFrom); gotMatch != tc.wantMatch {
527
+				t.Errorf("match got %+v, want %+v", gotMatch, tc.wantMatch)
528
+			}
529
+
530
+			if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
531
+				diff, err := proftest.Diff([]byte(want), []byte(got))
532
+				if err != nil {
533
+					t.Fatalf("failed to get diff: %v", err)
534
+				}
535
+				t.Errorf("profile samples got diff(want->got):\n%s", diff)
536
+			}
537
+		})
538
+	}
539
+}
540
+
395
 // sampleFuncs returns a slice of strings where each string represents one
541
 // sampleFuncs returns a slice of strings where each string represents one
396
 // profile sample in the format "<fun1> <fun2> <fun3>: <value>". This allows
542
 // profile sample in the format "<fun1> <fun2> <fun3>: <value>". This allows
397
 // the expected values for test cases to be specifed in human-readable strings.
543
 // the expected values for test cases to be specifed in human-readable strings.