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())
|
||||
},
|
||||
},
|
||||
"/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": {
|
||||
deps: makeStatDepSet(sysStatsDep),
|
||||
compute: func(in *statAggregate, out *metricValue) {
|
||||
|
@ -345,6 +422,7 @@ type statDep uint
|
|||
const (
|
||||
heapStatsDep statDep = iota // corresponds to heapStatsAggregate
|
||||
sysStatsDep // corresponds to sysStatsAggregate
|
||||
cpuStatsDep // corresponds to cpuStatsAggregate
|
||||
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.
|
||||
//
|
||||
// It contains multiple aggregates of runtime statistics, as well
|
||||
|
@ -499,6 +594,7 @@ type statAggregate struct {
|
|||
ensured statDepSet
|
||||
heapStats heapStatsAggregate
|
||||
sysStats sysStatsAggregate
|
||||
cpuStats cpuStatsAggregate
|
||||
}
|
||||
|
||||
// ensure populates statistics aggregates determined by deps if they
|
||||
|
@ -517,6 +613,8 @@ func (a *statAggregate) ensure(deps *statDepSet) {
|
|||
a.heapStats.compute()
|
||||
case sysStatsDep:
|
||||
a.sysStats.compute()
|
||||
case cpuStatsDep:
|
||||
a.cpuStats.compute()
|
||||
}
|
||||
}
|
||||
a.ensured = a.ensured.union(missing)
|
||||
|
|
|
@ -57,6 +57,122 @@ var allDesc = []Description{
|
|||
Kind: KindUint64,
|
||||
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",
|
||||
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
|
||||
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
|
||||
Count of completed GC cycles generated by the Go runtime.
|
||||
|
||||
|
|
|
@ -163,6 +163,12 @@ func TestReadMetricsConsistency(t *testing.T) {
|
|||
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.
|
||||
descs, samples := prepareAllMetricsSamples()
|
||||
metrics.Read(samples)
|
||||
|
@ -181,6 +187,22 @@ func TestReadMetricsConsistency(t *testing.T) {
|
|||
numGC 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 {
|
||||
kind := samples[i].Value.Kind()
|
||||
if want := descs[samples[i].Name].Kind; kind != want {
|
||||
|
@ -199,6 +221,28 @@ func TestReadMetricsConsistency(t *testing.T) {
|
|||
}
|
||||
}
|
||||
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":
|
||||
totalVirtual.got = samples[i].Value.Uint64()
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
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.
|
||||
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
|
||||
// beginning of this GC cycle.
|
||||
initialHeapLive uint64
|
||||
|
@ -404,6 +400,9 @@ type workType struct {
|
|||
|
||||
// debug.gctrace heap sizes for this cycle.
|
||||
heap0, heap1, heap2 uint64
|
||||
|
||||
// Cumulative estimated CPU usage.
|
||||
cpuStats
|
||||
}
|
||||
|
||||
// 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_total_ns += uint64(work.pauseNS)
|
||||
|
||||
// Update work.totaltime.
|
||||
sweepTermCpu := int64(work.stwprocs) * (work.tMark - work.tSweepTerm)
|
||||
// We report idle marking time below, but omit it from the
|
||||
// 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)
|
||||
cycleCpu := sweepTermCpu + markCpu + markTermCpu
|
||||
work.totaltime += cycleCpu
|
||||
scavAssistCpu := scavenge.assistTime.Load()
|
||||
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.
|
||||
totalCpu := sched.totaltime + (now-sched.procresizetime)*int64(gomaxprocs)
|
||||
memstats.gc_cpu_fraction = float64(work.totaltime) / float64(totalCpu)
|
||||
// Omit idle marking time from the overall utilization here since it's "free".
|
||||
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
|
||||
// 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.
|
||||
sweep.nbgsweep = 0
|
||||
|
|
|
@ -469,9 +469,10 @@ func (e *limiterEvent) stop(typ limiterEventType, now int64) {
|
|||
// Account for the event.
|
||||
switch typ {
|
||||
case limiterEventIdleMarkWork:
|
||||
fallthrough
|
||||
gcCPULimiter.addIdleTime(duration)
|
||||
case limiterEventIdle:
|
||||
gcCPULimiter.addIdleTime(duration)
|
||||
sched.idleTime.Add(duration)
|
||||
case limiterEventMarkAssist:
|
||||
fallthrough
|
||||
case limiterEventScavengeAssist:
|
||||
|
|
|
@ -221,6 +221,16 @@ var scavenge struct {
|
|||
// gcController.memoryLimit by choosing to target the memory limit or
|
||||
// some lower target to keep the scavenger working.
|
||||
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 (
|
||||
|
@ -361,6 +371,7 @@ func (s *scavengerState) init() {
|
|||
if start >= end {
|
||||
return r, 0
|
||||
}
|
||||
scavenge.backgroundTime.Add(end - start)
|
||||
return r, end - start
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1327,7 +1327,7 @@ HaveSpan:
|
|||
if track {
|
||||
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.
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"runtime/internal/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
@ -273,15 +272,10 @@ type pageAlloc struct {
|
|||
// scavenge.
|
||||
index scavengeIndex
|
||||
|
||||
// released is the amount of memory released this generation.
|
||||
// released is the amount of memory released this scavenge cycle.
|
||||
//
|
||||
// Updated atomically.
|
||||
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
|
||||
|
|
|
@ -879,3 +879,25 @@ func (m *consistentHeapStats) read(out *heapStatsDelta) {
|
|||
|
||||
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
|
||||
// it transitions to _Grunning.
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue