@@ -7,6 +7,7 @@ package bytesprofile
77import (
88 "cmp"
99 "fmt"
10+ "iter"
1011 "maps"
1112 "runtime"
1213 "slices"
@@ -56,6 +57,23 @@ func (p *Profile) Record(bytes int64) {
5657 p .samples [stack ] = curr
5758}
5859
60+ func (p * Profile ) all () iter.Seq2 [stack , aggSamples ] {
61+ return func (yield func (stack , aggSamples ) bool ) {
62+ p .mu .Lock ()
63+ defer p .mu .Unlock ()
64+ // Sort the stacks by bytes in descending order.
65+ uniqueStacks := slices .SortedFunc (maps .Keys (p .samples ), func (a , b stack ) int {
66+ return - cmp .Compare (p .samples [a ].bytes , p .samples [b ].bytes )
67+ })
68+ for _ , stack := range uniqueStacks {
69+ samples := p .samples [stack ]
70+ if ! yield (stack , samples ) {
71+ break
72+ }
73+ }
74+ }
75+ }
76+
5977// TODO(jackson): We could add the ability to export the profile to a pprof file
6078// (which internally is just a protocol buffer). Ideally the Go standard library
6179// would provide facilities for this (e.g., golang/go#18454). The runtime/pprof
@@ -64,20 +82,15 @@ func (p *Profile) Record(bytes int64) {
6482
6583// String returns a string representation of the stacks captured by the profile.
6684func (p * Profile ) String () string {
67- p .mu .Lock ()
68- defer p .mu .Unlock ()
69- // Sort the stacks by bytes in descending order.
70- uniqueStacks := slices .SortedFunc (maps .Keys (p .samples ), func (a , b stack ) int {
71- return - cmp .Compare (p .samples [a ].bytes , p .samples [b ].bytes )
72- })
7385 var sb strings.Builder
74- for i , stack := range uniqueStacks {
86+ var i int
87+ for stack , stats := range p .all () {
7588 if i > 0 {
7689 sb .WriteString ("\n " )
7790 }
7891 fmt .Fprintf (& sb , "%d: Count: %d (%s), Bytes: %d (%s)\n " , i ,
79- p . samples [ stack ]. count , humanize .Count .Int64 (p . samples [ stack ] .count ),
80- p . samples [ stack ]. bytes , humanize .Bytes .Int64 (p . samples [ stack ] .bytes ))
92+ stats . count , humanize .Count .Int64 (stats .count ),
93+ stats . bytes , humanize .Bytes .Int64 (stats .bytes ))
8194 frames := runtime .CallersFrames (stack .trimmed ())
8295 for {
8396 frame , more := frames .Next ()
@@ -86,6 +99,39 @@ func (p *Profile) String() string {
8699 break
87100 }
88101 }
102+ i ++
89103 }
90104 return sb .String ()
91105}
106+
107+ // StackStats contains the stack trace and statistics for a given stack.
108+ type StackStats struct {
109+ Stack string
110+ Bytes int64
111+ Count int64
112+ }
113+
114+ // Collect returns a slice of StackStats, sorted by bytes in descending order.
115+ func (p * Profile ) Collect () []StackStats {
116+ var stats []StackStats
117+ var sb strings.Builder
118+ for stack , samples := range p .all () {
119+ sb .Reset ()
120+ frames := runtime .CallersFrames (stack .trimmed ())
121+ for {
122+ frame , more := frames .Next ()
123+ fmt .Fprintf (& sb , " %s\n %s:%d" , frame .Function , frame .File , frame .Line )
124+ if ! more {
125+ break
126+ }
127+ sb .WriteString ("\n " )
128+ }
129+
130+ stats = append (stats , StackStats {
131+ Stack : sb .String (),
132+ Bytes : samples .bytes ,
133+ Count : samples .count ,
134+ })
135+ }
136+ return stats
137+ }
0 commit comments