1// Copyright 2017 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package trace 6 7import ( 8 "container/heap" 9 "math" 10 "sort" 11 "strings" 12 "time" 13) 14 15// MutatorUtil is a change in mutator utilization at a particular 16// time. Mutator utilization functions are represented as a 17// time-ordered []MutatorUtil. 18type MutatorUtil struct { 19 Time int64 20 // Util is the mean mutator utilization starting at Time. This 21 // is in the range [0, 1]. 22 Util float64 23} 24 25// UtilFlags controls the behavior of MutatorUtilization. 26type UtilFlags int 27 28const ( 29 // UtilSTW means utilization should account for STW events. 30 // This includes non-GC STW events, which are typically user-requested. 31 UtilSTW UtilFlags = 1 << iota 32 // UtilBackground means utilization should account for 33 // background mark workers. 34 UtilBackground 35 // UtilAssist means utilization should account for mark 36 // assists. 37 UtilAssist 38 // UtilSweep means utilization should account for sweeping. 39 UtilSweep 40 41 // UtilPerProc means each P should be given a separate 42 // utilization function. Otherwise, there is a single function 43 // and each P is given a fraction of the utilization. 44 UtilPerProc 45) 46 47// MutatorUtilizationV2 returns a set of mutator utilization functions 48// for the given v2 trace, passed as an io.Reader. Each function will 49// always end with 0 utilization. The bounds of each function are implicit 50// in the first and last event; outside of these bounds each function is 51// undefined. 52// 53// If the UtilPerProc flag is not given, this always returns a single 54// utilization function. Otherwise, it returns one function per P. 55func MutatorUtilizationV2(events []Event, flags UtilFlags) [][]MutatorUtil { 56 // Set up a bunch of analysis state. 57 type perP struct { 58 // gc > 0 indicates that GC is active on this P. 59 gc int 60 // series the logical series number for this P. This 61 // is necessary because Ps may be removed and then 62 // re-added, and then the new P needs a new series. 63 series int 64 } 65 type procsCount struct { 66 // time at which procs changed. 67 time int64 68 // n is the number of procs at that point. 69 n int 70 } 71 out := [][]MutatorUtil{} 72 stw := 0 73 ps := []perP{} 74 inGC := make(map[GoID]bool) 75 states := make(map[GoID]GoState) 76 bgMark := make(map[GoID]bool) 77 procs := []procsCount{} 78 seenSync := false 79 80 // Helpers. 81 handleSTW := func(r Range) bool { 82 return flags&UtilSTW != 0 && isGCSTW(r) 83 } 84 handleMarkAssist := func(r Range) bool { 85 return flags&UtilAssist != 0 && isGCMarkAssist(r) 86 } 87 handleSweep := func(r Range) bool { 88 return flags&UtilSweep != 0 && isGCSweep(r) 89 } 90 91 // Iterate through the trace, tracking mutator utilization. 92 var lastEv *Event 93 for i := range events { 94 ev := &events[i] 95 lastEv = ev 96 97 // Process the event. 98 switch ev.Kind() { 99 case EventSync: 100 seenSync = true 101 case EventMetric: 102 m := ev.Metric() 103 if m.Name != "/sched/gomaxprocs:threads" { 104 break 105 } 106 gomaxprocs := int(m.Value.Uint64()) 107 if len(ps) > gomaxprocs { 108 if flags&UtilPerProc != 0 { 109 // End each P's series. 110 for _, p := range ps[gomaxprocs:] { 111 out[p.series] = addUtil(out[p.series], MutatorUtil{int64(ev.Time()), 0}) 112 } 113 } 114 ps = ps[:gomaxprocs] 115 } 116 for len(ps) < gomaxprocs { 117 // Start new P's series. 118 series := 0 119 if flags&UtilPerProc != 0 || len(out) == 0 { 120 series = len(out) 121 out = append(out, []MutatorUtil{{int64(ev.Time()), 1}}) 122 } 123 ps = append(ps, perP{series: series}) 124 } 125 if len(procs) == 0 || gomaxprocs != procs[len(procs)-1].n { 126 procs = append(procs, procsCount{time: int64(ev.Time()), n: gomaxprocs}) 127 } 128 } 129 if len(ps) == 0 { 130 // We can't start doing any analysis until we see what GOMAXPROCS is. 131 // It will show up very early in the trace, but we need to be robust to 132 // something else being emitted beforehand. 133 continue 134 } 135 136 switch ev.Kind() { 137 case EventRangeActive: 138 if seenSync { 139 // If we've seen a sync, then we can be sure we're not finding out about 140 // something late; we have complete information after that point, and these 141 // active events will just be redundant. 142 break 143 } 144 // This range is active back to the start of the trace. We're failing to account 145 // for this since we just found out about it now. Fix up the mutator utilization. 146 // 147 // N.B. A trace can't start during a STW, so we don't handle it here. 148 r := ev.Range() 149 switch { 150 case handleMarkAssist(r): 151 if !states[ev.Goroutine()].Executing() { 152 // If the goroutine isn't executing, then the fact that it was in mark 153 // assist doesn't actually count. 154 break 155 } 156 // This G has been in a mark assist *and running on its P* since the start 157 // of the trace. 158 fallthrough 159 case handleSweep(r): 160 // This P has been in sweep (or mark assist, from above) in the start of the trace. 161 // 162 // We don't need to do anything if UtilPerProc is set. If we get an event like 163 // this for a running P, it must show up the first time a P is mentioned. Therefore, 164 // this P won't actually have any MutatorUtils on its list yet. 165 // 166 // However, if UtilPerProc isn't set, then we probably have data from other procs 167 // and from previous events. We need to fix that up. 168 if flags&UtilPerProc != 0 { 169 break 170 } 171 // Subtract out 1/gomaxprocs mutator utilization for all time periods 172 // from the beginning of the trace until now. 173 mi, pi := 0, 0 174 for mi < len(out[0]) { 175 if pi < len(procs)-1 && procs[pi+1].time < out[0][mi].Time { 176 pi++ 177 continue 178 } 179 out[0][mi].Util -= float64(1) / float64(procs[pi].n) 180 if out[0][mi].Util < 0 { 181 out[0][mi].Util = 0 182 } 183 mi++ 184 } 185 } 186 // After accounting for the portion we missed, this just acts like the 187 // beginning of a new range. 188 fallthrough 189 case EventRangeBegin: 190 r := ev.Range() 191 if handleSTW(r) { 192 stw++ 193 } else if handleSweep(r) { 194 ps[ev.Proc()].gc++ 195 } else if handleMarkAssist(r) { 196 ps[ev.Proc()].gc++ 197 if g := r.Scope.Goroutine(); g != NoGoroutine { 198 inGC[g] = true 199 } 200 } 201 case EventRangeEnd: 202 r := ev.Range() 203 if handleSTW(r) { 204 stw-- 205 } else if handleSweep(r) { 206 ps[ev.Proc()].gc-- 207 } else if handleMarkAssist(r) { 208 ps[ev.Proc()].gc-- 209 if g := r.Scope.Goroutine(); g != NoGoroutine { 210 delete(inGC, g) 211 } 212 } 213 case EventStateTransition: 214 st := ev.StateTransition() 215 if st.Resource.Kind != ResourceGoroutine { 216 break 217 } 218 old, new := st.Goroutine() 219 g := st.Resource.Goroutine() 220 if inGC[g] || bgMark[g] { 221 if !old.Executing() && new.Executing() { 222 // Started running while doing GC things. 223 ps[ev.Proc()].gc++ 224 } else if old.Executing() && !new.Executing() { 225 // Stopped running while doing GC things. 226 ps[ev.Proc()].gc-- 227 } 228 } 229 states[g] = new 230 case EventLabel: 231 l := ev.Label() 232 if flags&UtilBackground != 0 && strings.HasPrefix(l.Label, "GC ") && l.Label != "GC (idle)" { 233 // Background mark worker. 234 // 235 // If we're in per-proc mode, we don't 236 // count dedicated workers because 237 // they kick all of the goroutines off 238 // that P, so don't directly 239 // contribute to goroutine latency. 240 if !(flags&UtilPerProc != 0 && l.Label == "GC (dedicated)") { 241 bgMark[ev.Goroutine()] = true 242 ps[ev.Proc()].gc++ 243 } 244 } 245 } 246 247 if flags&UtilPerProc == 0 { 248 // Compute the current average utilization. 249 if len(ps) == 0 { 250 continue 251 } 252 gcPs := 0 253 if stw > 0 { 254 gcPs = len(ps) 255 } else { 256 for i := range ps { 257 if ps[i].gc > 0 { 258 gcPs++ 259 } 260 } 261 } 262 mu := MutatorUtil{int64(ev.Time()), 1 - float64(gcPs)/float64(len(ps))} 263 264 // Record the utilization change. (Since 265 // len(ps) == len(out), we know len(out) > 0.) 266 out[0] = addUtil(out[0], mu) 267 } else { 268 // Check for per-P utilization changes. 269 for i := range ps { 270 p := &ps[i] 271 util := 1.0 272 if stw > 0 || p.gc > 0 { 273 util = 0.0 274 } 275 out[p.series] = addUtil(out[p.series], MutatorUtil{int64(ev.Time()), util}) 276 } 277 } 278 } 279 280 // No events in the stream. 281 if lastEv == nil { 282 return nil 283 } 284 285 // Add final 0 utilization event to any remaining series. This 286 // is important to mark the end of the trace. The exact value 287 // shouldn't matter since no window should extend beyond this, 288 // but using 0 is symmetric with the start of the trace. 289 mu := MutatorUtil{int64(lastEv.Time()), 0} 290 for i := range ps { 291 out[ps[i].series] = addUtil(out[ps[i].series], mu) 292 } 293 return out 294} 295 296func addUtil(util []MutatorUtil, mu MutatorUtil) []MutatorUtil { 297 if len(util) > 0 { 298 if mu.Util == util[len(util)-1].Util { 299 // No change. 300 return util 301 } 302 if mu.Time == util[len(util)-1].Time { 303 // Take the lowest utilization at a time stamp. 304 if mu.Util < util[len(util)-1].Util { 305 util[len(util)-1] = mu 306 } 307 return util 308 } 309 } 310 return append(util, mu) 311} 312 313// totalUtil is total utilization, measured in nanoseconds. This is a 314// separate type primarily to distinguish it from mean utilization, 315// which is also a float64. 316type totalUtil float64 317 318func totalUtilOf(meanUtil float64, dur int64) totalUtil { 319 return totalUtil(meanUtil * float64(dur)) 320} 321 322// mean returns the mean utilization over dur. 323func (u totalUtil) mean(dur time.Duration) float64 { 324 return float64(u) / float64(dur) 325} 326 327// An MMUCurve is the minimum mutator utilization curve across 328// multiple window sizes. 329type MMUCurve struct { 330 series []mmuSeries 331} 332 333type mmuSeries struct { 334 util []MutatorUtil 335 // sums[j] is the cumulative sum of util[:j]. 336 sums []totalUtil 337 // bands summarizes util in non-overlapping bands of duration 338 // bandDur. 339 bands []mmuBand 340 // bandDur is the duration of each band. 341 bandDur int64 342} 343 344type mmuBand struct { 345 // minUtil is the minimum instantaneous mutator utilization in 346 // this band. 347 minUtil float64 348 // cumUtil is the cumulative total mutator utilization between 349 // time 0 and the left edge of this band. 350 cumUtil totalUtil 351 352 // integrator is the integrator for the left edge of this 353 // band. 354 integrator integrator 355} 356 357// NewMMUCurve returns an MMU curve for the given mutator utilization 358// function. 359func NewMMUCurve(utils [][]MutatorUtil) *MMUCurve { 360 series := make([]mmuSeries, len(utils)) 361 for i, util := range utils { 362 series[i] = newMMUSeries(util) 363 } 364 return &MMUCurve{series} 365} 366 367// bandsPerSeries is the number of bands to divide each series into. 368// This is only changed by tests. 369var bandsPerSeries = 1000 370 371func newMMUSeries(util []MutatorUtil) mmuSeries { 372 // Compute cumulative sum. 373 sums := make([]totalUtil, len(util)) 374 var prev MutatorUtil 375 var sum totalUtil 376 for j, u := range util { 377 sum += totalUtilOf(prev.Util, u.Time-prev.Time) 378 sums[j] = sum 379 prev = u 380 } 381 382 // Divide the utilization curve up into equal size 383 // non-overlapping "bands" and compute a summary for each of 384 // these bands. 385 // 386 // Compute the duration of each band. 387 numBands := bandsPerSeries 388 if numBands > len(util) { 389 // There's no point in having lots of bands if there 390 // aren't many events. 391 numBands = len(util) 392 } 393 dur := util[len(util)-1].Time - util[0].Time 394 bandDur := (dur + int64(numBands) - 1) / int64(numBands) 395 if bandDur < 1 { 396 bandDur = 1 397 } 398 // Compute the bands. There are numBands+1 bands in order to 399 // record the final cumulative sum. 400 bands := make([]mmuBand, numBands+1) 401 s := mmuSeries{util, sums, bands, bandDur} 402 leftSum := integrator{&s, 0} 403 for i := range bands { 404 startTime, endTime := s.bandTime(i) 405 cumUtil := leftSum.advance(startTime) 406 predIdx := leftSum.pos 407 minUtil := 1.0 408 for i := predIdx; i < len(util) && util[i].Time < endTime; i++ { 409 minUtil = math.Min(minUtil, util[i].Util) 410 } 411 bands[i] = mmuBand{minUtil, cumUtil, leftSum} 412 } 413 414 return s 415} 416 417func (s *mmuSeries) bandTime(i int) (start, end int64) { 418 start = int64(i)*s.bandDur + s.util[0].Time 419 end = start + s.bandDur 420 return 421} 422 423type bandUtil struct { 424 // Utilization series index 425 series int 426 // Band index 427 i int 428 // Lower bound of mutator utilization for all windows 429 // with a left edge in this band. 430 utilBound float64 431} 432 433type bandUtilHeap []bandUtil 434 435func (h bandUtilHeap) Len() int { 436 return len(h) 437} 438 439func (h bandUtilHeap) Less(i, j int) bool { 440 return h[i].utilBound < h[j].utilBound 441} 442 443func (h bandUtilHeap) Swap(i, j int) { 444 h[i], h[j] = h[j], h[i] 445} 446 447func (h *bandUtilHeap) Push(x any) { 448 *h = append(*h, x.(bandUtil)) 449} 450 451func (h *bandUtilHeap) Pop() any { 452 x := (*h)[len(*h)-1] 453 *h = (*h)[:len(*h)-1] 454 return x 455} 456 457// UtilWindow is a specific window at Time. 458type UtilWindow struct { 459 Time int64 460 // MutatorUtil is the mean mutator utilization in this window. 461 MutatorUtil float64 462} 463 464type utilHeap []UtilWindow 465 466func (h utilHeap) Len() int { 467 return len(h) 468} 469 470func (h utilHeap) Less(i, j int) bool { 471 if h[i].MutatorUtil != h[j].MutatorUtil { 472 return h[i].MutatorUtil > h[j].MutatorUtil 473 } 474 return h[i].Time > h[j].Time 475} 476 477func (h utilHeap) Swap(i, j int) { 478 h[i], h[j] = h[j], h[i] 479} 480 481func (h *utilHeap) Push(x any) { 482 *h = append(*h, x.(UtilWindow)) 483} 484 485func (h *utilHeap) Pop() any { 486 x := (*h)[len(*h)-1] 487 *h = (*h)[:len(*h)-1] 488 return x 489} 490 491// An accumulator takes a windowed mutator utilization function and 492// tracks various statistics for that function. 493type accumulator struct { 494 mmu float64 495 496 // bound is the mutator utilization bound where adding any 497 // mutator utilization above this bound cannot affect the 498 // accumulated statistics. 499 bound float64 500 501 // Worst N window tracking 502 nWorst int 503 wHeap utilHeap 504 505 // Mutator utilization distribution tracking 506 mud *mud 507 // preciseMass is the distribution mass that must be precise 508 // before accumulation is stopped. 509 preciseMass float64 510 // lastTime and lastMU are the previous point added to the 511 // windowed mutator utilization function. 512 lastTime int64 513 lastMU float64 514} 515 516// resetTime declares a discontinuity in the windowed mutator 517// utilization function by resetting the current time. 518func (acc *accumulator) resetTime() { 519 // This only matters for distribution collection, since that's 520 // the only thing that depends on the progression of the 521 // windowed mutator utilization function. 522 acc.lastTime = math.MaxInt64 523} 524 525// addMU adds a point to the windowed mutator utilization function at 526// (time, mu). This must be called for monotonically increasing values 527// of time. 528// 529// It returns true if further calls to addMU would be pointless. 530func (acc *accumulator) addMU(time int64, mu float64, window time.Duration) bool { 531 if mu < acc.mmu { 532 acc.mmu = mu 533 } 534 acc.bound = acc.mmu 535 536 if acc.nWorst == 0 { 537 // If the minimum has reached zero, it can't go any 538 // lower, so we can stop early. 539 return mu == 0 540 } 541 542 // Consider adding this window to the n worst. 543 if len(acc.wHeap) < acc.nWorst || mu < acc.wHeap[0].MutatorUtil { 544 // This window is lower than the K'th worst window. 545 // 546 // Check if there's any overlapping window 547 // already in the heap and keep whichever is 548 // worse. 549 for i, ui := range acc.wHeap { 550 if time+int64(window) > ui.Time && ui.Time+int64(window) > time { 551 if ui.MutatorUtil <= mu { 552 // Keep the first window. 553 goto keep 554 } else { 555 // Replace it with this window. 556 heap.Remove(&acc.wHeap, i) 557 break 558 } 559 } 560 } 561 562 heap.Push(&acc.wHeap, UtilWindow{time, mu}) 563 if len(acc.wHeap) > acc.nWorst { 564 heap.Pop(&acc.wHeap) 565 } 566 keep: 567 } 568 569 if len(acc.wHeap) < acc.nWorst { 570 // We don't have N windows yet, so keep accumulating. 571 acc.bound = 1.0 572 } else { 573 // Anything above the least worst window has no effect. 574 acc.bound = math.Max(acc.bound, acc.wHeap[0].MutatorUtil) 575 } 576 577 if acc.mud != nil { 578 if acc.lastTime != math.MaxInt64 { 579 // Update distribution. 580 acc.mud.add(acc.lastMU, mu, float64(time-acc.lastTime)) 581 } 582 acc.lastTime, acc.lastMU = time, mu 583 if _, mudBound, ok := acc.mud.approxInvCumulativeSum(); ok { 584 acc.bound = math.Max(acc.bound, mudBound) 585 } else { 586 // We haven't accumulated enough total precise 587 // mass yet to even reach our goal, so keep 588 // accumulating. 589 acc.bound = 1 590 } 591 // It's not worth checking percentiles every time, so 592 // just keep accumulating this band. 593 return false 594 } 595 596 // If we've found enough 0 utilizations, we can stop immediately. 597 return len(acc.wHeap) == acc.nWorst && acc.wHeap[0].MutatorUtil == 0 598} 599 600// MMU returns the minimum mutator utilization for the given time 601// window. This is the minimum utilization for all windows of this 602// duration across the execution. The returned value is in the range 603// [0, 1]. 604func (c *MMUCurve) MMU(window time.Duration) (mmu float64) { 605 acc := accumulator{mmu: 1.0, bound: 1.0} 606 c.mmu(window, &acc) 607 return acc.mmu 608} 609 610// Examples returns n specific examples of the lowest mutator 611// utilization for the given window size. The returned windows will be 612// disjoint (otherwise there would be a huge number of 613// mostly-overlapping windows at the single lowest point). There are 614// no guarantees on which set of disjoint windows this returns. 615func (c *MMUCurve) Examples(window time.Duration, n int) (worst []UtilWindow) { 616 acc := accumulator{mmu: 1.0, bound: 1.0, nWorst: n} 617 c.mmu(window, &acc) 618 sort.Sort(sort.Reverse(acc.wHeap)) 619 return ([]UtilWindow)(acc.wHeap) 620} 621 622// MUD returns mutator utilization distribution quantiles for the 623// given window size. 624// 625// The mutator utilization distribution is the distribution of mean 626// mutator utilization across all windows of the given window size in 627// the trace. 628// 629// The minimum mutator utilization is the minimum (0th percentile) of 630// this distribution. (However, if only the minimum is desired, it's 631// more efficient to use the MMU method.) 632func (c *MMUCurve) MUD(window time.Duration, quantiles []float64) []float64 { 633 if len(quantiles) == 0 { 634 return []float64{} 635 } 636 637 // Each unrefined band contributes a known total mass to the 638 // distribution (bandDur except at the end), but in an unknown 639 // way. However, we know that all the mass it contributes must 640 // be at or above its worst-case mean mutator utilization. 641 // 642 // Hence, we refine bands until the highest desired 643 // distribution quantile is less than the next worst-case mean 644 // mutator utilization. At this point, all further 645 // contributions to the distribution must be beyond the 646 // desired quantile and hence cannot affect it. 647 // 648 // First, find the highest desired distribution quantile. 649 maxQ := quantiles[0] 650 for _, q := range quantiles { 651 if q > maxQ { 652 maxQ = q 653 } 654 } 655 // The distribution's mass is in units of time (it's not 656 // normalized because this would make it more annoying to 657 // account for future contributions of unrefined bands). The 658 // total final mass will be the duration of the trace itself 659 // minus the window size. Using this, we can compute the mass 660 // corresponding to quantile maxQ. 661 var duration int64 662 for _, s := range c.series { 663 duration1 := s.util[len(s.util)-1].Time - s.util[0].Time 664 if duration1 >= int64(window) { 665 duration += duration1 - int64(window) 666 } 667 } 668 qMass := float64(duration) * maxQ 669 670 // Accumulate the MUD until we have precise information for 671 // everything to the left of qMass. 672 acc := accumulator{mmu: 1.0, bound: 1.0, preciseMass: qMass, mud: new(mud)} 673 acc.mud.setTrackMass(qMass) 674 c.mmu(window, &acc) 675 676 // Evaluate the quantiles on the accumulated MUD. 677 out := make([]float64, len(quantiles)) 678 for i := range out { 679 mu, _ := acc.mud.invCumulativeSum(float64(duration) * quantiles[i]) 680 if math.IsNaN(mu) { 681 // There are a few legitimate ways this can 682 // happen: 683 // 684 // 1. If the window is the full trace 685 // duration, then the windowed MU function is 686 // only defined at a single point, so the MU 687 // distribution is not well-defined. 688 // 689 // 2. If there are no events, then the MU 690 // distribution has no mass. 691 // 692 // Either way, all of the quantiles will have 693 // converged toward the MMU at this point. 694 mu = acc.mmu 695 } 696 out[i] = mu 697 } 698 return out 699} 700 701func (c *MMUCurve) mmu(window time.Duration, acc *accumulator) { 702 if window <= 0 { 703 acc.mmu = 0 704 return 705 } 706 707 var bandU bandUtilHeap 708 windows := make([]time.Duration, len(c.series)) 709 for i, s := range c.series { 710 windows[i] = window 711 if max := time.Duration(s.util[len(s.util)-1].Time - s.util[0].Time); window > max { 712 windows[i] = max 713 } 714 715 bandU1 := bandUtilHeap(s.mkBandUtil(i, windows[i])) 716 if bandU == nil { 717 bandU = bandU1 718 } else { 719 bandU = append(bandU, bandU1...) 720 } 721 } 722 723 // Process bands from lowest utilization bound to highest. 724 heap.Init(&bandU) 725 726 // Refine each band into a precise window and MMU until 727 // refining the next lowest band can no longer affect the MMU 728 // or windows. 729 for len(bandU) > 0 && bandU[0].utilBound < acc.bound { 730 i := bandU[0].series 731 c.series[i].bandMMU(bandU[0].i, windows[i], acc) 732 heap.Pop(&bandU) 733 } 734} 735 736func (c *mmuSeries) mkBandUtil(series int, window time.Duration) []bandUtil { 737 // For each band, compute the worst-possible total mutator 738 // utilization for all windows that start in that band. 739 740 // minBands is the minimum number of bands a window can span 741 // and maxBands is the maximum number of bands a window can 742 // span in any alignment. 743 minBands := int((int64(window) + c.bandDur - 1) / c.bandDur) 744 maxBands := int((int64(window) + 2*(c.bandDur-1)) / c.bandDur) 745 if window > 1 && maxBands < 2 { 746 panic("maxBands < 2") 747 } 748 tailDur := int64(window) % c.bandDur 749 nUtil := len(c.bands) - maxBands + 1 750 if nUtil < 0 { 751 nUtil = 0 752 } 753 bandU := make([]bandUtil, nUtil) 754 for i := range bandU { 755 // To compute the worst-case MU, we assume the minimum 756 // for any bands that are only partially overlapped by 757 // some window and the mean for any bands that are 758 // completely covered by all windows. 759 var util totalUtil 760 761 // Find the lowest and second lowest of the partial 762 // bands. 763 l := c.bands[i].minUtil 764 r1 := c.bands[i+minBands-1].minUtil 765 r2 := c.bands[i+maxBands-1].minUtil 766 minBand := math.Min(l, math.Min(r1, r2)) 767 // Assume the worst window maximally overlaps the 768 // worst minimum and then the rest overlaps the second 769 // worst minimum. 770 if minBands == 1 { 771 util += totalUtilOf(minBand, int64(window)) 772 } else { 773 util += totalUtilOf(minBand, c.bandDur) 774 midBand := 0.0 775 switch { 776 case minBand == l: 777 midBand = math.Min(r1, r2) 778 case minBand == r1: 779 midBand = math.Min(l, r2) 780 case minBand == r2: 781 midBand = math.Min(l, r1) 782 } 783 util += totalUtilOf(midBand, tailDur) 784 } 785 786 // Add the total mean MU of bands that are completely 787 // overlapped by all windows. 788 if minBands > 2 { 789 util += c.bands[i+minBands-1].cumUtil - c.bands[i+1].cumUtil 790 } 791 792 bandU[i] = bandUtil{series, i, util.mean(window)} 793 } 794 795 return bandU 796} 797 798// bandMMU computes the precise minimum mutator utilization for 799// windows with a left edge in band bandIdx. 800func (c *mmuSeries) bandMMU(bandIdx int, window time.Duration, acc *accumulator) { 801 util := c.util 802 803 // We think of the mutator utilization over time as the 804 // box-filtered utilization function, which we call the 805 // "windowed mutator utilization function". The resulting 806 // function is continuous and piecewise linear (unless 807 // window==0, which we handle elsewhere), where the boundaries 808 // between segments occur when either edge of the window 809 // encounters a change in the instantaneous mutator 810 // utilization function. Hence, the minimum of this function 811 // will always occur when one of the edges of the window 812 // aligns with a utilization change, so these are the only 813 // points we need to consider. 814 // 815 // We compute the mutator utilization function incrementally 816 // by tracking the integral from t=0 to the left edge of the 817 // window and to the right edge of the window. 818 left := c.bands[bandIdx].integrator 819 right := left 820 time, endTime := c.bandTime(bandIdx) 821 if utilEnd := util[len(util)-1].Time - int64(window); utilEnd < endTime { 822 endTime = utilEnd 823 } 824 acc.resetTime() 825 for { 826 // Advance edges to time and time+window. 827 mu := (right.advance(time+int64(window)) - left.advance(time)).mean(window) 828 if acc.addMU(time, mu, window) { 829 break 830 } 831 if time == endTime { 832 break 833 } 834 835 // The maximum slope of the windowed mutator 836 // utilization function is 1/window, so we can always 837 // advance the time by at least (mu - mmu) * window 838 // without dropping below mmu. 839 minTime := time + int64((mu-acc.bound)*float64(window)) 840 841 // Advance the window to the next time where either 842 // the left or right edge of the window encounters a 843 // change in the utilization curve. 844 if t1, t2 := left.next(time), right.next(time+int64(window))-int64(window); t1 < t2 { 845 time = t1 846 } else { 847 time = t2 848 } 849 if time < minTime { 850 time = minTime 851 } 852 if time >= endTime { 853 // For MMUs we could stop here, but for MUDs 854 // it's important that we span the entire 855 // band. 856 time = endTime 857 } 858 } 859} 860 861// An integrator tracks a position in a utilization function and 862// integrates it. 863type integrator struct { 864 u *mmuSeries 865 // pos is the index in u.util of the current time's non-strict 866 // predecessor. 867 pos int 868} 869 870// advance returns the integral of the utilization function from 0 to 871// time. advance must be called on monotonically increasing values of 872// times. 873func (in *integrator) advance(time int64) totalUtil { 874 util, pos := in.u.util, in.pos 875 // Advance pos until pos+1 is time's strict successor (making 876 // pos time's non-strict predecessor). 877 // 878 // Very often, this will be nearby, so we optimize that case, 879 // but it may be arbitrarily far away, so we handled that 880 // efficiently, too. 881 const maxSeq = 8 882 if pos+maxSeq < len(util) && util[pos+maxSeq].Time > time { 883 // Nearby. Use a linear scan. 884 for pos+1 < len(util) && util[pos+1].Time <= time { 885 pos++ 886 } 887 } else { 888 // Far. Binary search for time's strict successor. 889 l, r := pos, len(util) 890 for l < r { 891 h := int(uint(l+r) >> 1) 892 if util[h].Time <= time { 893 l = h + 1 894 } else { 895 r = h 896 } 897 } 898 pos = l - 1 // Non-strict predecessor. 899 } 900 in.pos = pos 901 var partial totalUtil 902 if time != util[pos].Time { 903 partial = totalUtilOf(util[pos].Util, time-util[pos].Time) 904 } 905 return in.u.sums[pos] + partial 906} 907 908// next returns the smallest time t' > time of a change in the 909// utilization function. 910func (in *integrator) next(time int64) int64 { 911 for _, u := range in.u.util[in.pos:] { 912 if u.Time > time { 913 return u.Time 914 } 915 } 916 return 1<<63 - 1 917} 918 919func isGCSTW(r Range) bool { 920 return strings.HasPrefix(r.Name, "stop-the-world") && strings.Contains(r.Name, "GC") 921} 922 923func isGCMarkAssist(r Range) bool { 924 return r.Name == "GC mark assist" 925} 926 927func isGCSweep(r Range) bool { 928 return r.Name == "GC incremental sweep" 929} 930