Kaynağa Gözat

Simplify function names in graph and flame views. (#424)

simplify function names in graph and flame
Margaret Nolan 6 yıl önce
ebeveyn
işleme
45cef52822

+ 81
- 0
internal/driver/driver_test.go Dosyayı Görüntüle

@@ -96,6 +96,8 @@ func TestParse(t *testing.T) {
96 96
 		{"peek=line.*01", "cpu"},
97 97
 		{"weblist=line[13],addresses,flat", "cpu"},
98 98
 		{"tags,tagfocus=400kb:", "heap_request"},
99
+		{"dot", "longNameFuncs"},
100
+		{"text", "longNameFuncs"},
99 101
 	}
100 102
 
101 103
 	baseVars := pprofVariables
@@ -438,6 +440,8 @@ func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string
438 440
 		p = contentionProfile()
439 441
 	case "symbolz":
440 442
 		p = symzProfile()
443
+	case "longNameFuncs":
444
+		p = longNameFuncsProfile()
441 445
 	default:
442 446
 		return nil, "", fmt.Errorf("unexpected source: %s", s)
443 447
 	}
@@ -521,6 +525,83 @@ func fakeDemangler(name string) string {
521 525
 	}
522 526
 }
523 527
 
528
+// Returns a profile with function names which should be shortened in
529
+// graph and flame views.
530
+func longNameFuncsProfile() *profile.Profile {
531
+	var longNameFuncsM = []*profile.Mapping{
532
+		{
533
+			ID:              1,
534
+			Start:           0x1000,
535
+			Limit:           0x4000,
536
+			File:            "/path/to/testbinary",
537
+			HasFunctions:    true,
538
+			HasFilenames:    true,
539
+			HasLineNumbers:  true,
540
+			HasInlineFrames: true,
541
+		},
542
+	}
543
+
544
+	var longNameFuncsF = []*profile.Function{
545
+		{ID: 1, Name: "path/to/package1.object.function1", SystemName: "path/to/package1.object.function1", Filename: "path/to/package1.go"},
546
+		{ID: 2, Name: "(anonymous namespace)::Bar::Foo", SystemName: "(anonymous namespace)::Bar::Foo", Filename: "a/long/path/to/package2.cc"},
547
+		{ID: 3, Name: "java.bar.foo.FooBar.run(java.lang.Runnable)", SystemName: "java.bar.foo.FooBar.run(java.lang.Runnable)", Filename: "FooBar.java"},
548
+	}
549
+
550
+	var longNameFuncsL = []*profile.Location{
551
+		{
552
+			ID:      1000,
553
+			Mapping: longNameFuncsM[0],
554
+			Address: 0x1000,
555
+			Line: []profile.Line{
556
+				{Function: longNameFuncsF[0], Line: 1},
557
+			},
558
+		},
559
+		{
560
+			ID:      2000,
561
+			Mapping: longNameFuncsM[0],
562
+			Address: 0x2000,
563
+			Line: []profile.Line{
564
+				{Function: longNameFuncsF[1], Line: 4},
565
+			},
566
+		},
567
+		{
568
+			ID:      3000,
569
+			Mapping: longNameFuncsM[0],
570
+			Address: 0x3000,
571
+			Line: []profile.Line{
572
+				{Function: longNameFuncsF[2], Line: 9},
573
+			},
574
+		},
575
+	}
576
+
577
+	return &profile.Profile{
578
+		PeriodType:    &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
579
+		Period:        1,
580
+		DurationNanos: 10e9,
581
+		SampleType: []*profile.ValueType{
582
+			{Type: "samples", Unit: "count"},
583
+			{Type: "cpu", Unit: "milliseconds"},
584
+		},
585
+		Sample: []*profile.Sample{
586
+			{
587
+				Location: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1], longNameFuncsL[2]},
588
+				Value:    []int64{1000, 1000},
589
+			},
590
+			{
591
+				Location: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1]},
592
+				Value:    []int64{100, 100},
593
+			},
594
+			{
595
+				Location: []*profile.Location{longNameFuncsL[2]},
596
+				Value:    []int64{10, 10},
597
+			},
598
+		},
599
+		Location: longNameFuncsL,
600
+		Function: longNameFuncsF,
601
+		Mapping:  longNameFuncsM,
602
+	}
603
+}
604
+
524 605
 func cpuProfile() *profile.Profile {
525 606
 	var cpuM = []*profile.Mapping{
526 607
 		{

+ 1
- 17
internal/driver/flamegraph.go Dosyayı Görüntüle

@@ -55,7 +55,7 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
55 55
 		v := n.CumValue()
56 56
 		fullName := n.Info.PrintableName()
57 57
 		node := &treeNode{
58
-			Name:      getNodeShortName(fullName),
58
+			Name:      graph.ShortenFunctionName(fullName),
59 59
 			FullName:  fullName,
60 60
 			Cum:       v,
61 61
 			CumFormat: config.FormatValue(v),
@@ -101,19 +101,3 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
101 101
 		Nodes:      nodeArr,
102 102
 	})
103 103
 }
104
-
105
-// getNodeShortName builds a short node name from fullName.
106
-func getNodeShortName(name string) string {
107
-	chunks := strings.SplitN(name, "(", 2)
108
-	head := chunks[0]
109
-	pathSep := strings.LastIndexByte(head, '/')
110
-	if pathSep == -1 || pathSep+1 >= len(head) {
111
-		return name
112
-	}
113
-	// Check if name is a stdlib package, i.e. doesn't have "." before "/"
114
-	if dot := strings.IndexByte(head, '.'); dot == -1 || dot > pathSep {
115
-		return name
116
-	}
117
-	// Trim package path prefix from node name
118
-	return name[pathSep+1:]
119
-}

+ 0
- 46
internal/driver/flamegraph_test.go Dosyayı Görüntüle

@@ -1,46 +0,0 @@
1
-package driver
2
-
3
-import "testing"
4
-
5
-func TestGetNodeShortName(t *testing.T) {
6
-	type testCase struct {
7
-		name string
8
-		want string
9
-	}
10
-	testcases := []testCase{
11
-		{
12
-			"root",
13
-			"root",
14
-		},
15
-		{
16
-			"syscall.Syscall",
17
-			"syscall.Syscall",
18
-		},
19
-		{
20
-			"net/http.(*conn).serve",
21
-			"net/http.(*conn).serve",
22
-		},
23
-		{
24
-			"github.com/blah/foo.Foo",
25
-			"foo.Foo",
26
-		},
27
-		{
28
-			"github.com/blah/foo_bar.(*FooBar).Foo",
29
-			"foo_bar.(*FooBar).Foo",
30
-		},
31
-		{
32
-			"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
33
-			"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
34
-		},
35
-		{
36
-			"github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
37
-			"redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
38
-		},
39
-	}
40
-	for _, tc := range testcases {
41
-		name := getNodeShortName(tc.name)
42
-		if got, want := name, tc.want; got != want {
43
-			t.Errorf("for %s, got %q, want %q", tc.name, got, want)
44
-		}
45
-	}
46
-}

+ 9
- 0
internal/driver/testdata/pprof.longNameFuncs.dot Dosyayı Görüntüle

@@ -0,0 +1,9 @@
1
+digraph "testbinary" {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.11s (11.10%)\lShowing nodes accounting for 1.11s, 100% of 1.11s total\l" tooltip="testbinary"] }
4
+N1 [label="package1\nobject\nfunction1\n1.10s (99.10%)" id="node1" fontsize=24 shape=box tooltip="path/to/package1.object.function1 (1.10s)" color="#b20000" fillcolor="#edd5d5"]
5
+N2 [label="FooBar\nrun\n0.01s (0.9%)\nof 1.01s (90.99%)" id="node2" fontsize=10 shape=box tooltip="java.bar.foo.FooBar.run(java.lang.Runnable) (1.01s)" color="#b20400" fillcolor="#edd6d5"]
6
+N3 [label="Bar\nFoo\n0 of 1.10s (99.10%)" id="node3" fontsize=8 shape=box tooltip="(anonymous namespace)::Bar::Foo (1.10s)" color="#b20000" fillcolor="#edd5d5"]
7
+N3 -> N1 [label=" 1.10s" weight=100 penwidth=5 color="#b20000" tooltip="(anonymous namespace)::Bar::Foo -> path/to/package1.object.function1 (1.10s)" labeltooltip="(anonymous namespace)::Bar::Foo -> path/to/package1.object.function1 (1.10s)"]
8
+N2 -> N3 [label=" 1s" weight=91 penwidth=5 color="#b20500" tooltip="java.bar.foo.FooBar.run(java.lang.Runnable) -> (anonymous namespace)::Bar::Foo (1s)" labeltooltip="java.bar.foo.FooBar.run(java.lang.Runnable) -> (anonymous namespace)::Bar::Foo (1s)"]
9
+}

+ 5
- 0
internal/driver/testdata/pprof.longNameFuncs.text Dosyayı Görüntüle

@@ -0,0 +1,5 @@
1
+Showing nodes accounting for 1.11s, 100% of 1.11s total
2
+      flat  flat%   sum%        cum   cum%
3
+     1.10s 99.10% 99.10%      1.10s 99.10%  path/to/package1.object.function1
4
+     0.01s   0.9%   100%      1.01s 90.99%  java.bar.foo.FooBar.run(java.lang.Runnable)
5
+         0     0%   100%      1.10s 99.10%  (anonymous namespace)::Bar::Foo

+ 1
- 0
internal/graph/dotgraph.go Dosyayı Görüntüle

@@ -382,6 +382,7 @@ func dotColor(score float64, isBackground bool) string {
382 382
 
383 383
 func multilinePrintableName(info *NodeInfo) string {
384 384
 	infoCopy := *info
385
+	infoCopy.Name = ShortenFunctionName(infoCopy.Name)
385 386
 	infoCopy.Name = strings.Replace(infoCopy.Name, "::", `\n`, -1)
386 387
 	infoCopy.Name = strings.Replace(infoCopy.Name, ".", `\n`, -1)
387 388
 	if infoCopy.File != "" {

+ 17
- 0
internal/graph/graph.go Dosyayı Görüntüle

@@ -19,6 +19,7 @@ import (
19 19
 	"fmt"
20 20
 	"math"
21 21
 	"path/filepath"
22
+	"regexp"
22 23
 	"sort"
23 24
 	"strconv"
24 25
 	"strings"
@@ -26,6 +27,12 @@ import (
26 27
 	"github.com/google/pprof/profile"
27 28
 )
28 29
 
30
+var (
31
+	javaRegExp = regexp.MustCompile(`^(?:[a-z]\w*\.)*([A-Z][\w\$]*\.(?:<init>|[a-z]\w*(?:\$\d+)?))(?:(?:\()|$)`)
32
+	goRegExp   = regexp.MustCompile(`^(?:[\w\-\.]+\/)+(.+)`)
33
+	cppRegExp  = regexp.MustCompile(`^(?:(?:\(anonymous namespace\)::)(\w+$))|(?:(?:\(anonymous namespace\)::)?(?:[_a-zA-Z]\w*\::|)*(_*[A-Z]\w*::~?[_a-zA-Z]\w*)$)`)
34
+)
35
+
29 36
 // Graph summarizes a performance profile into a format that is
30 37
 // suitable for visualization.
31 38
 type Graph struct {
@@ -420,6 +427,16 @@ func newTree(prof *profile.Profile, o *Options) (g *Graph) {
420 427
 	return selectNodesForGraph(nodes, o.DropNegative)
421 428
 }
422 429
 
430
+// ShortenFunctionName returns a shortened version of a function's name.
431
+func ShortenFunctionName(f string) string {
432
+	for _, re := range []*regexp.Regexp{goRegExp, javaRegExp, cppRegExp} {
433
+		if matches := re.FindStringSubmatch(f); len(matches) >= 2 {
434
+			return strings.Join(matches[1:], "")
435
+		}
436
+	}
437
+	return f
438
+}
439
+
423 440
 // TrimTree trims a Graph in forest form, keeping only the nodes in kept. This
424 441
 // will not work correctly if even a single node has multiple parents.
425 442
 func (g *Graph) TrimTree(kept NodePtrSet) {

+ 71
- 0
internal/graph/graph_test.go Dosyayı Görüntüle

@@ -398,3 +398,74 @@ func TestCreateNodes(t *testing.T) {
398 398
 		}
399 399
 	}
400 400
 }
401
+
402
+func TestShortenFunctionName(t *testing.T) {
403
+	type testCase struct {
404
+		name string
405
+		want string
406
+	}
407
+	testcases := []testCase{
408
+		{
409
+			"root",
410
+			"root",
411
+		},
412
+		{
413
+			"syscall.Syscall",
414
+			"syscall.Syscall",
415
+		},
416
+		{
417
+			"net/http.(*conn).serve",
418
+			"http.(*conn).serve",
419
+		},
420
+		{
421
+			"github.com/blahBlah/foo.Foo",
422
+			"foo.Foo",
423
+		},
424
+		{
425
+			"github.com/BlahBlah/foo.Foo",
426
+			"foo.Foo",
427
+		},
428
+		{
429
+			"github.com/blah-blah/foo_bar.(*FooBar).Foo",
430
+			"foo_bar.(*FooBar).Foo",
431
+		},
432
+		{
433
+			"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
434
+			"json.(*structEncoder).(encoding/json.encode)-fm",
435
+		},
436
+		{
437
+			"github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
438
+			"redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
439
+		},
440
+		{
441
+			"java.util.concurrent.ThreadPoolExecutor$Worker.run",
442
+			"ThreadPoolExecutor$Worker.run",
443
+		},
444
+		{
445
+			"java.bar.foo.FooBar.run(java.lang.Runnable)",
446
+			"FooBar.run",
447
+		},
448
+		{
449
+			"(anonymous namespace)::Bar::Foo",
450
+			"Bar::Foo",
451
+		},
452
+		{
453
+			"(anonymous namespace)::foo",
454
+			"foo",
455
+		},
456
+		{
457
+			"foo_bar::Foo::bar",
458
+			"Foo::bar",
459
+		},
460
+		{
461
+			"foo",
462
+			"foo",
463
+		},
464
+	}
465
+	for _, tc := range testcases {
466
+		name := ShortenFunctionName(tc.name)
467
+		if got, want := name, tc.want; got != want {
468
+			t.Errorf("ShortenFunctionName(%q) = %q, want %q", tc.name, got, want)
469
+		}
470
+	}
471
+}