mirror of
https://github.com/golang/go
synced 2024-10-14 03:43:28 +00:00
runtime/metrics: add CPU stats
This changes adds a breakdown for estimated CPU usage by time. These estimates are not based on real on-CPU counters, so each metric has a disclaimer explaining so. They can, however, be more reasonably compared to a total CPU time metric that this change also adds. Fixes #47216. Change-Id: I125006526be9f8e0d609200e193da5a78d9935be Reviewed-on: https://go-review.googlesource.com/c/go/+/404307 Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Josh MacDonald <jmacd@lightstep.com> Auto-Submit: Michael Knyszek <mknyszek@google.com> Reviewed-by: David Chase <drchase@google.com> Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
87eda2a782
commit
b7c28f484d
|
@ -90,6 +90,83 @@ func initMetrics() {
|
||||||
out.scalar = uint64(NumCgoCall())
|
out.scalar = uint64(NumCgoCall())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"/cpu/classes/gc/mark/assist:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.gcAssistTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/gc/mark/dedicated:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.gcDedicatedTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/gc/mark/idle:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.gcIdleTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/gc/pause:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.gcPauseTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/gc/total:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.gcTotalTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/idle:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.idleTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/scavenge/assist:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.scavengeAssistTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/scavenge/background:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.scavengeBgTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/scavenge/total:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.scavengeTotalTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/total:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.totalTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/cpu/classes/user:cpu-seconds": {
|
||||||
|
deps: makeStatDepSet(cpuStatsDep),
|
||||||
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
out.kind = metricKindFloat64
|
||||||
|
out.scalar = float64bits(nsToSec(in.cpuStats.userTime))
|
||||||
|
},
|
||||||
|
},
|
||||||
"/gc/cycles/automatic:gc-cycles": {
|
"/gc/cycles/automatic:gc-cycles": {
|
||||||
deps: makeStatDepSet(sysStatsDep),
|
deps: makeStatDepSet(sysStatsDep),
|
||||||
compute: func(in *statAggregate, out *metricValue) {
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
@ -345,6 +422,7 @@ type statDep uint
|
||||||
const (
|
const (
|
||||||
heapStatsDep statDep = iota // corresponds to heapStatsAggregate
|
heapStatsDep statDep = iota // corresponds to heapStatsAggregate
|
||||||
sysStatsDep // corresponds to sysStatsAggregate
|
sysStatsDep // corresponds to sysStatsAggregate
|
||||||
|
cpuStatsDep // corresponds to cpuStatsAggregate
|
||||||
numStatsDeps
|
numStatsDeps
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -490,6 +568,23 @@ func (a *sysStatsAggregate) compute() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cpuStatsAggregate represents CPU stats obtained from the runtime
|
||||||
|
// acquired together to avoid skew and inconsistencies.
|
||||||
|
type cpuStatsAggregate struct {
|
||||||
|
cpuStats
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute populates the cpuStatsAggregate with values from the runtime.
|
||||||
|
func (a *cpuStatsAggregate) compute() {
|
||||||
|
a.cpuStats = work.cpuStats
|
||||||
|
}
|
||||||
|
|
||||||
|
// nsToSec takes a duration in nanoseconds and converts it to seconds as
|
||||||
|
// a float64.
|
||||||
|
func nsToSec(ns int64) float64 {
|
||||||
|
return float64(ns) / 1e9
|
||||||
|
}
|
||||||
|
|
||||||
// statAggregate is the main driver of the metrics implementation.
|
// statAggregate is the main driver of the metrics implementation.
|
||||||
//
|
//
|
||||||
// It contains multiple aggregates of runtime statistics, as well
|
// It contains multiple aggregates of runtime statistics, as well
|
||||||
|
@ -499,6 +594,7 @@ type statAggregate struct {
|
||||||
ensured statDepSet
|
ensured statDepSet
|
||||||
heapStats heapStatsAggregate
|
heapStats heapStatsAggregate
|
||||||
sysStats sysStatsAggregate
|
sysStats sysStatsAggregate
|
||||||
|
cpuStats cpuStatsAggregate
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure populates statistics aggregates determined by deps if they
|
// ensure populates statistics aggregates determined by deps if they
|
||||||
|
@ -517,6 +613,8 @@ func (a *statAggregate) ensure(deps *statDepSet) {
|
||||||
a.heapStats.compute()
|
a.heapStats.compute()
|
||||||
case sysStatsDep:
|
case sysStatsDep:
|
||||||
a.sysStats.compute()
|
a.sysStats.compute()
|
||||||
|
case cpuStatsDep:
|
||||||
|
a.cpuStats.compute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.ensured = a.ensured.union(missing)
|
a.ensured = a.ensured.union(missing)
|
||||||
|
|
|
@ -57,6 +57,122 @@ var allDesc = []Description{
|
||||||
Kind: KindUint64,
|
Kind: KindUint64,
|
||||||
Cumulative: true,
|
Cumulative: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/gc/mark/assist:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time goroutines spent performing GC tasks " +
|
||||||
|
"to assist the GC and prevent it from falling behind the application. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/gc/mark/dedicated:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent performing GC tasks on " +
|
||||||
|
"processors (as defined by GOMAXPROCS) dedicated to those tasks. " +
|
||||||
|
"This includes time spent with the world stopped due to the GC. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/gc/mark/idle:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent performing GC tasks on " +
|
||||||
|
"spare CPU resources that the Go scheduler could not otherwise find " +
|
||||||
|
"a use for. This should be subtracted from the total GC CPU time to " +
|
||||||
|
"obtain a measure of compulsory GC CPU time. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/gc/pause:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent with the application paused by " +
|
||||||
|
"the GC. Even if only one thread is running during the pause, this is " +
|
||||||
|
"computed as GOMAXPROCS times the pause latency because nothing else " +
|
||||||
|
"can be executing. This is the exact sum of samples in /gc/pause:seconds " +
|
||||||
|
"if each sample is multiplied by GOMAXPROCS at the time it is taken. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/gc/total:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent performing GC tasks. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics. Sum of all metrics in /cpu/classes/gc.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/idle:cpu-seconds",
|
||||||
|
Description: "Estimated total available CPU time not spent executing any Go or Go runtime code. " +
|
||||||
|
"In other words, the part of /cpu/classes/total:cpu-seconds that was unused. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/scavenge/assist:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent returning unused memory to the " +
|
||||||
|
"underlying platform in response eagerly in response to memory pressure. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/scavenge/background:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent performing background tasks " +
|
||||||
|
"to return unused memory to the underlying platform. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/scavenge/total:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent performing tasks that return " +
|
||||||
|
"unused memory to the underlying platform. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics. Sum of all metrics in /cpu/classes/scavenge.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/total:cpu-seconds",
|
||||||
|
Description: "Estimated total available CPU time for user Go code " +
|
||||||
|
"or the Go runtime, as defined by GOMAXPROCS. In other words, GOMAXPROCS " +
|
||||||
|
"integrated over the wall-clock duration this process has been executing for. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics. Sum of all metrics in /cpu/classes.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "/cpu/classes/user:cpu-seconds",
|
||||||
|
Description: "Estimated total CPU time spent running user Go code. This may " +
|
||||||
|
"also include some small amount of time spent in the Go runtime. " +
|
||||||
|
"This metric is an overestimate, and not directly comparable to " +
|
||||||
|
"system CPU time measurements. Compare only with other /cpu/classes " +
|
||||||
|
"metrics.",
|
||||||
|
Kind: KindFloat64,
|
||||||
|
Cumulative: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "/gc/cycles/automatic:gc-cycles",
|
Name: "/gc/cycles/automatic:gc-cycles",
|
||||||
Description: "Count of completed GC cycles generated by the Go runtime.",
|
Description: "Count of completed GC cycles generated by the Go runtime.",
|
||||||
|
|
|
@ -54,6 +54,90 @@ Below is the full list of supported metrics, ordered lexicographically.
|
||||||
/cgo/go-to-c-calls:calls
|
/cgo/go-to-c-calls:calls
|
||||||
Count of calls made from Go to C by the current process.
|
Count of calls made from Go to C by the current process.
|
||||||
|
|
||||||
|
/cpu/classes/gc/mark/assist:cpu-seconds
|
||||||
|
Estimated total CPU time goroutines spent performing GC tasks
|
||||||
|
to assist the GC and prevent it from falling behind the application.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
/cpu/classes/gc/mark/dedicated:cpu-seconds
|
||||||
|
Estimated total CPU time spent performing GC tasks on
|
||||||
|
processors (as defined by GOMAXPROCS) dedicated to those tasks.
|
||||||
|
This includes time spent with the world stopped due to the GC.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
/cpu/classes/gc/mark/idle:cpu-seconds
|
||||||
|
Estimated total CPU time spent performing GC tasks on
|
||||||
|
spare CPU resources that the Go scheduler could not otherwise find
|
||||||
|
a use for. This should be subtracted from the total GC CPU time to
|
||||||
|
obtain a measure of compulsory GC CPU time.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
/cpu/classes/gc/pause:cpu-seconds
|
||||||
|
Estimated total CPU time spent with the application paused by
|
||||||
|
the GC. Even if only one thread is running during the pause, this is
|
||||||
|
computed as GOMAXPROCS times the pause latency because nothing else
|
||||||
|
can be executing. This is the exact sum of samples in /gc/pause:seconds
|
||||||
|
if each sample is multiplied by GOMAXPROCS at the time it is taken.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
/cpu/classes/gc/total:cpu-seconds
|
||||||
|
Estimated total CPU time spent performing GC tasks.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics. Sum of all metrics in /cpu/classes/gc.
|
||||||
|
|
||||||
|
/cpu/classes/idle:cpu-seconds
|
||||||
|
Estimated total available CPU time not spent executing any Go or Go
|
||||||
|
runtime code. In other words, the part of /cpu/classes/total:cpu-seconds
|
||||||
|
that was unused.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
/cpu/classes/scavenge/assist:cpu-seconds
|
||||||
|
Estimated total CPU time spent returning unused memory to the
|
||||||
|
underlying platform in response eagerly in response to memory pressure.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
/cpu/classes/scavenge/background:cpu-seconds
|
||||||
|
Estimated total CPU time spent performing background tasks
|
||||||
|
to return unused memory to the underlying platform.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
/cpu/classes/scavenge/total:cpu-seconds
|
||||||
|
Estimated total CPU time spent performing tasks that return
|
||||||
|
unused memory to the underlying platform.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics. Sum of all metrics in /cpu/classes/scavenge.
|
||||||
|
|
||||||
|
/cpu/classes/total:cpu-seconds
|
||||||
|
Estimated total available CPU time for user Go code or the Go runtime, as
|
||||||
|
defined by GOMAXPROCS. In other words, GOMAXPROCS integrated over the
|
||||||
|
wall-clock duration this process has been executing for.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics. Sum of all metrics in /cpu/classes.
|
||||||
|
|
||||||
|
/cpu/classes/user:cpu-seconds
|
||||||
|
Estimated total CPU time spent running user Go code. This may
|
||||||
|
also include some small amount of time spent in the Go runtime.
|
||||||
|
This metric is an overestimate, and not directly comparable to
|
||||||
|
system CPU time measurements. Compare only with other /cpu/classes
|
||||||
|
metrics.
|
||||||
|
|
||||||
/gc/cycles/automatic:gc-cycles
|
/gc/cycles/automatic:gc-cycles
|
||||||
Count of completed GC cycles generated by the Go runtime.
|
Count of completed GC cycles generated by the Go runtime.
|
||||||
|
|
||||||
|
|
|
@ -163,6 +163,12 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
||||||
|
// Set GOMAXPROCS high then sleep briefly to ensure we generate
|
||||||
|
// some idle time.
|
||||||
|
oldmaxprocs := runtime.GOMAXPROCS(10)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
runtime.GOMAXPROCS(oldmaxprocs)
|
||||||
|
|
||||||
// Read all the supported metrics through the metrics package.
|
// Read all the supported metrics through the metrics package.
|
||||||
descs, samples := prepareAllMetricsSamples()
|
descs, samples := prepareAllMetricsSamples()
|
||||||
metrics.Read(samples)
|
metrics.Read(samples)
|
||||||
|
@ -181,6 +187,22 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
numGC uint64
|
numGC uint64
|
||||||
pauses uint64
|
pauses uint64
|
||||||
}
|
}
|
||||||
|
var cpu struct {
|
||||||
|
gcAssist float64
|
||||||
|
gcDedicated float64
|
||||||
|
gcIdle float64
|
||||||
|
gcPause float64
|
||||||
|
gcTotal float64
|
||||||
|
|
||||||
|
idle float64
|
||||||
|
user float64
|
||||||
|
|
||||||
|
scavengeAssist float64
|
||||||
|
scavengeBg float64
|
||||||
|
scavengeTotal float64
|
||||||
|
|
||||||
|
total float64
|
||||||
|
}
|
||||||
for i := range samples {
|
for i := range samples {
|
||||||
kind := samples[i].Value.Kind()
|
kind := samples[i].Value.Kind()
|
||||||
if want := descs[samples[i].Name].Kind; kind != want {
|
if want := descs[samples[i].Name].Kind; kind != want {
|
||||||
|
@ -199,6 +221,28 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch samples[i].Name {
|
switch samples[i].Name {
|
||||||
|
case "/cpu/classes/gc/mark/assist:cpu-seconds":
|
||||||
|
cpu.gcAssist = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/gc/mark/dedicated:cpu-seconds":
|
||||||
|
cpu.gcDedicated = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/gc/mark/idle:cpu-seconds":
|
||||||
|
cpu.gcIdle = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/gc/pause:cpu-seconds":
|
||||||
|
cpu.gcPause = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/gc/total:cpu-seconds":
|
||||||
|
cpu.gcTotal = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/idle:cpu-seconds":
|
||||||
|
cpu.idle = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/scavenge/assist:cpu-seconds":
|
||||||
|
cpu.scavengeAssist = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/scavenge/background:cpu-seconds":
|
||||||
|
cpu.scavengeBg = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/scavenge/total:cpu-seconds":
|
||||||
|
cpu.scavengeTotal = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/total:cpu-seconds":
|
||||||
|
cpu.total = samples[i].Value.Float64()
|
||||||
|
case "/cpu/classes/user:cpu-seconds":
|
||||||
|
cpu.user = samples[i].Value.Float64()
|
||||||
case "/memory/classes/total:bytes":
|
case "/memory/classes/total:bytes":
|
||||||
totalVirtual.got = samples[i].Value.Uint64()
|
totalVirtual.got = samples[i].Value.Uint64()
|
||||||
case "/memory/classes/heap/objects:bytes":
|
case "/memory/classes/heap/objects:bytes":
|
||||||
|
@ -235,6 +279,33 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Only check this on Linux where we can be reasonably sure we have a high-resolution timer.
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
if cpu.gcDedicated <= 0 && cpu.gcAssist <= 0 && cpu.gcIdle <= 0 {
|
||||||
|
t.Errorf("found no time spent on GC work: %#v", cpu)
|
||||||
|
}
|
||||||
|
if cpu.gcPause <= 0 {
|
||||||
|
t.Errorf("found no GC pauses: %f", cpu.gcPause)
|
||||||
|
}
|
||||||
|
if cpu.idle <= 0 {
|
||||||
|
t.Errorf("found no idle time: %f", cpu.idle)
|
||||||
|
}
|
||||||
|
if total := cpu.gcDedicated + cpu.gcAssist + cpu.gcIdle + cpu.gcPause; !withinEpsilon(cpu.gcTotal, total, 0.01) {
|
||||||
|
t.Errorf("calculated total GC CPU not within 1%% of sampled total: %f vs. %f", total, cpu.gcTotal)
|
||||||
|
}
|
||||||
|
if total := cpu.scavengeAssist + cpu.scavengeBg; !withinEpsilon(cpu.scavengeTotal, total, 0.01) {
|
||||||
|
t.Errorf("calculated total scavenge CPU not within 1%% of sampled total: %f vs. %f", total, cpu.scavengeTotal)
|
||||||
|
}
|
||||||
|
if cpu.total <= 0 {
|
||||||
|
t.Errorf("found no total CPU time passed")
|
||||||
|
}
|
||||||
|
if cpu.user <= 0 {
|
||||||
|
t.Errorf("found no user time passed")
|
||||||
|
}
|
||||||
|
if total := cpu.gcTotal + cpu.scavengeTotal + cpu.user + cpu.idle; !withinEpsilon(cpu.total, total, 0.02) {
|
||||||
|
t.Errorf("calculated total CPU not within 2%% of sampled total: %f vs. %f", total, cpu.total)
|
||||||
|
}
|
||||||
|
}
|
||||||
if totalVirtual.got != totalVirtual.want {
|
if totalVirtual.got != totalVirtual.want {
|
||||||
t.Errorf(`"/memory/classes/total:bytes" does not match sum of /memory/classes/**: got %d, want %d`, totalVirtual.got, totalVirtual.want)
|
t.Errorf(`"/memory/classes/total:bytes" does not match sum of /memory/classes/**: got %d, want %d`, totalVirtual.got, totalVirtual.want)
|
||||||
}
|
}
|
||||||
|
@ -411,3 +482,7 @@ func TestReadMetricsCumulative(t *testing.T) {
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withinEpsilon(v1, v2, e float64) bool {
|
||||||
|
return v2-v2*e <= v1 && v1 <= v2+v2*e
|
||||||
|
}
|
||||||
|
|
|
@ -366,10 +366,6 @@ type workType struct {
|
||||||
// explicit user call.
|
// explicit user call.
|
||||||
userForced bool
|
userForced bool
|
||||||
|
|
||||||
// totaltime is the CPU nanoseconds spent in GC since the
|
|
||||||
// program started if debug.gctrace > 0.
|
|
||||||
totaltime int64
|
|
||||||
|
|
||||||
// initialHeapLive is the value of gcController.heapLive at the
|
// initialHeapLive is the value of gcController.heapLive at the
|
||||||
// beginning of this GC cycle.
|
// beginning of this GC cycle.
|
||||||
initialHeapLive uint64
|
initialHeapLive uint64
|
||||||
|
@ -404,6 +400,9 @@ type workType struct {
|
||||||
|
|
||||||
// debug.gctrace heap sizes for this cycle.
|
// debug.gctrace heap sizes for this cycle.
|
||||||
heap0, heap1, heap2 uint64
|
heap0, heap1, heap2 uint64
|
||||||
|
|
||||||
|
// Cumulative estimated CPU usage.
|
||||||
|
cpuStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// GC runs a garbage collection and blocks the caller until the
|
// GC runs a garbage collection and blocks the caller until the
|
||||||
|
@ -1006,24 +1005,57 @@ func gcMarkTermination() {
|
||||||
memstats.pause_end[memstats.numgc%uint32(len(memstats.pause_end))] = uint64(unixNow)
|
memstats.pause_end[memstats.numgc%uint32(len(memstats.pause_end))] = uint64(unixNow)
|
||||||
memstats.pause_total_ns += uint64(work.pauseNS)
|
memstats.pause_total_ns += uint64(work.pauseNS)
|
||||||
|
|
||||||
// Update work.totaltime.
|
|
||||||
sweepTermCpu := int64(work.stwprocs) * (work.tMark - work.tSweepTerm)
|
sweepTermCpu := int64(work.stwprocs) * (work.tMark - work.tSweepTerm)
|
||||||
// We report idle marking time below, but omit it from the
|
// We report idle marking time below, but omit it from the
|
||||||
// overall utilization here since it's "free".
|
// overall utilization here since it's "free".
|
||||||
markCpu := gcController.assistTime.Load() + gcController.dedicatedMarkTime.Load() + gcController.fractionalMarkTime.Load()
|
markAssistCpu := gcController.assistTime.Load()
|
||||||
|
markDedicatedCpu := gcController.dedicatedMarkTime.Load()
|
||||||
|
markFractionalCpu := gcController.fractionalMarkTime.Load()
|
||||||
|
markIdleCpu := gcController.idleMarkTime.Load()
|
||||||
markTermCpu := int64(work.stwprocs) * (work.tEnd - work.tMarkTerm)
|
markTermCpu := int64(work.stwprocs) * (work.tEnd - work.tMarkTerm)
|
||||||
cycleCpu := sweepTermCpu + markCpu + markTermCpu
|
scavAssistCpu := scavenge.assistTime.Load()
|
||||||
work.totaltime += cycleCpu
|
scavBgCpu := scavenge.backgroundTime.Load()
|
||||||
|
|
||||||
|
// Update cumulative GC CPU stats.
|
||||||
|
work.cpuStats.gcAssistTime += markAssistCpu
|
||||||
|
work.cpuStats.gcDedicatedTime += markDedicatedCpu + markFractionalCpu
|
||||||
|
work.cpuStats.gcIdleTime += markIdleCpu
|
||||||
|
work.cpuStats.gcPauseTime += sweepTermCpu + markTermCpu
|
||||||
|
work.cpuStats.gcTotalTime += sweepTermCpu + markAssistCpu + markDedicatedCpu + markFractionalCpu + markIdleCpu + markTermCpu
|
||||||
|
|
||||||
|
// Update cumulative scavenge CPU stats.
|
||||||
|
work.cpuStats.scavengeAssistTime += scavAssistCpu
|
||||||
|
work.cpuStats.scavengeBgTime += scavBgCpu
|
||||||
|
work.cpuStats.scavengeTotalTime += scavAssistCpu + scavBgCpu
|
||||||
|
|
||||||
|
// Update total CPU.
|
||||||
|
work.cpuStats.totalTime = sched.totaltime + (now-sched.procresizetime)*int64(gomaxprocs)
|
||||||
|
work.cpuStats.idleTime += sched.idleTime.Load()
|
||||||
|
|
||||||
|
// Compute userTime. We compute this indirectly as everything that's not the above.
|
||||||
|
//
|
||||||
|
// Since time spent in _Pgcstop is covered by gcPauseTime, and time spent in _Pidle
|
||||||
|
// is covered by idleTime, what we're left with is time spent in _Prunning and _Psyscall,
|
||||||
|
// the latter of which is fine because the P will either go idle or get used for something
|
||||||
|
// else via sysmon. Meanwhile if we subtract GC time from whatever's left, we get non-GC
|
||||||
|
// _Prunning time. Note that this still leaves time spent in sweeping and in the scheduler,
|
||||||
|
// but that's fine. The overwhelming majority of this time will be actual user time.
|
||||||
|
work.cpuStats.userTime = work.cpuStats.totalTime - (work.cpuStats.gcTotalTime +
|
||||||
|
work.cpuStats.scavengeTotalTime + work.cpuStats.idleTime)
|
||||||
|
|
||||||
// Compute overall GC CPU utilization.
|
// Compute overall GC CPU utilization.
|
||||||
totalCpu := sched.totaltime + (now-sched.procresizetime)*int64(gomaxprocs)
|
// Omit idle marking time from the overall utilization here since it's "free".
|
||||||
memstats.gc_cpu_fraction = float64(work.totaltime) / float64(totalCpu)
|
memstats.gc_cpu_fraction = float64(work.cpuStats.gcTotalTime-work.cpuStats.gcIdleTime) / float64(work.cpuStats.totalTime)
|
||||||
|
|
||||||
// Reset assist time stat.
|
// Reset assist time and background time stats.
|
||||||
//
|
//
|
||||||
// Do this now, instead of at the start of the next GC cycle, because
|
// Do this now, instead of at the start of the next GC cycle, because
|
||||||
// these two may keep accumulating even if the GC is not active.
|
// these two may keep accumulating even if the GC is not active.
|
||||||
mheap_.pages.scav.assistTime.Store(0)
|
scavenge.assistTime.Store(0)
|
||||||
|
scavenge.backgroundTime.Store(0)
|
||||||
|
|
||||||
|
// Reset idle time stat.
|
||||||
|
sched.idleTime.Store(0)
|
||||||
|
|
||||||
// Reset sweep state.
|
// Reset sweep state.
|
||||||
sweep.nbgsweep = 0
|
sweep.nbgsweep = 0
|
||||||
|
|
|
@ -469,9 +469,10 @@ func (e *limiterEvent) stop(typ limiterEventType, now int64) {
|
||||||
// Account for the event.
|
// Account for the event.
|
||||||
switch typ {
|
switch typ {
|
||||||
case limiterEventIdleMarkWork:
|
case limiterEventIdleMarkWork:
|
||||||
fallthrough
|
gcCPULimiter.addIdleTime(duration)
|
||||||
case limiterEventIdle:
|
case limiterEventIdle:
|
||||||
gcCPULimiter.addIdleTime(duration)
|
gcCPULimiter.addIdleTime(duration)
|
||||||
|
sched.idleTime.Add(duration)
|
||||||
case limiterEventMarkAssist:
|
case limiterEventMarkAssist:
|
||||||
fallthrough
|
fallthrough
|
||||||
case limiterEventScavengeAssist:
|
case limiterEventScavengeAssist:
|
||||||
|
|
|
@ -221,6 +221,16 @@ var scavenge struct {
|
||||||
// gcController.memoryLimit by choosing to target the memory limit or
|
// gcController.memoryLimit by choosing to target the memory limit or
|
||||||
// some lower target to keep the scavenger working.
|
// some lower target to keep the scavenger working.
|
||||||
memoryLimitGoal atomic.Uint64
|
memoryLimitGoal atomic.Uint64
|
||||||
|
|
||||||
|
// assistTime is the time spent by the allocator scavenging in the last GC cycle.
|
||||||
|
//
|
||||||
|
// This is reset once a GC cycle ends.
|
||||||
|
assistTime atomic.Int64
|
||||||
|
|
||||||
|
// backgroundTime is the time spent by the background scavenger in the last GC cycle.
|
||||||
|
//
|
||||||
|
// This is reset once a GC cycle ends.
|
||||||
|
backgroundTime atomic.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -361,6 +371,7 @@ func (s *scavengerState) init() {
|
||||||
if start >= end {
|
if start >= end {
|
||||||
return r, 0
|
return r, 0
|
||||||
}
|
}
|
||||||
|
scavenge.backgroundTime.Add(end - start)
|
||||||
return r, end - start
|
return r, end - start
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1327,7 +1327,7 @@ HaveSpan:
|
||||||
if track {
|
if track {
|
||||||
pp.limiterEvent.stop(limiterEventScavengeAssist, now)
|
pp.limiterEvent.stop(limiterEventScavengeAssist, now)
|
||||||
}
|
}
|
||||||
h.pages.scav.assistTime.Add(now - start)
|
scavenge.assistTime.Add(now - start)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit and account for any scavenged memory that the span now owns.
|
// Commit and account for any scavenged memory that the span now owns.
|
||||||
|
|
|
@ -48,7 +48,6 @@
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime/internal/atomic"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -273,15 +272,10 @@ type pageAlloc struct {
|
||||||
// scavenge.
|
// scavenge.
|
||||||
index scavengeIndex
|
index scavengeIndex
|
||||||
|
|
||||||
// released is the amount of memory released this generation.
|
// released is the amount of memory released this scavenge cycle.
|
||||||
//
|
//
|
||||||
// Updated atomically.
|
// Updated atomically.
|
||||||
released uintptr
|
released uintptr
|
||||||
|
|
||||||
// scavengeAssistTime is the time spent scavenging in the last GC cycle.
|
|
||||||
//
|
|
||||||
// This is reset once a GC cycle ends.
|
|
||||||
assistTime atomic.Int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mheap_.lock. This level of indirection makes it possible
|
// mheap_.lock. This level of indirection makes it possible
|
||||||
|
|
|
@ -879,3 +879,25 @@ func (m *consistentHeapStats) read(out *heapStatsDelta) {
|
||||||
|
|
||||||
releasem(mp)
|
releasem(mp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cpuStats struct {
|
||||||
|
// All fields are CPU time in nanoseconds computed by comparing
|
||||||
|
// calls of nanotime. This means they're all overestimates, because
|
||||||
|
// they don't accurately compute on-CPU time (so some of the time
|
||||||
|
// could be spent scheduled away by the OS).
|
||||||
|
|
||||||
|
gcAssistTime int64 // GC assists
|
||||||
|
gcDedicatedTime int64 // GC dedicated mark workers + pauses
|
||||||
|
gcIdleTime int64 // GC idle mark workers
|
||||||
|
gcPauseTime int64 // GC pauses (all GOMAXPROCS, even if just 1 is running)
|
||||||
|
gcTotalTime int64
|
||||||
|
|
||||||
|
scavengeAssistTime int64 // background scavenger
|
||||||
|
scavengeBgTime int64 // scavenge assists
|
||||||
|
scavengeTotalTime int64
|
||||||
|
|
||||||
|
idleTime int64 // Time Ps spent in _Pidle.
|
||||||
|
userTime int64 // Time Ps spent in _Prunning or _Psyscall that's not any of the above.
|
||||||
|
|
||||||
|
totalTime int64 // GOMAXPROCS * (monotonic wall clock time elapsed)
|
||||||
|
}
|
||||||
|
|
|
@ -838,6 +838,11 @@ type schedt struct {
|
||||||
// as the sum of time a G spends in the _Grunnable state before
|
// as the sum of time a G spends in the _Grunnable state before
|
||||||
// it transitions to _Grunning.
|
// it transitions to _Grunning.
|
||||||
timeToRun timeHistogram
|
timeToRun timeHistogram
|
||||||
|
|
||||||
|
// idleTime is the total CPU time Ps have "spent" idle.
|
||||||
|
//
|
||||||
|
// Reset on each GC cycle.
|
||||||
|
idleTime atomic.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values for the flags field of a sigTabT.
|
// Values for the flags field of a sigTabT.
|
||||||
|
|
Loading…
Reference in a new issue