41
41
package pgo
42
42
43
43
import (
44
+ "bufio"
44
45
"cmd/compile/internal/base"
45
46
"cmd/compile/internal/ir"
46
- "cmd/compile/internal/pgo/internal/graph"
47
47
"cmd/compile/internal/typecheck"
48
48
"cmd/compile/internal/types"
49
49
"errors"
50
50
"fmt"
51
51
"internal/profile"
52
+ "io/ioutil"
52
53
"os"
53
54
"sort"
55
+ "strconv"
56
+ "strings"
54
57
)
55
58
56
59
// IRGraph is a call graph with nodes pointing to IRs of functions and edges
@@ -105,6 +108,7 @@ type NamedCallEdge struct {
105
108
CallerName string
106
109
CalleeName string
107
110
CallSiteOffset int // Line offset from function start line.
111
+ CallStartLine int // Start line of the function. Can be 0 which means missing.
108
112
}
109
113
110
114
// NamedEdgeMap contains all unique call edges in the profile and their
@@ -139,8 +143,47 @@ type Profile struct {
139
143
WeightedCG * IRGraph
140
144
}
141
145
142
- // New generates a profile-graph from the profile.
146
+ var wantHdr = "GO PREPROFILE V1\n "
147
+
148
+ func isPreProfileFile (filename string ) (bool , error ) {
149
+ content , err := ioutil .ReadFile (filename )
150
+ if err != nil {
151
+ return false , err
152
+ }
153
+
154
+ /* check the header */
155
+ fileContent := string (content )
156
+ if strings .HasPrefix (fileContent , wantHdr ) {
157
+ return true , nil
158
+ }
159
+ return false , nil
160
+ }
161
+
162
+ // New generates a profile-graph from the profile or pre-processed profile.
143
163
func New (profileFile string ) (* Profile , error ) {
164
+ var profile * Profile
165
+ var err error
166
+ isPreProf , err := isPreProfileFile (profileFile )
167
+ if err != nil {
168
+ return nil , fmt .Errorf ("error opening profile: %w" , err )
169
+ }
170
+ if ! isPreProf {
171
+ profile , err = processProto (profileFile )
172
+ if err != nil {
173
+ return nil , fmt .Errorf ("error processing pprof PGO profile: %w" , err )
174
+ }
175
+ } else {
176
+ profile , err = processPreprof (profileFile )
177
+ if err != nil {
178
+ return nil , fmt .Errorf ("error processing preprocessed PGO profile: %w" , err )
179
+ }
180
+ }
181
+ return profile , nil
182
+
183
+ }
184
+
185
+ // processProto generates a profile-graph from the profile.
186
+ func processProto (profileFile string ) (* Profile , error ) {
144
187
f , err := os .Open (profileFile )
145
188
if err != nil {
146
189
return nil , fmt .Errorf ("error opening profile: %w" , err )
@@ -175,7 +218,7 @@ func New(profileFile string) (*Profile, error) {
175
218
return nil , fmt .Errorf (`profile does not contain a sample index with value/type "samples/count" or cpu/nanoseconds"` )
176
219
}
177
220
178
- g := graph .NewGraph (p , & graph .Options {
221
+ g := profile .NewGraph (p , & profile .Options {
179
222
SampleValue : func (v []int64 ) int64 { return v [valueIndex ] },
180
223
})
181
224
@@ -198,11 +241,134 @@ func New(profileFile string) (*Profile, error) {
198
241
}, nil
199
242
}
200
243
244
+ // processPreprof generates a profile-graph from the pre-procesed profile.
245
+ func processPreprof (preprofileFile string ) (* Profile , error ) {
246
+ namedEdgeMap , totalWeight , err := createNamedEdgeMapFromPreprocess (preprofileFile )
247
+ if err != nil {
248
+ return nil , err
249
+ }
250
+
251
+ if totalWeight == 0 {
252
+ return nil , nil // accept but ignore profile with no samples.
253
+ }
254
+
255
+ // Create package-level call graph with weights from profile and IR.
256
+ wg := createIRGraph (namedEdgeMap )
257
+
258
+ return & Profile {
259
+ TotalWeight : totalWeight ,
260
+ NamedEdgeMap : namedEdgeMap ,
261
+ WeightedCG : wg ,
262
+ }, nil
263
+ }
264
+
265
+ func postProcessNamedEdgeMap (weight map [NamedCallEdge ]int64 , weightVal int64 ) (edgeMap NamedEdgeMap , totalWeight int64 , err error ) {
266
+ if weightVal == 0 {
267
+ return NamedEdgeMap {}, 0 , nil // accept but ignore profile with no samples.
268
+ }
269
+ byWeight := make ([]NamedCallEdge , 0 , len (weight ))
270
+ for namedEdge := range weight {
271
+ byWeight = append (byWeight , namedEdge )
272
+ }
273
+ sort .Slice (byWeight , func (i , j int ) bool {
274
+ ei , ej := byWeight [i ], byWeight [j ]
275
+ if wi , wj := weight [ei ], weight [ej ]; wi != wj {
276
+ return wi > wj // want larger weight first
277
+ }
278
+ // same weight, order by name/line number
279
+ if ei .CallerName != ej .CallerName {
280
+ return ei .CallerName < ej .CallerName
281
+ }
282
+ if ei .CalleeName != ej .CalleeName {
283
+ return ei .CalleeName < ej .CalleeName
284
+ }
285
+ return ei .CallSiteOffset < ej .CallSiteOffset
286
+ })
287
+
288
+ edgeMap = NamedEdgeMap {
289
+ Weight : weight ,
290
+ ByWeight : byWeight ,
291
+ }
292
+
293
+ totalWeight = weightVal
294
+
295
+ return edgeMap , totalWeight , nil
296
+ }
297
+
298
+ // restore NodeMap information from a preprocessed profile.
299
+ // The reader can refer to the format of preprocessed profile in cmd/preprofile/main.go.
300
+ func createNamedEdgeMapFromPreprocess (preprofileFile string ) (edgeMap NamedEdgeMap , totalWeight int64 , err error ) {
301
+ readFile , err := os .Open (preprofileFile )
302
+ if err != nil {
303
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("error opening preprocessed profile: %w" , err )
304
+ }
305
+ defer readFile .Close ()
306
+
307
+ fileScanner := bufio .NewScanner (readFile )
308
+ fileScanner .Split (bufio .ScanLines )
309
+ weight := make (map [NamedCallEdge ]int64 )
310
+
311
+ if ! fileScanner .Scan () {
312
+ if err := fileScanner .Err (); err != nil {
313
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("error reading preprocessed profile: %w" , err )
314
+ }
315
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("preprocessed profile missing header" )
316
+ }
317
+ if gotHdr := fileScanner .Text () + "\n " ; gotHdr != wantHdr {
318
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("preprocessed profile malformed header; got %q want %q" , gotHdr , wantHdr )
319
+ }
320
+
321
+ for fileScanner .Scan () {
322
+ readStr := fileScanner .Text ()
323
+
324
+ callerName := readStr
325
+
326
+ if ! fileScanner .Scan () {
327
+ if err := fileScanner .Err (); err != nil {
328
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("error reading preprocessed profile: %w" , err )
329
+ }
330
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("preprocessed profile entry missing callee" )
331
+ }
332
+ calleeName := fileScanner .Text ()
333
+
334
+ if ! fileScanner .Scan () {
335
+ if err := fileScanner .Err (); err != nil {
336
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("error reading preprocessed profile: %w" , err )
337
+ }
338
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("preprocessed profile entry missing weight" )
339
+ }
340
+ readStr = fileScanner .Text ()
341
+
342
+ split := strings .Split (readStr , " " )
343
+
344
+ if len (split ) != 5 {
345
+ return NamedEdgeMap {}, 0 , fmt .Errorf ("preprocessed profile entry got %v want 5 fields" , split )
346
+ }
347
+
348
+ co , _ := strconv .Atoi (split [0 ])
349
+ cs , _ := strconv .Atoi (split [1 ])
350
+
351
+ namedEdge := NamedCallEdge {
352
+ CallerName : callerName ,
353
+ CallSiteOffset : co - cs ,
354
+ }
355
+
356
+ namedEdge .CalleeName = calleeName
357
+ EWeight , _ := strconv .ParseInt (split [4 ], 10 , 64 )
358
+
359
+ weight [namedEdge ] += EWeight
360
+ totalWeight += EWeight
361
+ }
362
+
363
+ return postProcessNamedEdgeMap (weight , totalWeight )
364
+
365
+ }
366
+
201
367
// createNamedEdgeMap builds a map of callsite-callee edge weights from the
202
368
// profile-graph.
203
369
//
204
370
// Caller should ignore the profile if totalWeight == 0.
205
- func createNamedEdgeMap (g * graph .Graph ) (edgeMap NamedEdgeMap , totalWeight int64 , err error ) {
371
+ func createNamedEdgeMap (g * profile .Graph ) (edgeMap NamedEdgeMap , totalWeight int64 , err error ) {
206
372
seenStartLine := false
207
373
208
374
// Process graph and build various node and edge maps which will
@@ -226,42 +392,13 @@ func createNamedEdgeMap(g *graph.Graph) (edgeMap NamedEdgeMap, totalWeight int64
226
392
}
227
393
}
228
394
229
- if totalWeight == 0 {
230
- return NamedEdgeMap {}, 0 , nil // accept but ignore profile with no samples.
231
- }
232
-
233
395
if ! seenStartLine {
234
396
// TODO(prattmic): If Function.start_line is missing we could
235
397
// fall back to using absolute line numbers, which is better
236
398
// than nothing.
237
399
return NamedEdgeMap {}, 0 , fmt .Errorf ("profile missing Function.start_line data (Go version of profiled application too old? Go 1.20+ automatically adds this to profiles)" )
238
400
}
239
-
240
- byWeight := make ([]NamedCallEdge , 0 , len (weight ))
241
- for namedEdge := range weight {
242
- byWeight = append (byWeight , namedEdge )
243
- }
244
- sort .Slice (byWeight , func (i , j int ) bool {
245
- ei , ej := byWeight [i ], byWeight [j ]
246
- if wi , wj := weight [ei ], weight [ej ]; wi != wj {
247
- return wi > wj // want larger weight first
248
- }
249
- // same weight, order by name/line number
250
- if ei .CallerName != ej .CallerName {
251
- return ei .CallerName < ej .CallerName
252
- }
253
- if ei .CalleeName != ej .CalleeName {
254
- return ei .CalleeName < ej .CalleeName
255
- }
256
- return ei .CallSiteOffset < ej .CallSiteOffset
257
- })
258
-
259
- edgeMap = NamedEdgeMap {
260
- Weight : weight ,
261
- ByWeight : byWeight ,
262
- }
263
-
264
- return edgeMap , totalWeight , nil
401
+ return postProcessNamedEdgeMap (weight , totalWeight )
265
402
}
266
403
267
404
// initializeIRGraph builds the IRGraph by visiting all the ir.Func in decl list
0 commit comments