github.com/sandwichdev/go-internals@v0.0.0-20210605002614-12311ac6b2c5/trace/gc_test.go (about) 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. 4 5 package trace 6 7 import ( 8 "bytes" 9 "math" 10 "os" 11 "testing" 12 "time" 13 ) 14 15 // aeq returns true if x and y are equal up to 8 digits (1 part in 100 16 // million). 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 // MU 30 // 1.0 ***** ***** ***** 31 // 0.5 * * * * 32 // 0.0 ***** ***** 33 // 0 1 2 3 4 5 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 // 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 65 // utilizations. 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 // Can't be t.Parallel() because it modifies the 81 // testingOneBand package variable. 82 if testing.Short() { 83 // test input too big for all.bash 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 // 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) 103 if !aeq(want, got) { 104 t.Errorf("want %f, got %f mutator utilization in window %s", want, got, window) 105 } 106 } 107 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) 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 // muInWindow returns the mean mutator utilization between 160 // util[0].Time and end. 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 // Consider all left-aligned windows. 184 update() 185 // Reverse the trace. Slightly subtle because each MutatorUtil 186 // is a *change*. 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 // Consider all right-aligned windows. 200 update() 201 return 202 }