1
2
3
4
5 package trace
6
7 import (
8 "bytes"
9 "math"
10 "os"
11 "testing"
12 "time"
13 )
14
15
16
17 func aeq(x, y float64) bool {
18 if x < 0 && y < 0 {
19 x, y = -x, -y
20 }
21 const digits = 8
22 factor := 1 - math.Pow(10, -digits+1)
23 return x*factor <= y && y*factor <= x
24 }
25
26 func TestMMU(t *testing.T) {
27 t.Parallel()
28
29
30
31
32
33
34 util := [][]MutatorUtil{{
35 {0e9, 1},
36 {1e9, 0},
37 {2e9, 1},
38 {3e9, 0},
39 {4e9, 1},
40 {5e9, 0},
41 }}
42 mmuCurve := NewMMUCurve(util)
43
44 for _, test := range []struct {
45 window time.Duration
46 want float64
47 worst []float64
48 }{
49 {0, 0, []float64{}},
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}},
57 } {
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)
60 }
61 worst := mmuCurve.Examples(test.window, 2)
62
63
64
65
66 if len(worst) != len(test.worst) {
67 t.Errorf("for %s window, want worst %v, got %v", test.window, test.worst, worst)
68 } else {
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)
72 break
73 }
74 }
75 }
76 }
77 }
78
79 func TestMMUTrace(t *testing.T) {
80
81
82 if testing.Short() {
83
84 t.Skip("skipping in -short mode")
85 }
86
87 data, err := os.ReadFile("testdata/stress_1_10_good")
88 if err != nil {
89 t.Fatalf("failed to read input file: %v", err)
90 }
91 _, events, err := parse(bytes.NewReader(data), "")
92 if err != nil {
93 t.Fatalf("failed to parse trace: %s", err)
94 }
95 mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist)
96 mmuCurve := NewMMUCurve(mu)
97
98
99
100 for window := time.Nanosecond; window < 10*time.Second; window *= 10 {
101 want := mmuSlow(mu[0], window)
102 got := mmuCurve.MMU(window)
103 if !aeq(want, got) {
104 t.Errorf("want %f, got %f mutator utilization in window %s", want, got, window)
105 }
106 }
107
108
109
110
111
112 defer func(old int) { bandsPerSeries = old }(bandsPerSeries)
113 bandsPerSeries = 1
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)
122 break
123 }
124 }
125 }
126 }
127
128 func BenchmarkMMU(b *testing.B) {
129 data, err := os.ReadFile("testdata/stress_1_10_good")
130 if err != nil {
131 b.Fatalf("failed to read input file: %v", err)
132 }
133 _, events, err := parse(bytes.NewReader(data), "")
134 if err != nil {
135 b.Fatalf("failed to parse trace: %s", err)
136 }
137 mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist|UtilSweep)
138 b.ResetTimer()
139
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))
144 const samples = 100
145 for i := 0; i < samples; i++ {
146 window := time.Duration(math.Exp(float64(i)/(samples-1)*(logMax-logMin) + logMin))
147 mmuCurve.MMU(window)
148 }
149 }
150 }
151
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 {
154 window = max
155 }
156
157 mmu = 1.0
158
159
160
161 muInWindow := func(util []MutatorUtil, end int64) float64 {
162 total := 0.0
163 var prevU MutatorUtil
164 for _, u := range util {
165 if u.Time > end {
166 total += prevU.Util * float64(end-prevU.Time)
167 break
168 }
169 total += prevU.Util * float64(u.Time-prevU.Time)
170 prevU = u
171 }
172 return total / float64(end-util[0].Time)
173 }
174 update := func() {
175 for i, u := range util {
176 if u.Time+int64(window) > util[len(util)-1].Time {
177 break
178 }
179 mmu = math.Min(mmu, muInWindow(util[i:], u.Time+int64(window)))
180 }
181 }
182
183
184 update()
185
186
187 rutil := make([]MutatorUtil, len(util))
188 if util[len(util)-1].Util != 0 {
189 panic("irreversible trace")
190 }
191 for i, u := range util {
192 util1 := 0.0
193 if i != 0 {
194 util1 = util[i-1].Util
195 }
196 rutil[len(rutil)-i-1] = MutatorUtil{Time: -u.Time, Util: util1}
197 }
198 util = rutil
199
200 update()
201 return
202 }
203
View as plain text