github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/runtime/metrics_test.go (about) 1 // Copyright 2020 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 runtime_test 6 7 import ( 8 "runtime" 9 "runtime/metrics" 10 "sort" 11 "strings" 12 "testing" 13 "time" 14 "unsafe" 15 ) 16 17 func prepareAllMetricsSamples() (map[string]metrics.Description, []metrics.Sample) { 18 all := metrics.All() 19 samples := make([]metrics.Sample, len(all)) 20 descs := make(map[string]metrics.Description) 21 for i := range all { 22 samples[i].Name = all[i].Name 23 descs[all[i].Name] = all[i] 24 } 25 return descs, samples 26 } 27 28 func TestReadMetrics(t *testing.T) { 29 // Tests whether readMetrics produces values aligning 30 // with ReadMemStats while the world is stopped. 31 var mstats runtime.MemStats 32 _, samples := prepareAllMetricsSamples() 33 runtime.ReadMetricsSlow(&mstats, unsafe.Pointer(&samples[0]), len(samples), cap(samples)) 34 35 checkUint64 := func(t *testing.T, m string, got, want uint64) { 36 t.Helper() 37 if got != want { 38 t.Errorf("metric %q: got %d, want %d", m, got, want) 39 } 40 } 41 42 // Check to make sure the values we read line up with other values we read. 43 for i := range samples { 44 switch name := samples[i].Name; name { 45 case "/memory/classes/heap/free:bytes": 46 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapIdle-mstats.HeapReleased) 47 case "/memory/classes/heap/released:bytes": 48 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapReleased) 49 case "/memory/classes/heap/objects:bytes": 50 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapAlloc) 51 case "/memory/classes/heap/unused:bytes": 52 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapInuse-mstats.HeapAlloc) 53 case "/memory/classes/heap/stacks:bytes": 54 checkUint64(t, name, samples[i].Value.Uint64(), mstats.StackInuse) 55 case "/memory/classes/metadata/mcache/free:bytes": 56 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MCacheSys-mstats.MCacheInuse) 57 case "/memory/classes/metadata/mcache/inuse:bytes": 58 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MCacheInuse) 59 case "/memory/classes/metadata/mspan/free:bytes": 60 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MSpanSys-mstats.MSpanInuse) 61 case "/memory/classes/metadata/mspan/inuse:bytes": 62 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MSpanInuse) 63 case "/memory/classes/metadata/other:bytes": 64 checkUint64(t, name, samples[i].Value.Uint64(), mstats.GCSys) 65 case "/memory/classes/os-stacks:bytes": 66 checkUint64(t, name, samples[i].Value.Uint64(), mstats.StackSys-mstats.StackInuse) 67 case "/memory/classes/other:bytes": 68 checkUint64(t, name, samples[i].Value.Uint64(), mstats.OtherSys) 69 case "/memory/classes/profiling/buckets:bytes": 70 checkUint64(t, name, samples[i].Value.Uint64(), mstats.BuckHashSys) 71 case "/memory/classes/total:bytes": 72 checkUint64(t, name, samples[i].Value.Uint64(), mstats.Sys) 73 case "/gc/heap/allocs-by-size:bytes": 74 hist := samples[i].Value.Float64Histogram() 75 // Skip size class 0 in BySize, because it's always empty and not represented 76 // in the histogram. 77 for i, sc := range mstats.BySize[1:] { 78 if b, s := hist.Buckets[i+1], float64(sc.Size+1); b != s { 79 t.Errorf("bucket does not match size class: got %f, want %f", b, s) 80 // The rest of the checks aren't expected to work anyway. 81 continue 82 } 83 if c, m := hist.Counts[i], sc.Mallocs; c != m { 84 t.Errorf("histogram counts do not much BySize for class %d: got %d, want %d", i, c, m) 85 } 86 } 87 case "/gc/heap/frees-by-size:bytes": 88 hist := samples[i].Value.Float64Histogram() 89 // Skip size class 0 in BySize, because it's always empty and not represented 90 // in the histogram. 91 for i, sc := range mstats.BySize[1:] { 92 if b, s := hist.Buckets[i+1], float64(sc.Size+1); b != s { 93 t.Errorf("bucket does not match size class: got %f, want %f", b, s) 94 // The rest of the checks aren't expected to work anyway. 95 continue 96 } 97 if c, f := hist.Counts[i], sc.Frees; c != f { 98 t.Errorf("histogram counts do not much BySize for class %d: got %d, want %d", i, c, f) 99 } 100 } 101 case "/gc/heap/objects:objects": 102 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapObjects) 103 case "/gc/heap/goal:bytes": 104 checkUint64(t, name, samples[i].Value.Uint64(), mstats.NextGC) 105 case "/gc/cycles/automatic:gc-cycles": 106 checkUint64(t, name, samples[i].Value.Uint64(), uint64(mstats.NumGC-mstats.NumForcedGC)) 107 case "/gc/cycles/forced:gc-cycles": 108 checkUint64(t, name, samples[i].Value.Uint64(), uint64(mstats.NumForcedGC)) 109 case "/gc/cycles/total:gc-cycles": 110 checkUint64(t, name, samples[i].Value.Uint64(), uint64(mstats.NumGC)) 111 } 112 } 113 } 114 115 func TestReadMetricsConsistency(t *testing.T) { 116 // Tests whether readMetrics produces consistent, sensible values. 117 // The values are read concurrently with the runtime doing other 118 // things (e.g. allocating) so what we read can't reasonably compared 119 // to runtime values. 120 121 // Run a few GC cycles to get some of the stats to be non-zero. 122 runtime.GC() 123 runtime.GC() 124 runtime.GC() 125 126 // Read all the supported metrics through the metrics package. 127 descs, samples := prepareAllMetricsSamples() 128 metrics.Read(samples) 129 130 // Check to make sure the values we read make sense. 131 var totalVirtual struct { 132 got, want uint64 133 } 134 var objects struct { 135 alloc, free *metrics.Float64Histogram 136 total uint64 137 } 138 var gc struct { 139 numGC uint64 140 pauses uint64 141 } 142 for i := range samples { 143 kind := samples[i].Value.Kind() 144 if want := descs[samples[i].Name].Kind; kind != want { 145 t.Errorf("supported metric %q has unexpected kind: got %d, want %d", samples[i].Name, kind, want) 146 continue 147 } 148 if samples[i].Name != "/memory/classes/total:bytes" && strings.HasPrefix(samples[i].Name, "/memory/classes") { 149 v := samples[i].Value.Uint64() 150 totalVirtual.want += v 151 152 // None of these stats should ever get this big. 153 // If they do, there's probably overflow involved, 154 // usually due to bad accounting. 155 if int64(v) < 0 { 156 t.Errorf("%q has high/negative value: %d", samples[i].Name, v) 157 } 158 } 159 switch samples[i].Name { 160 case "/memory/classes/total:bytes": 161 totalVirtual.got = samples[i].Value.Uint64() 162 case "/gc/heap/objects:objects": 163 objects.total = samples[i].Value.Uint64() 164 case "/gc/heap/allocs-by-size:bytes": 165 objects.alloc = samples[i].Value.Float64Histogram() 166 case "/gc/heap/frees-by-size:bytes": 167 objects.free = samples[i].Value.Float64Histogram() 168 case "/gc/cycles:gc-cycles": 169 gc.numGC = samples[i].Value.Uint64() 170 case "/gc/pauses:seconds": 171 h := samples[i].Value.Float64Histogram() 172 gc.pauses = 0 173 for i := range h.Counts { 174 gc.pauses += h.Counts[i] 175 } 176 case "/sched/goroutines:goroutines": 177 if samples[i].Value.Uint64() < 1 { 178 t.Error("number of goroutines is less than one") 179 } 180 } 181 } 182 if totalVirtual.got != totalVirtual.want { 183 t.Errorf(`"/memory/classes/total:bytes" does not match sum of /memory/classes/**: got %d, want %d`, totalVirtual.got, totalVirtual.want) 184 } 185 if b, c := len(objects.alloc.Buckets), len(objects.alloc.Counts); b != c+1 { 186 t.Errorf("allocs-by-size has wrong bucket or counts length: %d buckets, %d counts", b, c) 187 } 188 if b, c := len(objects.free.Buckets), len(objects.free.Counts); b != c+1 { 189 t.Errorf("frees-by-size has wrong bucket or counts length: %d buckets, %d counts", b, c) 190 } 191 if len(objects.alloc.Buckets) != len(objects.free.Buckets) { 192 t.Error("allocs-by-size and frees-by-size buckets don't match in length") 193 } else if len(objects.alloc.Counts) != len(objects.free.Counts) { 194 t.Error("allocs-by-size and frees-by-size counts don't match in length") 195 } else { 196 for i := range objects.alloc.Buckets { 197 ba := objects.alloc.Buckets[i] 198 bf := objects.free.Buckets[i] 199 if ba != bf { 200 t.Errorf("bucket %d is different for alloc and free hists: %f != %f", i, ba, bf) 201 } 202 } 203 if !t.Failed() { 204 got, want := uint64(0), objects.total 205 for i := range objects.alloc.Counts { 206 if objects.alloc.Counts[i] < objects.free.Counts[i] { 207 t.Errorf("found more allocs than frees in object dist bucket %d", i) 208 continue 209 } 210 got += objects.alloc.Counts[i] - objects.free.Counts[i] 211 } 212 if got != want { 213 t.Errorf("object distribution counts don't match count of live objects: got %d, want %d", got, want) 214 } 215 } 216 } 217 // The current GC has at least 2 pauses per GC. 218 // Check to see if that value makes sense. 219 if gc.pauses < gc.numGC*2 { 220 t.Errorf("fewer pauses than expected: got %d, want at least %d", gc.pauses, gc.numGC*2) 221 } 222 } 223 224 func BenchmarkReadMetricsLatency(b *testing.B) { 225 stop := applyGCLoad(b) 226 227 // Spend this much time measuring latencies. 228 latencies := make([]time.Duration, 0, 1024) 229 _, samples := prepareAllMetricsSamples() 230 231 // Hit metrics.Read continuously and measure. 232 b.ResetTimer() 233 for i := 0; i < b.N; i++ { 234 start := time.Now() 235 metrics.Read(samples) 236 latencies = append(latencies, time.Now().Sub(start)) 237 } 238 // Make sure to stop the timer before we wait! The load created above 239 // is very heavy-weight and not easy to stop, so we could end up 240 // confusing the benchmarking framework for small b.N. 241 b.StopTimer() 242 stop() 243 244 // Disable the default */op metrics. 245 // ns/op doesn't mean anything because it's an average, but we 246 // have a sleep in our b.N loop above which skews this significantly. 247 b.ReportMetric(0, "ns/op") 248 b.ReportMetric(0, "B/op") 249 b.ReportMetric(0, "allocs/op") 250 251 // Sort latencies then report percentiles. 252 sort.Slice(latencies, func(i, j int) bool { 253 return latencies[i] < latencies[j] 254 }) 255 b.ReportMetric(float64(latencies[len(latencies)*50/100]), "p50-ns") 256 b.ReportMetric(float64(latencies[len(latencies)*90/100]), "p90-ns") 257 b.ReportMetric(float64(latencies[len(latencies)*99/100]), "p99-ns") 258 }