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 data, err := ioutil.ReadFile("testdata/stress_1_10_good")
85 t.Fatalf("failed to read input file: %v", err)
87 _, events, err := parse(bytes.NewReader(data), "")
89 t.Fatalf("failed to parse trace: %s", err)
91 mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist)
92 mmuCurve := NewMMUCurve(mu)
94 // Test the optimized implementation against the "obviously
95 // correct" implementation.
96 for window := time.Nanosecond; window < 10*time.Second; window *= 10 {
97 want := mmuSlow(mu[0], window)
98 got := mmuCurve.MMU(window)
100 t.Errorf("want %f, got %f mutator utilization in window %s", want, got, window)
104 // Test MUD with band optimization against MUD without band
105 // optimization. We don't have a simple testing implementation
106 // of MUDs (the simplest implementation is still quite
107 // complex), but this is still a pretty good test.
108 defer func(old int) { bandsPerSeries = old }(bandsPerSeries)
110 mmuCurve2 := NewMMUCurve(mu)
111 quantiles := []float64{0, 1 - .999, 1 - .99}
112 for window := time.Microsecond; window < time.Second; window *= 10 {
113 mud1 := mmuCurve.MUD(window, quantiles)
114 mud2 := mmuCurve2.MUD(window, quantiles)
115 for i := range mud1 {
116 if !aeq(mud1[i], mud2[i]) {
117 t.Errorf("for quantiles %v at window %v, want %v, got %v", quantiles, window, mud2, mud1)
124 func BenchmarkMMU(b *testing.B) {
125 data, err := ioutil.ReadFile("testdata/stress_1_10_good")
127 b.Fatalf("failed to read input file: %v", err)
129 _, events, err := parse(bytes.NewReader(data), "")
131 b.Fatalf("failed to parse trace: %s", err)
133 mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist|UtilSweep)
136 for i := 0; i < b.N; i++ {
137 mmuCurve := NewMMUCurve(mu)
138 xMin, xMax := time.Microsecond, time.Second
139 logMin, logMax := math.Log(float64(xMin)), math.Log(float64(xMax))
141 for i := 0; i < samples; i++ {
142 window := time.Duration(math.Exp(float64(i)/(samples-1)*(logMax-logMin) + logMin))
148 func mmuSlow(util []MutatorUtil, window time.Duration) (mmu float64) {
149 if max := time.Duration(util[len(util)-1].Time - util[0].Time); window > max {
155 // muInWindow returns the mean mutator utilization between
156 // util[0].Time and end.
157 muInWindow := func(util []MutatorUtil, end int64) float64 {
159 var prevU MutatorUtil
160 for _, u := range util {
162 total += prevU.Util * float64(end-prevU.Time)
165 total += prevU.Util * float64(u.Time-prevU.Time)
168 return total / float64(end-util[0].Time)
171 for i, u := range util {
172 if u.Time+int64(window) > util[len(util)-1].Time {
175 mmu = math.Min(mmu, muInWindow(util[i:], u.Time+int64(window)))
179 // Consider all left-aligned windows.
181 // Reverse the trace. Slightly subtle because each MutatorUtil
183 rutil := make([]MutatorUtil, len(util))
184 if util[len(util)-1].Util != 0 {
185 panic("irreversible trace")
187 for i, u := range util {
190 util1 = util[i-1].Util
192 rutil[len(rutil)-i-1] = MutatorUtil{Time: -u.Time, Util: util1}
195 // Consider all right-aligned windows.