@@ -6,6 +6,8 @@ package trace
6
6
7
7
import (
8
8
"container/heap"
9
+ tracev2 "internal/trace/v2"
10
+ "io"
9
11
"math"
10
12
"sort"
11
13
"strings"
@@ -202,6 +204,268 @@ func MutatorUtilization(events []*Event, flags UtilFlags) [][]MutatorUtil {
202
204
return out
203
205
}
204
206
207
+ // MutatorUtilizationV2 returns a set of mutator utilization functions
208
+ // for the given v2 trace, passed as an io.Reader. Each function will
209
+ // always end with 0 utilization. The bounds of each function are implicit
210
+ // in the first and last event; outside of these bounds each function is
211
+ // undefined.
212
+ //
213
+ // If the UtilPerProc flag is not given, this always returns a single
214
+ // utilization function. Otherwise, it returns one function per P.
215
+ func MutatorUtilizationV2 (trace io.Reader , flags UtilFlags ) ([][]MutatorUtil , error ) {
216
+ // Create a reader.
217
+ r , err := tracev2 .NewReader (trace )
218
+ if err != nil {
219
+ return nil , err
220
+ }
221
+
222
+ // Set up a bunch of analysis state.
223
+ type perP struct {
224
+ // gc > 0 indicates that GC is active on this P.
225
+ gc int
226
+ // series the logical series number for this P. This
227
+ // is necessary because Ps may be removed and then
228
+ // re-added, and then the new P needs a new series.
229
+ series int
230
+ }
231
+ type procsCount struct {
232
+ // time at which procs changed.
233
+ time int64
234
+ // n is the number of procs at that point.
235
+ n int
236
+ }
237
+ out := [][]MutatorUtil {}
238
+ stw := 0
239
+ ps := []perP {}
240
+ inGC := make (map [tracev2.GoID ]bool )
241
+ states := make (map [tracev2.GoID ]tracev2.GoState )
242
+ bgMark := make (map [tracev2.GoID ]bool )
243
+ procs := []procsCount {}
244
+ seenSync := false
245
+
246
+ // Helpers.
247
+ handleSTW := func (r tracev2.Range ) bool {
248
+ return flags & UtilSTW != 0 && isGCSTW (r )
249
+ }
250
+ handleMarkAssist := func (r tracev2.Range ) bool {
251
+ return flags & UtilAssist != 0 && isGCMarkAssist (r )
252
+ }
253
+ handleSweep := func (r tracev2.Range ) bool {
254
+ return flags & UtilSweep != 0 && isGCSweep (r )
255
+ }
256
+
257
+ // Iterate through the trace, tracking mutator utilization.
258
+ var lastEv tracev2.Event
259
+ for {
260
+ // Read a single event.
261
+ ev , err := r .ReadEvent ()
262
+ if err == io .EOF {
263
+ break
264
+ }
265
+ if err != nil {
266
+ return nil , err
267
+ }
268
+ lastEv = ev
269
+
270
+ // Process the event.
271
+ switch ev .Kind () {
272
+ case tracev2 .EventSync :
273
+ seenSync = true
274
+ case tracev2 .EventMetric :
275
+ m := ev .Metric ()
276
+ if m .Name != "/sched/gomaxprocs:threads" {
277
+ break
278
+ }
279
+ gomaxprocs := int (m .Value .Uint64 ())
280
+ if len (ps ) > gomaxprocs {
281
+ if flags & UtilPerProc != 0 {
282
+ // End each P's series.
283
+ for _ , p := range ps [gomaxprocs :] {
284
+ out [p .series ] = addUtil (out [p .series ], MutatorUtil {int64 (ev .Time ()), 0 })
285
+ }
286
+ }
287
+ ps = ps [:gomaxprocs ]
288
+ }
289
+ for len (ps ) < gomaxprocs {
290
+ // Start new P's series.
291
+ series := 0
292
+ if flags & UtilPerProc != 0 || len (out ) == 0 {
293
+ series = len (out )
294
+ out = append (out , []MutatorUtil {{int64 (ev .Time ()), 1 }})
295
+ }
296
+ ps = append (ps , perP {series : series })
297
+ }
298
+ if len (procs ) == 0 || gomaxprocs != procs [len (procs )- 1 ].n {
299
+ procs = append (procs , procsCount {time : int64 (ev .Time ()), n : gomaxprocs })
300
+ }
301
+ }
302
+ if len (ps ) == 0 {
303
+ // We can't start doing any analysis until we see what GOMAXPROCS is.
304
+ // It will show up very early in the trace, but we need to be robust to
305
+ // something else being emitted beforehand.
306
+ continue
307
+ }
308
+
309
+ switch ev .Kind () {
310
+ case tracev2 .EventRangeActive :
311
+ if seenSync {
312
+ // If we've seen a sync, then we can be sure we're not finding out about
313
+ // something late; we have complete information after that point, and these
314
+ // active events will just be redundant.
315
+ break
316
+ }
317
+ // This range is active back to the start of the trace. We're failing to account
318
+ // for this since we just found out about it now. Fix up the mutator utilization.
319
+ //
320
+ // N.B. A trace can't start during a STW, so we don't handle it here.
321
+ r := ev .Range ()
322
+ switch {
323
+ case handleMarkAssist (r ):
324
+ if ! states [ev .Goroutine ()].Executing () {
325
+ // If the goroutine isn't executing, then the fact that it was in mark
326
+ // assist doesn't actually count.
327
+ break
328
+ }
329
+ // This G has been in a mark assist *and running on its P* since the start
330
+ // of the trace.
331
+ fallthrough
332
+ case handleSweep (r ):
333
+ // This P has been in sweep (or mark assist, from above) in the start of the trace.
334
+ //
335
+ // We don't need to do anything if UtilPerProc is set. If we get an event like
336
+ // this for a running P, it must show up the first time a P is mentioned. Therefore,
337
+ // this P won't actually have any MutatorUtils on its list yet.
338
+ //
339
+ // However, if UtilPerProc isn't set, then we probably have data from other procs
340
+ // and from previous events. We need to fix that up.
341
+ if flags & UtilPerProc != 0 {
342
+ break
343
+ }
344
+ // Subtract out 1/gomaxprocs mutator utilization for all time periods
345
+ // from the beginning of the trace until now.
346
+ mi , pi := 0 , 0
347
+ for mi < len (out [0 ]) {
348
+ if pi < len (procs )- 1 && procs [pi + 1 ].time < out [0 ][mi ].Time {
349
+ pi ++
350
+ continue
351
+ }
352
+ out [0 ][mi ].Util -= float64 (1 ) / float64 (procs [pi ].n )
353
+ if out [0 ][mi ].Util < 0 {
354
+ out [0 ][mi ].Util = 0
355
+ }
356
+ mi ++
357
+ }
358
+ }
359
+ // After accounting for the portion we missed, this just acts like the
360
+ // beginning of a new range.
361
+ fallthrough
362
+ case tracev2 .EventRangeBegin :
363
+ r := ev .Range ()
364
+ if handleSTW (r ) {
365
+ stw ++
366
+ } else if handleSweep (r ) {
367
+ ps [ev .Proc ()].gc ++
368
+ } else if handleMarkAssist (r ) {
369
+ ps [ev .Proc ()].gc ++
370
+ if g := r .Scope .Goroutine (); g != tracev2 .NoGoroutine {
371
+ inGC [g ] = true
372
+ }
373
+ }
374
+ case tracev2 .EventRangeEnd :
375
+ r := ev .Range ()
376
+ if handleSTW (r ) {
377
+ stw --
378
+ } else if handleSweep (r ) {
379
+ ps [ev .Proc ()].gc --
380
+ } else if handleMarkAssist (r ) {
381
+ ps [ev .Proc ()].gc --
382
+ if g := r .Scope .Goroutine (); g != tracev2 .NoGoroutine {
383
+ delete (inGC , g )
384
+ }
385
+ }
386
+ case tracev2 .EventStateTransition :
387
+ st := ev .StateTransition ()
388
+ if st .Resource .Kind != tracev2 .ResourceGoroutine {
389
+ break
390
+ }
391
+ old , new := st .Goroutine ()
392
+ g := st .Resource .Goroutine ()
393
+ if inGC [g ] || bgMark [g ] {
394
+ if ! old .Executing () && new .Executing () {
395
+ // Started running while doing GC things.
396
+ ps [ev .Proc ()].gc ++
397
+ } else if old .Executing () && ! new .Executing () {
398
+ // Stopped running while doing GC things.
399
+ ps [ev .Proc ()].gc --
400
+ }
401
+ }
402
+ states [g ] = new
403
+ case tracev2 .EventLabel :
404
+ l := ev .Label ()
405
+ if flags & UtilBackground != 0 && strings .HasPrefix (l .Label , "GC " ) && l .Label != "GC (idle)" {
406
+ // Background mark worker.
407
+ //
408
+ // If we're in per-proc mode, we don't
409
+ // count dedicated workers because
410
+ // they kick all of the goroutines off
411
+ // that P, so don't directly
412
+ // contribute to goroutine latency.
413
+ if ! (flags & UtilPerProc != 0 && l .Label == "GC (dedicated)" ) {
414
+ bgMark [ev .Goroutine ()] = true
415
+ ps [ev .Proc ()].gc ++
416
+ }
417
+ }
418
+ }
419
+
420
+ if flags & UtilPerProc == 0 {
421
+ // Compute the current average utilization.
422
+ if len (ps ) == 0 {
423
+ continue
424
+ }
425
+ gcPs := 0
426
+ if stw > 0 {
427
+ gcPs = len (ps )
428
+ } else {
429
+ for i := range ps {
430
+ if ps [i ].gc > 0 {
431
+ gcPs ++
432
+ }
433
+ }
434
+ }
435
+ mu := MutatorUtil {int64 (ev .Time ()), 1 - float64 (gcPs )/ float64 (len (ps ))}
436
+
437
+ // Record the utilization change. (Since
438
+ // len(ps) == len(out), we know len(out) > 0.)
439
+ out [0 ] = addUtil (out [0 ], mu )
440
+ } else {
441
+ // Check for per-P utilization changes.
442
+ for i := range ps {
443
+ p := & ps [i ]
444
+ util := 1.0
445
+ if stw > 0 || p .gc > 0 {
446
+ util = 0.0
447
+ }
448
+ out [p .series ] = addUtil (out [p .series ], MutatorUtil {int64 (ev .Time ()), util })
449
+ }
450
+ }
451
+ }
452
+
453
+ // No events in the stream.
454
+ if lastEv .Kind () == tracev2 .EventBad {
455
+ return nil , nil
456
+ }
457
+
458
+ // Add final 0 utilization event to any remaining series. This
459
+ // is important to mark the end of the trace. The exact value
460
+ // shouldn't matter since no window should extend beyond this,
461
+ // but using 0 is symmetric with the start of the trace.
462
+ mu := MutatorUtil {int64 (lastEv .Time ()), 0 }
463
+ for i := range ps {
464
+ out [ps [i ].series ] = addUtil (out [ps [i ].series ], mu )
465
+ }
466
+ return out , nil
467
+ }
468
+
205
469
func addUtil (util []MutatorUtil , mu MutatorUtil ) []MutatorUtil {
206
470
if len (util ) > 0 {
207
471
if mu .Util == util [len (util )- 1 ].Util {
@@ -824,3 +1088,15 @@ func (in *integrator) next(time int64) int64 {
824
1088
}
825
1089
return 1 << 63 - 1
826
1090
}
1091
+
1092
+ func isGCSTW (r tracev2.Range ) bool {
1093
+ return strings .HasPrefix (r .Name , "stop-the-world" ) && strings .Contains (r .Name , "GC" )
1094
+ }
1095
+
1096
+ func isGCMarkAssist (r tracev2.Range ) bool {
1097
+ return r .Name == "GC mark assist"
1098
+ }
1099
+
1100
+ func isGCSweep (r tracev2.Range ) bool {
1101
+ return r .Name == "GC incremental sweep"
1102
+ }
0 commit comments