mirror of https://github.com/golang/go.git
internal/trace: track worst N mutator utilization windows
This will let the trace viewer show specifically when poor utilization happened and link to specific instances in the trace. Change-Id: I1f03a0f9d9a7570009bb15762e7b8b6f215e9423 Reviewed-on: https://go-review.googlesource.com/c/60793 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
parent
52ee654b25
commit
cbe04e8d70
|
|
@ -7,6 +7,7 @@ package traceparser
|
|||
import (
|
||||
"container/heap"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -239,13 +240,135 @@ func (h *bandUtilHeap) Pop() interface{} {
|
|||
return x
|
||||
}
|
||||
|
||||
// UtilWindow is a specific window at Time.
|
||||
type UtilWindow struct {
|
||||
Time int64
|
||||
// MutatorUtil is the mean mutator utilization in this window.
|
||||
MutatorUtil float64
|
||||
}
|
||||
|
||||
type utilHeap []UtilWindow
|
||||
|
||||
func (h utilHeap) Len() int {
|
||||
return len(h)
|
||||
}
|
||||
|
||||
func (h utilHeap) Less(i, j int) bool {
|
||||
if h[i].MutatorUtil != h[j].MutatorUtil {
|
||||
return h[i].MutatorUtil > h[j].MutatorUtil
|
||||
}
|
||||
return h[i].Time > h[j].Time
|
||||
}
|
||||
|
||||
func (h utilHeap) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
}
|
||||
|
||||
func (h *utilHeap) Push(x interface{}) {
|
||||
*h = append(*h, x.(UtilWindow))
|
||||
}
|
||||
|
||||
func (h *utilHeap) Pop() interface{} {
|
||||
x := (*h)[len(*h)-1]
|
||||
*h = (*h)[:len(*h)-1]
|
||||
return x
|
||||
}
|
||||
|
||||
// An accumulator collects different MMU-related statistics depending
|
||||
// on what's desired.
|
||||
type accumulator struct {
|
||||
mmu float64
|
||||
|
||||
// bound is the mutator utilization bound where adding any
|
||||
// mutator utilization above this bound cannot affect the
|
||||
// accumulated statistics.
|
||||
bound float64
|
||||
|
||||
// Worst N window tracking
|
||||
nWorst int
|
||||
wHeap utilHeap
|
||||
}
|
||||
|
||||
// addMU records mutator utilization mu over the given window starting
|
||||
// at time.
|
||||
//
|
||||
// It returns true if further calls to addMU would be pointless.
|
||||
func (acc *accumulator) addMU(time int64, mu float64, window time.Duration) bool {
|
||||
if mu < acc.mmu {
|
||||
acc.mmu = mu
|
||||
}
|
||||
acc.bound = acc.mmu
|
||||
|
||||
if acc.nWorst == 0 {
|
||||
// If the minimum has reached zero, it can't go any
|
||||
// lower, so we can stop early.
|
||||
return mu == 0
|
||||
}
|
||||
|
||||
// Consider adding this window to the n worst.
|
||||
if len(acc.wHeap) < acc.nWorst || mu < acc.wHeap[0].MutatorUtil {
|
||||
// This window is lower than the K'th worst window.
|
||||
//
|
||||
// Check if there's any overlapping window
|
||||
// already in the heap and keep whichever is
|
||||
// worse.
|
||||
for i, ui := range acc.wHeap {
|
||||
if time+int64(window) > ui.Time && ui.Time+int64(window) > time {
|
||||
if ui.MutatorUtil <= mu {
|
||||
// Keep the first window.
|
||||
goto keep
|
||||
} else {
|
||||
// Replace it with this window.
|
||||
heap.Remove(&acc.wHeap, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heap.Push(&acc.wHeap, UtilWindow{time, mu})
|
||||
if len(acc.wHeap) > acc.nWorst {
|
||||
heap.Pop(&acc.wHeap)
|
||||
}
|
||||
keep:
|
||||
}
|
||||
if len(acc.wHeap) < acc.nWorst {
|
||||
// We don't have N windows yet, so keep accumulating.
|
||||
acc.bound = 1.0
|
||||
} else {
|
||||
// Anything above the least worst window has no effect.
|
||||
acc.bound = math.Max(acc.bound, acc.wHeap[0].MutatorUtil)
|
||||
}
|
||||
|
||||
// If we've found enough 0 utilizations, we can stop immediately.
|
||||
return len(acc.wHeap) == acc.nWorst && acc.wHeap[0].MutatorUtil == 0
|
||||
}
|
||||
|
||||
// MMU returns the minimum mutator utilization for the given time
|
||||
// window. This is the minimum utilization for all windows of this
|
||||
// duration across the execution. The returned value is in the range
|
||||
// [0, 1].
|
||||
func (c *MMUCurve) MMU(window time.Duration) (mmu float64) {
|
||||
acc := accumulator{mmu: 1.0, bound: 1.0}
|
||||
c.mmu(window, &acc)
|
||||
return acc.mmu
|
||||
}
|
||||
|
||||
// Examples returns n specific examples of the lowest mutator
|
||||
// utilization for the given window size. The returned windows will be
|
||||
// disjoint (otherwise there would be a huge number of
|
||||
// mostly-overlapping windows at the single lowest point). There are
|
||||
// no guarantees on which set of disjoint windows this returns.
|
||||
func (c *MMUCurve) Examples(window time.Duration, n int) (worst []UtilWindow) {
|
||||
acc := accumulator{mmu: 1.0, bound: 1.0, nWorst: n}
|
||||
c.mmu(window, &acc)
|
||||
sort.Sort(sort.Reverse(acc.wHeap))
|
||||
return ([]UtilWindow)(acc.wHeap)
|
||||
}
|
||||
|
||||
func (c *MMUCurve) mmu(window time.Duration, acc *accumulator) {
|
||||
if window <= 0 {
|
||||
return 0
|
||||
acc.mmu = 0
|
||||
return
|
||||
}
|
||||
util := c.util
|
||||
if max := time.Duration(util[len(util)-1].Time - util[0].Time); window > max {
|
||||
|
|
@ -257,14 +380,13 @@ func (c *MMUCurve) MMU(window time.Duration) (mmu float64) {
|
|||
// Process bands from lowest utilization bound to highest.
|
||||
heap.Init(&bandU)
|
||||
|
||||
// Refine each band into a precise window and MMU until the
|
||||
// precise MMU is less than the lowest band bound.
|
||||
mmu = 1.0
|
||||
for len(bandU) > 0 && bandU[0].utilBound < mmu {
|
||||
mmu = c.bandMMU(bandU[0].i, window, mmu)
|
||||
// Refine each band into a precise window and MMU until
|
||||
// refining the next lowest band can no longer affect the MMU
|
||||
// or windows.
|
||||
for len(bandU) > 0 && bandU[0].utilBound < acc.bound {
|
||||
c.bandMMU(bandU[0].i, window, acc)
|
||||
heap.Pop(&bandU)
|
||||
}
|
||||
return mmu
|
||||
}
|
||||
|
||||
func (c *MMUCurve) mkBandUtil(window time.Duration) []bandUtil {
|
||||
|
|
@ -331,9 +453,8 @@ func (c *MMUCurve) mkBandUtil(window time.Duration) []bandUtil {
|
|||
|
||||
// bandMMU computes the precise minimum mutator utilization for
|
||||
// windows with a left edge in band bandIdx.
|
||||
func (c *MMUCurve) bandMMU(bandIdx int, window time.Duration, curMMU float64) (mmu float64) {
|
||||
func (c *MMUCurve) bandMMU(bandIdx int, window time.Duration, acc *accumulator) {
|
||||
util := c.util
|
||||
mmu = curMMU
|
||||
|
||||
// We think of the mutator utilization over time as the
|
||||
// box-filtered utilization function, which we call the
|
||||
|
|
@ -359,20 +480,15 @@ func (c *MMUCurve) bandMMU(bandIdx int, window time.Duration, curMMU float64) (m
|
|||
for {
|
||||
// Advance edges to time and time+window.
|
||||
mu := (right.advance(time+int64(window)) - left.advance(time)).mean(window)
|
||||
if mu < mmu {
|
||||
mmu = mu
|
||||
if mmu == 0 {
|
||||
// The minimum can't go any lower than
|
||||
// zero, so stop early.
|
||||
break
|
||||
}
|
||||
if acc.addMU(time, mu, window) {
|
||||
break
|
||||
}
|
||||
|
||||
// The maximum slope of the windowed mutator
|
||||
// utilization function is 1/window, so we can always
|
||||
// advance the time by at least (mu - mmu) * window
|
||||
// without dropping below mmu.
|
||||
minTime := time + int64((mu-mmu)*float64(window))
|
||||
minTime := time + int64((mu-acc.bound)*float64(window))
|
||||
|
||||
// Advance the window to the next time where either
|
||||
// the left or right edge of the window encounters a
|
||||
|
|
@ -389,7 +505,6 @@ func (c *MMUCurve) bandMMU(bandIdx int, window time.Duration, curMMU float64) (m
|
|||
break
|
||||
}
|
||||
}
|
||||
return mmu
|
||||
}
|
||||
|
||||
// An integrator tracks a position in a utilization function and
|
||||
|
|
|
|||
|
|
@ -42,19 +42,35 @@ func TestMMU(t *testing.T) {
|
|||
for _, test := range []struct {
|
||||
window time.Duration
|
||||
want float64
|
||||
worst []float64
|
||||
}{
|
||||
{0, 0},
|
||||
{time.Millisecond, 0},
|
||||
{time.Second, 0},
|
||||
{2 * time.Second, 0.5},
|
||||
{3 * time.Second, 1 / 3.0},
|
||||
{4 * time.Second, 0.5},
|
||||
{5 * time.Second, 3 / 5.0},
|
||||
{6 * time.Second, 3 / 5.0},
|
||||
{0, 0, []float64{}},
|
||||
{time.Millisecond, 0, []float64{0, 0}},
|
||||
{time.Second, 0, []float64{0, 0}},
|
||||
{2 * time.Second, 0.5, []float64{0.5, 0.5}},
|
||||
{3 * time.Second, 1 / 3.0, []float64{1 / 3.0}},
|
||||
{4 * time.Second, 0.5, []float64{0.5}},
|
||||
{5 * time.Second, 3 / 5.0, []float64{3 / 5.0}},
|
||||
{6 * time.Second, 3 / 5.0, []float64{3 / 5.0}},
|
||||
} {
|
||||
if got := mmuCurve.MMU(test.window); !aeq(test.want, got) {
|
||||
t.Errorf("for %s window, want mu = %f, got %f", test.window, test.want, got)
|
||||
}
|
||||
worst := mmuCurve.Examples(test.window, 2)
|
||||
// Which exact windows are returned is unspecified
|
||||
// (and depends on the exact banding), so we just
|
||||
// check that we got the right number with the right
|
||||
// utilizations.
|
||||
if len(worst) != len(test.worst) {
|
||||
t.Errorf("for %s window, want worst %v, got %v", test.window, test.worst, worst)
|
||||
} else {
|
||||
for i := range worst {
|
||||
if worst[i].MutatorUtil != test.worst[i] {
|
||||
t.Errorf("for %s window, want worst %v, got %v", test.window, test.worst, worst)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue