forwprop: Also dce from added statements from gimple_simplify
[official-gcc.git] / libgo / go / runtime / metrics_test.go
blob5d32ef469caaa9658a1052d6868abd30f1a87870
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.
5 package runtime_test
7 import (
8 "runtime"
9 "runtime/metrics"
10 "sort"
11 "strings"
12 "testing"
13 "time"
14 "unsafe"
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]
25 return descs, samples
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))
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)
42 // Check to make sure the values we read line up with other values we read.
43 var allocsBySize *metrics.Float64Histogram
44 var tinyAllocs uint64
45 var mallocs, frees uint64
46 for i := range samples {
47 switch name := samples[i].Name; name {
48 case "/memory/classes/heap/free:bytes":
49 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapIdle-mstats.HeapReleased)
50 case "/memory/classes/heap/released:bytes":
51 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapReleased)
52 case "/memory/classes/heap/objects:bytes":
53 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapAlloc)
54 case "/memory/classes/heap/unused:bytes":
55 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapInuse-mstats.HeapAlloc)
56 case "/memory/classes/heap/stacks:bytes":
57 checkUint64(t, name, samples[i].Value.Uint64(), mstats.StackInuse)
58 case "/memory/classes/metadata/mcache/free:bytes":
59 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MCacheSys-mstats.MCacheInuse)
60 case "/memory/classes/metadata/mcache/inuse:bytes":
61 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MCacheInuse)
62 case "/memory/classes/metadata/mspan/free:bytes":
63 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MSpanSys-mstats.MSpanInuse)
64 case "/memory/classes/metadata/mspan/inuse:bytes":
65 checkUint64(t, name, samples[i].Value.Uint64(), mstats.MSpanInuse)
66 case "/memory/classes/metadata/other:bytes":
67 checkUint64(t, name, samples[i].Value.Uint64(), mstats.GCSys)
68 case "/memory/classes/os-stacks:bytes":
69 checkUint64(t, name, samples[i].Value.Uint64(), mstats.StackSys-mstats.StackInuse)
70 case "/memory/classes/other:bytes":
71 checkUint64(t, name, samples[i].Value.Uint64(), mstats.OtherSys)
72 case "/memory/classes/profiling/buckets:bytes":
73 checkUint64(t, name, samples[i].Value.Uint64(), mstats.BuckHashSys)
74 case "/memory/classes/total:bytes":
75 checkUint64(t, name, samples[i].Value.Uint64(), mstats.Sys)
76 case "/gc/heap/allocs-by-size:bytes":
77 hist := samples[i].Value.Float64Histogram()
78 // Skip size class 0 in BySize, because it's always empty and not represented
79 // in the histogram.
80 for i, sc := range mstats.BySize[1:] {
81 if b, s := hist.Buckets[i+1], float64(sc.Size+1); b != s {
82 t.Errorf("bucket does not match size class: got %f, want %f", b, s)
83 // The rest of the checks aren't expected to work anyway.
84 continue
86 if c, m := hist.Counts[i], sc.Mallocs; c != m {
87 t.Errorf("histogram counts do not much BySize for class %d: got %d, want %d", i, c, m)
90 allocsBySize = hist
91 case "/gc/heap/allocs:bytes":
92 checkUint64(t, name, samples[i].Value.Uint64(), mstats.TotalAlloc)
93 case "/gc/heap/frees-by-size:bytes":
94 hist := samples[i].Value.Float64Histogram()
95 // Skip size class 0 in BySize, because it's always empty and not represented
96 // in the histogram.
97 for i, sc := range mstats.BySize[1:] {
98 if b, s := hist.Buckets[i+1], float64(sc.Size+1); b != s {
99 t.Errorf("bucket does not match size class: got %f, want %f", b, s)
100 // The rest of the checks aren't expected to work anyway.
101 continue
103 if c, f := hist.Counts[i], sc.Frees; c != f {
104 t.Errorf("histogram counts do not match BySize for class %d: got %d, want %d", i, c, f)
107 case "/gc/heap/frees:bytes":
108 checkUint64(t, name, samples[i].Value.Uint64(), mstats.TotalAlloc-mstats.HeapAlloc)
109 case "/gc/heap/tiny/allocs:objects":
110 // Currently, MemStats adds tiny alloc count to both Mallocs AND Frees.
111 // The reason for this is because MemStats couldn't be extended at the time
112 // but there was a desire to have Mallocs at least be a little more representative,
113 // while having Mallocs - Frees still represent a live object count.
114 // Unfortunately, MemStats doesn't actually export a large allocation count,
115 // so it's impossible to pull this number out directly.
117 // Check tiny allocation count outside of this loop, by using the allocs-by-size
118 // histogram in order to figure out how many large objects there are.
119 tinyAllocs = samples[i].Value.Uint64()
120 // Because the next two metrics tests are checking against Mallocs and Frees,
121 // we can't check them directly for the same reason: we need to account for tiny
122 // allocations included in Mallocs and Frees.
123 case "/gc/heap/allocs:objects":
124 mallocs = samples[i].Value.Uint64()
125 case "/gc/heap/frees:objects":
126 frees = samples[i].Value.Uint64()
127 case "/gc/heap/objects:objects":
128 checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapObjects)
129 case "/gc/heap/goal:bytes":
130 checkUint64(t, name, samples[i].Value.Uint64(), mstats.NextGC)
131 case "/gc/cycles/automatic:gc-cycles":
132 checkUint64(t, name, samples[i].Value.Uint64(), uint64(mstats.NumGC-mstats.NumForcedGC))
133 case "/gc/cycles/forced:gc-cycles":
134 checkUint64(t, name, samples[i].Value.Uint64(), uint64(mstats.NumForcedGC))
135 case "/gc/cycles/total:gc-cycles":
136 checkUint64(t, name, samples[i].Value.Uint64(), uint64(mstats.NumGC))
140 // Check tinyAllocs.
141 nonTinyAllocs := uint64(0)
142 for _, c := range allocsBySize.Counts {
143 nonTinyAllocs += c
145 checkUint64(t, "/gc/heap/tiny/allocs:objects", tinyAllocs, mstats.Mallocs-nonTinyAllocs)
147 // Check allocation and free counts.
148 checkUint64(t, "/gc/heap/allocs:objects", mallocs, mstats.Mallocs-tinyAllocs)
149 checkUint64(t, "/gc/heap/frees:objects", frees, mstats.Frees-tinyAllocs)
152 func TestReadMetricsConsistency(t *testing.T) {
153 // Tests whether readMetrics produces consistent, sensible values.
154 // The values are read concurrently with the runtime doing other
155 // things (e.g. allocating) so what we read can't reasonably compared
156 // to runtime values.
158 // Run a few GC cycles to get some of the stats to be non-zero.
159 runtime.GC()
160 runtime.GC()
161 runtime.GC()
163 // Read all the supported metrics through the metrics package.
164 descs, samples := prepareAllMetricsSamples()
165 metrics.Read(samples)
167 // Check to make sure the values we read make sense.
168 var totalVirtual struct {
169 got, want uint64
171 var objects struct {
172 alloc, free *metrics.Float64Histogram
173 allocs, frees uint64
174 allocdBytes, freedBytes uint64
175 total, totalBytes uint64
177 var gc struct {
178 numGC uint64
179 pauses uint64
181 for i := range samples {
182 kind := samples[i].Value.Kind()
183 if want := descs[samples[i].Name].Kind; kind != want {
184 t.Errorf("supported metric %q has unexpected kind: got %d, want %d", samples[i].Name, kind, want)
185 continue
187 if samples[i].Name != "/memory/classes/total:bytes" && strings.HasPrefix(samples[i].Name, "/memory/classes") {
188 v := samples[i].Value.Uint64()
189 totalVirtual.want += v
191 // None of these stats should ever get this big.
192 // If they do, there's probably overflow involved,
193 // usually due to bad accounting.
194 if int64(v) < 0 {
195 t.Errorf("%q has high/negative value: %d", samples[i].Name, v)
198 switch samples[i].Name {
199 case "/memory/classes/total:bytes":
200 totalVirtual.got = samples[i].Value.Uint64()
201 case "/memory/classes/heap/objects:bytes":
202 objects.totalBytes = samples[i].Value.Uint64()
203 case "/gc/heap/objects:objects":
204 objects.total = samples[i].Value.Uint64()
205 case "/gc/heap/allocs:bytes":
206 objects.allocdBytes = samples[i].Value.Uint64()
207 case "/gc/heap/allocs:objects":
208 objects.allocs = samples[i].Value.Uint64()
209 case "/gc/heap/allocs-by-size:bytes":
210 objects.alloc = samples[i].Value.Float64Histogram()
211 case "/gc/heap/frees:bytes":
212 objects.freedBytes = samples[i].Value.Uint64()
213 case "/gc/heap/frees:objects":
214 objects.frees = samples[i].Value.Uint64()
215 case "/gc/heap/frees-by-size:bytes":
216 objects.free = samples[i].Value.Float64Histogram()
217 case "/gc/cycles:gc-cycles":
218 gc.numGC = samples[i].Value.Uint64()
219 case "/gc/pauses:seconds":
220 h := samples[i].Value.Float64Histogram()
221 gc.pauses = 0
222 for i := range h.Counts {
223 gc.pauses += h.Counts[i]
225 case "/sched/goroutines:goroutines":
226 if samples[i].Value.Uint64() < 1 {
227 t.Error("number of goroutines is less than one")
231 if totalVirtual.got != totalVirtual.want {
232 t.Errorf(`"/memory/classes/total:bytes" does not match sum of /memory/classes/**: got %d, want %d`, totalVirtual.got, totalVirtual.want)
234 if got, want := objects.allocs-objects.frees, objects.total; got != want {
235 t.Errorf("mismatch between object alloc/free tallies and total: got %d, want %d", got, want)
237 if got, want := objects.allocdBytes-objects.freedBytes, objects.totalBytes; got != want {
238 t.Errorf("mismatch between object alloc/free tallies and total: got %d, want %d", got, want)
240 if b, c := len(objects.alloc.Buckets), len(objects.alloc.Counts); b != c+1 {
241 t.Errorf("allocs-by-size has wrong bucket or counts length: %d buckets, %d counts", b, c)
243 if b, c := len(objects.free.Buckets), len(objects.free.Counts); b != c+1 {
244 t.Errorf("frees-by-size has wrong bucket or counts length: %d buckets, %d counts", b, c)
246 if len(objects.alloc.Buckets) != len(objects.free.Buckets) {
247 t.Error("allocs-by-size and frees-by-size buckets don't match in length")
248 } else if len(objects.alloc.Counts) != len(objects.free.Counts) {
249 t.Error("allocs-by-size and frees-by-size counts don't match in length")
250 } else {
251 for i := range objects.alloc.Buckets {
252 ba := objects.alloc.Buckets[i]
253 bf := objects.free.Buckets[i]
254 if ba != bf {
255 t.Errorf("bucket %d is different for alloc and free hists: %f != %f", i, ba, bf)
258 if !t.Failed() {
259 var gotAlloc, gotFree uint64
260 want := objects.total
261 for i := range objects.alloc.Counts {
262 if objects.alloc.Counts[i] < objects.free.Counts[i] {
263 t.Errorf("found more allocs than frees in object dist bucket %d", i)
264 continue
266 gotAlloc += objects.alloc.Counts[i]
267 gotFree += objects.free.Counts[i]
269 if got := gotAlloc - gotFree; got != want {
270 t.Errorf("object distribution counts don't match count of live objects: got %d, want %d", got, want)
272 if gotAlloc != objects.allocs {
273 t.Errorf("object distribution counts don't match total allocs: got %d, want %d", gotAlloc, objects.allocs)
275 if gotFree != objects.frees {
276 t.Errorf("object distribution counts don't match total allocs: got %d, want %d", gotFree, objects.frees)
280 // The current GC has at least 2 pauses per GC.
281 // Check to see if that value makes sense.
282 if gc.pauses < gc.numGC*2 {
283 t.Errorf("fewer pauses than expected: got %d, want at least %d", gc.pauses, gc.numGC*2)
287 func BenchmarkReadMetricsLatency(b *testing.B) {
288 stop := applyGCLoad(b)
290 // Spend this much time measuring latencies.
291 latencies := make([]time.Duration, 0, 1024)
292 _, samples := prepareAllMetricsSamples()
294 // Hit metrics.Read continuously and measure.
295 b.ResetTimer()
296 for i := 0; i < b.N; i++ {
297 start := time.Now()
298 metrics.Read(samples)
299 latencies = append(latencies, time.Now().Sub(start))
301 // Make sure to stop the timer before we wait! The load created above
302 // is very heavy-weight and not easy to stop, so we could end up
303 // confusing the benchmarking framework for small b.N.
304 b.StopTimer()
305 stop()
307 // Disable the default */op metrics.
308 // ns/op doesn't mean anything because it's an average, but we
309 // have a sleep in our b.N loop above which skews this significantly.
310 b.ReportMetric(0, "ns/op")
311 b.ReportMetric(0, "B/op")
312 b.ReportMetric(0, "allocs/op")
314 // Sort latencies then report percentiles.
315 sort.Slice(latencies, func(i, j int) bool {
316 return latencies[i] < latencies[j]
318 b.ReportMetric(float64(latencies[len(latencies)*50/100]), "p50-ns")
319 b.ReportMetric(float64(latencies[len(latencies)*90/100]), "p90-ns")
320 b.ReportMetric(float64(latencies[len(latencies)*99/100]), "p99-ns")