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.
15 // aeq returns true if x and y are equal up to 8 digits (1 part in 100
17 func aeq(x, y float64) bool {
22 factor := 1 - math.Pow(10, -digits+1)
23 return x*factor <= y && y*factor <= x
26 func TestMMU(t *testing.T) {
30 // 1.0 ***** ***** *****
34 util := [][]MutatorUtil{{
42 mmuCurve := NewMMUCurve(util)
44 for _, test := range []struct {
50 {time.Millisecond, 0, []float64{0, 0}},
51 {time.Second, 0, []float64{0, 0}},
52 {2 * time.Second, 0.5, []float64{0.5, 0.5}},
53 {3 * time.Second, 1 / 3.0, []float64{1 / 3.0}},
54 {4 * time.Second, 0.5, []float64{0.5}},
55 {5 * time.Second, 3 / 5.0, []float64{3 / 5.0}},
56 {6 * time.Second, 3 / 5.0, []float64{3 / 5.0}},
58 if got := mmuCurve.MMU(test.window); !aeq(test.want, got) {
59 t.Errorf("for %s window, want mu = %f, got %f", test.window, test.want, got)
61 worst := mmuCurve.Examples(test.window, 2)
62 // Which exact windows are returned is unspecified
63 // (and depends on the exact banding), so we just
64 // check that we got the right number with the right
66 if len(worst) != len(test.worst) {
67 t.Errorf("for %s window, want worst %v, got %v", test.window, test.worst, worst)
69 for i := range worst {
70 if worst[i].MutatorUtil != test.worst[i] {
71 t.Errorf("for %s window, want worst %v, got %v", test.window, test.worst, worst)
79 func TestMMUTrace(t *testing.T) {
80 // Can't be t.Parallel() because it modifies the
81 // testingOneBand package variable.
83 // test input too big for all.bash
84 t.Skip("skipping in -short mode")
87 data, err := os.ReadFile("testdata/stress_1_10_good")
89 t.Fatalf("failed to read input file: %v", err)
91 _, events, err := parse(bytes.NewReader(data), "")
93 t.Fatalf("failed to parse trace: %s", err)
95 mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist)
96 mmuCurve := NewMMUCurve(mu)
98 // Test the optimized implementation against the "obviously
99 // correct" implementation.
100 for window := time.Nanosecond; window < 10*time.Second; window *= 10 {
101 want := mmuSlow(mu[0], window)
102 got := mmuCurve.MMU(window)
104 t.Errorf("want %f, got %f mutator utilization in window %s", want, got, window)
108 // Test MUD with band optimization against MUD without band
109 // optimization. We don't have a simple testing implementation
110 // of MUDs (the simplest implementation is still quite
111 // complex), but this is still a pretty good test.
112 defer func(old int) { bandsPerSeries = old }(bandsPerSeries)
114 mmuCurve2 := NewMMUCurve(mu)
115 quantiles := []float64{0, 1 - .999, 1 - .99}
116 for window := time.Microsecond; window < time.Second; window *= 10 {
117 mud1 := mmuCurve.MUD(window, quantiles)
118 mud2 := mmuCurve2.MUD(window, quantiles)
119 for i := range mud1 {
120 if !aeq(mud1[i], mud2[i]) {
121 t.Errorf("for quantiles %v at window %v, want %v, got %v", quantiles, window, mud2, mud1)
128 func BenchmarkMMU(b *testing.B) {
129 data, err := os.ReadFile("testdata/stress_1_10_good")
131 b.Fatalf("failed to read input file: %v", err)
133 _, events, err := parse(bytes.NewReader(data), "")
135 b.Fatalf("failed to parse trace: %s", err)
137 mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist|UtilSweep)
140 for i := 0; i < b.N; i++ {
141 mmuCurve := NewMMUCurve(mu)
142 xMin, xMax := time.Microsecond, time.Second
143 logMin, logMax := math.Log(float64(xMin)), math.Log(float64(xMax))
145 for i := 0; i < samples; i++ {
146 window := time.Duration(math.Exp(float64(i)/(samples-1)*(logMax-logMin) + logMin))
152 func mmuSlow(util []MutatorUtil, window time.Duration) (mmu float64) {
153 if max := time.Duration(util[len(util)-1].Time - util[0].Time); window > max {
159 // muInWindow returns the mean mutator utilization between
160 // util[0].Time and end.
161 muInWindow := func(util []MutatorUtil, end int64) float64 {
163 var prevU MutatorUtil
164 for _, u := range util {
166 total += prevU.Util * float64(end-prevU.Time)
169 total += prevU.Util * float64(u.Time-prevU.Time)
172 return total / float64(end-util[0].Time)
175 for i, u := range util {
176 if u.Time+int64(window) > util[len(util)-1].Time {
179 mmu = math.Min(mmu, muInWindow(util[i:], u.Time+int64(window)))
183 // Consider all left-aligned windows.
185 // Reverse the trace. Slightly subtle because each MutatorUtil
187 rutil := make([]MutatorUtil, len(util))
188 if util[len(util)-1].Util != 0 {
189 panic("irreversible trace")
191 for i, u := range util {
194 util1 = util[i-1].Util
196 rutil[len(rutil)-i-1] = MutatorUtil{Time: -u.Time, Util: util1}
199 // Consider all right-aligned windows.