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.
9 "internal/trace/v2/testtrace"
16 // aeq returns true if x and y are equal up to 8 digits (1 part in 100
18 func aeq(x, y float64) bool {
23 factor := 1 - math.Pow(10, -digits+1)
24 return x*factor <= y && y*factor <= x
27 func TestMMU(t *testing.T) {
31 // 1.0 ***** ***** *****
35 util := [][]MutatorUtil{{
43 mmuCurve := NewMMUCurve(util)
45 for _, test := range []struct {
51 {time.Millisecond, 0, []float64{0, 0}},
52 {time.Second, 0, []float64{0, 0}},
53 {2 * time.Second, 0.5, []float64{0.5, 0.5}},
54 {3 * time.Second, 1 / 3.0, []float64{1 / 3.0}},
55 {4 * time.Second, 0.5, []float64{0.5}},
56 {5 * time.Second, 3 / 5.0, []float64{3 / 5.0}},
57 {6 * time.Second, 3 / 5.0, []float64{3 / 5.0}},
59 if got := mmuCurve.MMU(test.window); !aeq(test.want, got) {
60 t.Errorf("for %s window, want mu = %f, got %f", test.window, test.want, got)
62 worst := mmuCurve.Examples(test.window, 2)
63 // Which exact windows are returned is unspecified
64 // (and depends on the exact banding), so we just
65 // check that we got the right number with the right
67 if len(worst) != len(test.worst) {
68 t.Errorf("for %s window, want worst %v, got %v", test.window, test.worst, worst)
70 for i := range worst {
71 if worst[i].MutatorUtil != test.worst[i] {
72 t.Errorf("for %s window, want worst %v, got %v", test.window, test.worst, worst)
80 func TestMMUTrace(t *testing.T) {
81 // Can't be t.Parallel() because it modifies the
82 // testingOneBand package variable.
84 // test input too big for all.bash
85 t.Skip("skipping in -short mode")
87 check := func(t *testing.T, mu [][]MutatorUtil) {
88 mmuCurve := NewMMUCurve(mu)
90 // Test the optimized implementation against the "obviously
91 // correct" implementation.
92 for window := time.Nanosecond; window < 10*time.Second; window *= 10 {
93 want := mmuSlow(mu[0], window)
94 got := mmuCurve.MMU(window)
96 t.Errorf("want %f, got %f mutator utilization in window %s", want, got, window)
100 // Test MUD with band optimization against MUD without band
101 // optimization. We don't have a simple testing implementation
102 // of MUDs (the simplest implementation is still quite
103 // complex), but this is still a pretty good test.
104 defer func(old int) { bandsPerSeries = old }(bandsPerSeries)
106 mmuCurve2 := NewMMUCurve(mu)
107 quantiles := []float64{0, 1 - .999, 1 - .99}
108 for window := time.Microsecond; window < time.Second; window *= 10 {
109 mud1 := mmuCurve.MUD(window, quantiles)
110 mud2 := mmuCurve2.MUD(window, quantiles)
111 for i := range mud1 {
112 if !aeq(mud1[i], mud2[i]) {
113 t.Errorf("for quantiles %v at window %v, want %v, got %v", quantiles, window, mud2, mud1)
119 t.Run("V1", func(t *testing.T) {
120 data, err := os.ReadFile("testdata/stress_1_10_good")
122 t.Fatalf("failed to read input file: %v", err)
124 events, err := Parse(bytes.NewReader(data), "")
126 t.Fatalf("failed to parse trace: %s", err)
128 check(t, MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist))
130 t.Run("V2", func(t *testing.T) {
131 testPath := "v2/testdata/tests/go122-gc-stress.test"
132 r, _, err := testtrace.ParseFile(testPath)
134 t.Fatalf("malformed test %s: bad trace file: %v", testPath, err)
136 // Pass the trace through MutatorUtilizationV2.
137 mu, err := MutatorUtilizationV2(r, UtilSTW|UtilBackground|UtilAssist)
139 t.Fatalf("failed to compute mutator utilization or parse trace: %v", err)
145 func BenchmarkMMU(b *testing.B) {
146 data, err := os.ReadFile("testdata/stress_1_10_good")
148 b.Fatalf("failed to read input file: %v", err)
150 events, err := Parse(bytes.NewReader(data), "")
152 b.Fatalf("failed to parse trace: %s", err)
154 mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist|UtilSweep)
157 for i := 0; i < b.N; i++ {
158 mmuCurve := NewMMUCurve(mu)
159 xMin, xMax := time.Microsecond, time.Second
160 logMin, logMax := math.Log(float64(xMin)), math.Log(float64(xMax))
162 for i := 0; i < samples; i++ {
163 window := time.Duration(math.Exp(float64(i)/(samples-1)*(logMax-logMin) + logMin))
169 func mmuSlow(util []MutatorUtil, window time.Duration) (mmu float64) {
170 if max := time.Duration(util[len(util)-1].Time - util[0].Time); window > max {
176 // muInWindow returns the mean mutator utilization between
177 // util[0].Time and end.
178 muInWindow := func(util []MutatorUtil, end int64) float64 {
180 var prevU MutatorUtil
181 for _, u := range util {
183 total += prevU.Util * float64(end-prevU.Time)
186 total += prevU.Util * float64(u.Time-prevU.Time)
189 return total / float64(end-util[0].Time)
192 for i, u := range util {
193 if u.Time+int64(window) > util[len(util)-1].Time {
196 mmu = math.Min(mmu, muInWindow(util[i:], u.Time+int64(window)))
200 // Consider all left-aligned windows.
202 // Reverse the trace. Slightly subtle because each MutatorUtil
204 rutil := make([]MutatorUtil, len(util))
205 if util[len(util)-1].Util != 0 {
206 panic("irreversible trace")
208 for i, u := range util {
211 util1 = util[i-1].Util
213 rutil[len(rutil)-i-1] = MutatorUtil{Time: -u.Time, Util: util1}
216 // Consider all right-aligned windows.