Repository: google/pprof
Pull request #929 (2025-02-08 17:55:02)

In the tags command, print percentages relative to profile total.
Before this change percentages were relative to the tag total and this sometimes causes confusion for users as it makes it look like the tag is set for all samples when it's not.

Note that the percentage calculation as implemented does not attempt to be friendly to samples where a given tag has multiple values. Multiple values per tag per sample are discouraged and we should deprecate them. Maybe we should make pprof print a warning for such profiles.

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/google/pprof/pull/929?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
Attention: Patch coverage is `0%` with `21 lines` in your changes missing coverage. Please review.
> Project coverage is 66.41%. Comparing base [(`0ed6a68`)](https://app.codecov.io/gh/google/pprof/commit/0ed6a68c8d9e6d5df69a1f2694c7b5c3297fdf4f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) to head [(`c1deddd`)](https://app.codecov.io/gh/google/pprof/commit/c1deddd8a3aaedb311cb3b093f398dfd6962b1f7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> Report is 64 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/google/pprof/pull/929?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Patch % | Lines |
|---|---|---|
| [internal/report/report.go](https://app.codecov.io/gh/google/pprof/pull/929?src=pr&el=tree&filepath=internal%2Freport%2Freport.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-aW50ZXJuYWwvcmVwb3J0L3JlcG9ydC5nbw==) | 0.00% | [21 Missing :warning: ](https://app.codecov.io/gh/google/pprof/pull/929?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #929      +/-   ##
==========================================
- Coverage   66.86%   66.41%   -0.46%     
==========================================
  Files          44       44              
  Lines        9824     9875      +51     
==========================================
- Hits         6569     6558      -11     
- Misses       2794     2876      +82     
+ Partials      461      441      -20     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/pprof/pull/929?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).


Patch:
diff --git a/internal/report/report.go b/internal/report/report.go
index 8e73f17..9d52872 100644
--- a/internal/report/report.go
+++ b/internal/report/report.go
@@ -699,13 +699,17 @@ func printTags(w io.Writer, rpt *Report) error {
 	p := rpt.prof
 
 	o := rpt.options
-	formatTag := func(v int64, key string) string {
-		return measurement.ScaledLabel(v, key, o.OutputUnit)
+	formatTag := func(v int64, unit string) string {
+		return measurement.ScaledLabel(v, unit, o.OutputUnit)
 	}
 
-	// Hashtable to keep accumulate tags as key,value,count.
+	// Accumulate tags as key,value,count.
 	tagMap := make(map[string]map[string]int64)
+	// Note that we assume single value per tag per sample. Multiple values are
+	// encodable in the format but are discouraged.
+	tagTotalMap := make(map[string]int64)
 	for _, s := range p.Sample {
+		sampleValue := o.SampleValue(s.Value)
 		for key, vals := range s.Label {
 			for _, val := range vals {
 				valueMap, ok := tagMap[key]
@@ -713,7 +717,8 @@ func printTags(w io.Writer, rpt *Report) error {
 					valueMap = make(map[string]int64)
 					tagMap[key] = valueMap
 				}
-				valueMap[val] += o.SampleValue(s.Value)
+				valueMap[val] += sampleValue
+				tagTotalMap[key] += sampleValue
 			}
 		}
 		for key, vals := range s.NumLabel {
@@ -725,7 +730,8 @@ func printTags(w io.Writer, rpt *Report) error {
 					valueMap = make(map[string]int64)
 					tagMap[key] = valueMap
 				}
-				valueMap[val] += o.SampleValue(s.Value)
+				valueMap[val] += sampleValue
+				tagTotalMap[key] += sampleValue
 			}
 		}
 	}
@@ -736,22 +742,23 @@ func printTags(w io.Writer, rpt *Report) error {
 	}
 	tabw := tabwriter.NewWriter(w, 0, 0, 1, ' ', tabwriter.AlignRight)
 	for _, tagKey := range graph.SortTags(tagKeys, true) {
-		var total int64
 		key := tagKey.Name
 		tags := make([]*graph.Tag, 0, len(tagMap[key]))
 		for t, c := range tagMap[key] {
-			total += c
 			tags = append(tags, &graph.Tag{Name: t, Flat: c})
 		}
 
-		f, u := measurement.Scale(total, o.SampleUnit, o.OutputUnit)
-		fmt.Fprintf(tabw, "%s:\t Total %.1f%s\n", key, f, u)
+		tagTotal, profileTotal := tagTotalMap[key], rpt.Total()
+		if profileTotal > 0 {
+			fmt.Fprintf(tabw, "%s:\t Total %s of %s (%s)\n", key, rpt.formatValue(tagTotal), rpt.formatValue(profileTotal), measurement.Percentage(tagTotal, profileTotal))
+		} else {
+			fmt.Fprintf(tabw, "%s:\t Total %s of %s\n", key, rpt.formatValue(tagTotal), rpt.formatValue(profileTotal))
+		}
 		for _, t := range graph.SortTags(tags, true) {
-			f, u := measurement.Scale(t.FlatValue(), o.SampleUnit, o.OutputUnit)
-			if total > 0 {
-				fmt.Fprintf(tabw, " \t%.1f%s (%s):\t %s\n", f, u, measurement.Percentage(t.FlatValue(), total), t.Name)
+			if profileTotal > 0 {
+				fmt.Fprintf(tabw, " \t%s (%s):\t %s\n", rpt.formatValue(t.FlatValue()), measurement.Percentage(t.FlatValue(), profileTotal), t.Name)
 			} else {
-				fmt.Fprintf(tabw, " \t%.1f%s:\t %s\n", f, u, t.Name)
+				fmt.Fprintf(tabw, " \t%s:\t %s\n", rpt.formatValue(t.FlatValue()), t.Name)
 			}
 		}
 		fmt.Fprintln(tabw)


Test patch:
diff --git a/internal/driver/driver_test.go b/internal/driver/driver_test.go
index bc7a97e..c90dec5 100644
--- a/internal/driver/driver_test.go
+++ b/internal/driver/driver_test.go
@@ -97,6 +97,7 @@ func TestParse(t *testing.T) {
 		{"weblist=line(1000|3000)$,addresses,flat", "cpu"},
 		{"tags,tagfocus=400kb:", "heap_request"},
 		{"tags,tagfocus=+400kb:", "heap_request"},
+		{"tags,relative_percentages,tagfocus=400kb:", "heap_request"},
 		{"dot", "long_name_funcs"},
 		{"text", "long_name_funcs"},
 	}
diff --git a/internal/driver/testdata/pprof.cpu.tags b/internal/driver/testdata/pprof.cpu.tags
index 5998b5b..14f3e35 100644
--- a/internal/driver/testdata/pprof.cpu.tags
+++ b/internal/driver/testdata/pprof.cpu.tags
@@ -1,13 +1,13 @@
- key1: Total 1.1s
-          1.0s (89.29%): tag1
-       100.0ms ( 8.93%): tag2
-        10.0ms ( 0.89%): tag3
-        10.0ms ( 0.89%): tag4
+ key1: Total 1.12s of 1.12s (  100%)
+          1s (89.29%): tag1
+       100ms ( 8.93%): tag2
+        10ms ( 0.89%): tag3
+        10ms ( 0.89%): tag4
 
- key2: Total 1.0s
-         1.0s (99.02%): tag1
-       10.0ms ( 0.98%): tag2
+ key2: Total 1.02s of 1.12s (91.07%)
+       1.01s (90.18%): tag1
+        10ms ( 0.89%): tag2
 
- key3: Total 100.0ms
-       100.0ms (  100%): tag2
+ key3: Total 100ms of 1.12s ( 8.93%)
+       100ms ( 8.93%): tag2
 
diff --git a/internal/driver/testdata/pprof.cpu.tags.focus.ignore b/internal/driver/testdata/pprof.cpu.tags.focus.ignore
index 9b99d43..b8d894a 100644
--- a/internal/driver/testdata/pprof.cpu.tags.focus.ignore
+++ b/internal/driver/testdata/pprof.cpu.tags.focus.ignore
@@ -1,6 +1,6 @@
- key1: Total 100.0ms
-       100.0ms (  100%): tag2
+ key1: Total 100ms of 1.12s ( 8.93%)
+       100ms ( 8.93%): tag2
 
- key3: Total 100.0ms
-       100.0ms (  100%): tag2
+ key3: Total 100ms of 1.12s ( 8.93%)
+       100ms ( 8.93%): tag2
 
diff --git a/internal/driver/testdata/pprof.heap.tags b/internal/driver/testdata/pprof.heap.tags
index 630e452..78678a3 100644
--- a/internal/driver/testdata/pprof.heap.tags
+++ b/internal/driver/testdata/pprof.heap.tags
@@ -1,6 +1,6 @@
- bytes: Total 98.6MB
-          62.5MB (63.37%): 1.56MB
-          31.2MB (31.68%): 400kB
-           3.9MB ( 3.96%): 200kB
-        1000.0kB ( 0.99%): 100kB
+ bytes: Total 98.63MB of 98.63MB (  100%)
+        62.50MB (63.37%): 1.56MB
+        31.25MB (31.68%): 400kB
+         3.91MB ( 3.96%): 200kB
+         1000kB ( 0.99%): 100kB
 
diff --git a/internal/driver/testdata/pprof.heap.tags.unit b/internal/driver/testdata/pprof.heap.tags.unit
index 5e565fc..85ebd1f 100644
--- a/internal/driver/testdata/pprof.heap.tags.unit
+++ b/internal/driver/testdata/pprof.heap.tags.unit
@@ -1,6 +1,6 @@
- bytes: Total 103424000.0B
-        65536000.0B (63.37%): 1638400B
-        32768000.0B (31.68%): 409600B
-         4096000.0B ( 3.96%): 204800B
-         1024000.0B ( 0.99%): 102400B
+ bytes: Total 103424000B of 103424000B (  100%)
+        65536000B (63.37%): 1638400B
+        32768000B (31.68%): 409600B
+         4096000B ( 3.96%): 204800B
+         1024000B ( 0.99%): 102400B
 
diff --git a/internal/driver/testdata/pprof.heap_request.relative_percentages.tags.focus b/internal/driver/testdata/pprof.heap_request.relative_percentages.tags.focus
new file mode 100644
index 0000000..260dce7
--- /dev/null
+++ b/internal/driver/testdata/pprof.heap_request.relative_percentages.tags.focus
@@ -0,0 +1,8 @@
+ bytes: Total 93.75MB of 93.75MB (  100%)
+        62.50MB (66.67%): 1.56MB
+        31.25MB (33.33%): 400kB
+
+ request: Total 93.75MB of 93.75MB (  100%)
+          62.50MB (66.67%): 1.56MB
+          31.25MB (33.33%): 400kB
+
diff --git a/internal/driver/testdata/pprof.heap_request.tags.focus b/internal/driver/testdata/pprof.heap_request.tags.focus
index b1a5f44..ec28720 100644
--- a/internal/driver/testdata/pprof.heap_request.tags.focus
+++ b/internal/driver/testdata/pprof.heap_request.tags.focus
@@ -1,8 +1,8 @@
- bytes: Total 93.8MB
-        62.5MB (66.67%): 1.56MB
-        31.2MB (33.33%): 400kB
+ bytes: Total 93.75MB of 98.63MB (95.05%)
+        62.50MB (63.37%): 1.56MB
+        31.25MB (31.68%): 400kB
 
- request: Total 93.8MB
-          62.5MB (66.67%): 1.56MB
-          31.2MB (33.33%): 400kB
+ request: Total 93.75MB of 98.63MB (95.05%)
+          62.50MB (63.37%): 1.56MB
+          31.25MB (31.68%): 400kB