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