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.
17 func prepareAllMetricsSamples() (map[string]metrics
.Description
, []metrics
.Sample
) {
19 samples
:= make([]metrics
.Sample
, len(all
))
20 descs
:= make(map[string]metrics
.Description
)
22 samples
[i
].Name
= all
[i
].Name
23 descs
[all
[i
].Name
] = all
[i
]
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) {
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
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
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.
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
)
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
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.
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
))
141 nonTinyAllocs
:= uint64(0)
142 for _
, c
:= range allocsBySize
.Counts
{
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.
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 {
172 alloc
, free
*metrics
.Float64Histogram
174 allocdBytes
, freedBytes
uint64
175 total
, totalBytes
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
)
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.
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()
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")
251 for i
:= range objects
.alloc
.Buckets
{
252 ba
:= objects
.alloc
.Buckets
[i
]
253 bf
:= objects
.free
.Buckets
[i
]
255 t
.Errorf("bucket %d is different for alloc and free hists: %f != %f", i
, ba
, bf
)
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
)
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.
296 for i
:= 0; i
< b
.N
; i
++ {
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.
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")