github.com/lzhfromustc/gofuzz@v0.0.0-20211116160056-151b3108bbd1/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  }