4
4
package fgprof
5
5
6
6
import (
7
+ "fmt"
7
8
"io"
8
9
"runtime"
10
+ "sort"
9
11
"strings"
10
12
"time"
13
+
14
+ "github.com/google/pprof/profile"
15
+ )
16
+
17
+ // Format decides how the output is rendered to the user.
18
+ type Format string
19
+
20
+ const (
21
+ // FormatFolded is used by Brendan Gregg's FlameGraph utility, see
22
+ // https://github.com/brendangregg/FlameGraph#2-fold-stacks.
23
+ FormatFolded Format = "folded"
24
+ // FormatPprof is used by Google's pprof utility, see
25
+ // https://github.com/google/pprof/blob/master/proto/README.md.
26
+ FormatPprof Format = "pprof"
11
27
)
12
28
13
29
// Start begins profiling the goroutines of the program and returns a function
@@ -23,7 +39,7 @@ func Start(w io.Writer, format Format) func() error {
23
39
stopCh := make (chan struct {})
24
40
25
41
prof := & profiler {}
26
- stackCounts := stackCounter {}
42
+ profile := newWallclockProfile ()
27
43
28
44
go func () {
29
45
defer ticker .Stop ()
@@ -32,7 +48,7 @@ func Start(w io.Writer, format Format) func() error {
32
48
select {
33
49
case <- ticker .C :
34
50
stacks := prof .GoroutineProfile ()
35
- stackCounts . Update (stacks )
51
+ profile . Add (stacks )
36
52
case <- stopCh :
37
53
return
38
54
}
@@ -42,14 +58,8 @@ func Start(w io.Writer, format Format) func() error {
42
58
return func () error {
43
59
stopCh <- struct {}{}
44
60
endTime := time .Now ()
45
- return writeFormat (
46
- w ,
47
- stackCounts .HumanMap (prof .SelfFrame ()),
48
- format ,
49
- hz ,
50
- startTime ,
51
- endTime ,
52
- )
61
+ profile .Ignore (prof .SelfFrames ()... )
62
+ return profile .Export (w , format , hz , startTime , endTime )
53
63
}
54
64
}
55
65
@@ -95,58 +105,203 @@ func (p *profiler) GoroutineProfile() []runtime.StackRecord {
95
105
}
96
106
}
97
107
98
- func (p * profiler ) SelfFrame () * runtime.Frame {
99
- return p .selfFrame
108
+ // SelfFrames returns frames that belong to the profiler so that we can ignore
109
+ // them when exporting the final profile.
110
+ func (p * profiler ) SelfFrames () []* runtime.Frame {
111
+ if p .selfFrame != nil {
112
+ return []* runtime.Frame {p .selfFrame }
113
+ }
114
+ return nil
100
115
}
101
116
102
- type stringStackCounter map [string ]int
117
+ func newWallclockProfile () * wallclockProfile {
118
+ return & wallclockProfile {stacks : map [[32 ]uintptr ]* wallclockStack {}}
119
+ }
103
120
104
- func (s stringStackCounter ) Update (p []runtime.StackRecord ) {
105
- for _ , pp := range p {
106
- frames := runtime .CallersFrames (pp .Stack ())
121
+ // wallclockProfile holds a wallclock profile that can be exported in different
122
+ // formats.
123
+ type wallclockProfile struct {
124
+ stacks map [[32 ]uintptr ]* wallclockStack
125
+ ignore []* runtime.Frame
126
+ }
107
127
108
- var stack []string
109
- for {
110
- frame , more := frames .Next ()
111
- stack = append ([]string {frame .Function }, stack ... )
112
- if ! more {
113
- break
128
+ // wallclockStack holds the symbolized frames of a stack trace and the number
129
+ // of times it has been seen.
130
+ type wallclockStack struct {
131
+ frames []* runtime.Frame
132
+ count int
133
+ }
134
+
135
+ // Ignore sets a list of frames that should be ignored when exporting the
136
+ // profile.
137
+ func (p * wallclockProfile ) Ignore (frames ... * runtime.Frame ) {
138
+ p .ignore = frames
139
+ }
140
+
141
+ // Add adds the given stack traces to the profile.
142
+ func (p * wallclockProfile ) Add (stackRecords []runtime.StackRecord ) {
143
+ for _ , stackRecord := range stackRecords {
144
+ if _ , ok := p .stacks [stackRecord .Stack0 ]; ! ok {
145
+ ws := & wallclockStack {}
146
+ // symbolize pcs into frames
147
+ frames := runtime .CallersFrames (stackRecord .Stack ())
148
+ for {
149
+ frame , more := frames .Next ()
150
+ ws .frames = append (ws .frames , & frame )
151
+ if ! more {
152
+ break
153
+ }
114
154
}
155
+ p .stacks [stackRecord .Stack0 ] = ws
115
156
}
116
- key := strings .Join (stack , ";" )
117
- s [key ]++
157
+ p .stacks [stackRecord .Stack0 ].count ++
118
158
}
119
159
}
120
160
121
- type stackCounter map [[32 ]uintptr ]int
161
+ func (p * wallclockProfile ) Export (w io.Writer , f Format , hz int , startTime , endTime time.Time ) error {
162
+ switch f {
163
+ case FormatFolded :
164
+ return p .exportFolded (w )
165
+ case FormatPprof :
166
+ return p .exportPprof (hz , startTime , endTime ).Write (w )
167
+ default :
168
+ return fmt .Errorf ("unknown format: %q" , f )
169
+ }
170
+ }
122
171
123
- func (s stackCounter ) Update (p []runtime.StackRecord ) {
124
- for _ , pp := range p {
125
- s [pp .Stack0 ]++
172
+ // exportStacks returns the stacks in this profile except those that have been
173
+ // set to Ignore().
174
+ func (p * wallclockProfile ) exportStacks () []* wallclockStack {
175
+ stacks := make ([]* wallclockStack , 0 , len (p .stacks ))
176
+ nextStack:
177
+ for _ , ws := range p .stacks {
178
+ for _ , f := range ws .frames {
179
+ for _ , igf := range p .ignore {
180
+ if f .Entry == igf .Entry {
181
+ continue nextStack
182
+ }
183
+ }
184
+ }
185
+ stacks = append (stacks , ws )
126
186
}
187
+ return stacks
127
188
}
128
189
129
- // @TODO(fg) create a better interface that avoids the pprof output having to
130
- // split the stacks using the `;` separator.
131
- func (s stackCounter ) HumanMap (exclude * runtime.Frame ) map [string ]int {
132
- m := map [string ]int {}
190
+ func (p * wallclockProfile ) exportFolded (w io.Writer ) error {
191
+ var lines []string
192
+ stacks := p .exportStacks ()
193
+ for _ , ws := range stacks {
194
+ var foldedStack []string
195
+ for _ , f := range ws .frames {
196
+ foldedStack = append (foldedStack , f .Function )
197
+ }
198
+ line := fmt .Sprintf ("%s %d" , strings .Join (foldedStack , ";" ), ws .count )
199
+ lines = append (lines , line )
200
+ }
201
+ sort .Strings (lines )
202
+ _ , err := io .WriteString (w , strings .Join (lines , "\n " )+ "\n " )
203
+ return err
204
+ }
205
+
206
+ func (p * wallclockProfile ) exportPprof (hz int , startTime , endTime time.Time ) * profile.Profile {
207
+ prof := & profile.Profile {}
208
+ m := & profile.Mapping {ID : 1 , HasFunctions : true }
209
+ prof .Period = int64 (1e9 / hz ) // Number of nanoseconds between samples.
210
+ prof .TimeNanos = startTime .UnixNano ()
211
+ prof .DurationNanos = int64 (endTime .Sub (startTime ))
212
+ prof .Mapping = []* profile.Mapping {m }
213
+ prof .SampleType = []* profile.ValueType {
214
+ {
215
+ Type : "samples" ,
216
+ Unit : "count" ,
217
+ },
218
+ {
219
+ Type : "time" ,
220
+ Unit : "nanoseconds" ,
221
+ },
222
+ }
223
+ prof .PeriodType = & profile.ValueType {
224
+ Type : "wallclock" ,
225
+ Unit : "nanoseconds" ,
226
+ }
227
+
228
+ type functionKey struct {
229
+ Name string
230
+ Filename string
231
+ }
232
+ funcIdx := map [functionKey ]* profile.Function {}
233
+
234
+ type locationKey struct {
235
+ Function functionKey
236
+ Line int
237
+ }
238
+ locationIdx := map [locationKey ]* profile.Location {}
239
+ for _ , ws := range p .exportStacks () {
240
+ sample := & profile.Sample {
241
+ Value : []int64 {
242
+ int64 (ws .count ),
243
+ int64 (1000 * 1000 * 1000 / hz * ws .count ),
244
+ },
245
+ }
246
+
247
+ for _ , frame := range ws .frames {
248
+ fnKey := functionKey {Name : frame .Function , Filename : frame .File }
249
+ function , ok := funcIdx [fnKey ]
250
+ if ! ok {
251
+ function = & profile.Function {
252
+ ID : uint64 (len (prof .Function )) + 1 ,
253
+ Name : frame .Function ,
254
+ SystemName : frame .Function ,
255
+ Filename : frame .File ,
256
+ }
257
+ funcIdx [fnKey ] = function
258
+ prof .Function = append (prof .Function , function )
259
+ }
260
+
261
+ locKey := locationKey {Function : fnKey , Line : frame .Line }
262
+ location , ok := locationIdx [locKey ]
263
+ if ! ok {
264
+ location = & profile.Location {
265
+ ID : uint64 (len (prof .Location )) + 1 ,
266
+ Mapping : m ,
267
+ Line : []profile.Line {{
268
+ Function : function ,
269
+ Line : int64 (frame .Line ),
270
+ }},
271
+ }
272
+ locationIdx [locKey ] = location
273
+ prof .Location = append (prof .Location , location )
274
+ }
275
+ sample .Location = append (sample .Location , location )
276
+ }
277
+ prof .Sample = append (prof .Sample , sample )
278
+ }
279
+ return prof
280
+ }
281
+
282
+ type symbolizedStacks map [[32 ]uintptr ][]frameCount
283
+
284
+ func (w wallclockProfile ) Symbolize (exclude * runtime.Frame ) symbolizedStacks {
285
+ m := make (symbolizedStacks )
133
286
outer:
134
- for stack0 , count := range s {
287
+ for stack0 , ws := range w . stacks {
135
288
frames := runtime .CallersFrames ((& runtime.StackRecord {Stack0 : stack0 }).Stack ())
136
289
137
- var stack []string
138
290
for {
139
291
frame , more := frames .Next ()
140
292
if frame .Entry == exclude .Entry {
141
293
continue outer
142
294
}
143
- stack = append ([] string { frame . Function }, stack ... )
295
+ m [ stack0 ] = append (m [ stack0 ], frameCount { Frame : & frame , Count : ws . count } )
144
296
if ! more {
145
297
break
146
298
}
147
299
}
148
- key := strings .Join (stack , ";" )
149
- m [key ] = count
150
300
}
151
301
return m
152
302
}
303
+
304
+ type frameCount struct {
305
+ * runtime.Frame
306
+ Count int
307
+ }
0 commit comments