Skip to content

Commit 6ef98ac

Browse files
prattmicgopherbot
authored andcommitted
runtime/metrics: add STW stopping and total time metrics
This CL adds four new time histogram metrics: /sched/pauses/stopping/gc:seconds /sched/pauses/stopping/other:seconds /sched/pauses/total/gc:seconds /sched/pauses/total/other:seconds The "stopping" metrics measure the time taken to start a stop-the-world pause. i.e., how long it takes stopTheWorldWithSema to stop all Ps. This can be used to detect STW struggling to preempt Ps. The "total" metrics measure the total duration of a stop-the-world pause, from starting to stop-the-world until the world is started again. This includes the time spent in the "start" phase. The "gc" metrics are used for GC-related STW pauses. The "other" metrics are used for all other STW pauses. All of these metrics start timing in stopTheWorldWithSema only after successfully acquiring sched.lock, thus excluding lock contention on sched.lock. The reasoning behind this is that while waiting on sched.lock the world is not stopped at all (all other Ps can run), so the impact of this contention is primarily limited to the goroutine attempting to stop-the-world. Additionally, we already have some visibility into sched.lock contention via contention profiles (#57071). /sched/pauses/total/gc:seconds is conceptually equivalent to /gc/pauses:seconds, so the latter is marked as deprecated and returns the same histogram as the former. In the implementation, there are a few minor differences: * For both mark and sweep termination stops, /gc/pauses:seconds started timing prior to calling startTheWorldWithSema, thus including lock contention. These details are minor enough, that I do not believe the slight change in reporting will matter. For mark termination stops, moving timing stop into startTheWorldWithSema does have the side effect of requiring moving other GC metric calculations outside of the STW, as they depend on the same end time. Fixes #63340 Change-Id: Iacd0bab11bedab85d3dcfb982361413a7d9c0d05 Reviewed-on: https://go-review.googlesource.com/c/go/+/534161 Reviewed-by: Michael Knyszek <[email protected]> Auto-Submit: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent a0df238 commit 6ef98ac

17 files changed

+425
-100
lines changed

src/runtime/debug.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ func GOMAXPROCS(n int) int {
2525
return ret
2626
}
2727

28-
stopTheWorldGC(stwGOMAXPROCS)
28+
stw := stopTheWorldGC(stwGOMAXPROCS)
2929

3030
// newprocs will be processed by startTheWorld
3131
newprocs = int32(n)
3232

33-
startTheWorldGC()
33+
startTheWorldGC(stw)
3434
return ret
3535
}
3636

src/runtime/export_debuglog_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ func DumpDebugLog() string {
3535
}
3636

3737
func ResetDebugLog() {
38-
stopTheWorld(stwForTestResetDebugLog)
38+
stw := stopTheWorld(stwForTestResetDebugLog)
3939
for l := allDloggers; l != nil; l = l.allLink {
4040
l.w.write = 0
4141
l.w.tick, l.w.nano = 0, 0
4242
l.w.r.begin, l.w.r.end = 0, 0
4343
l.w.r.tick, l.w.r.nano = 0, 0
4444
}
45-
startTheWorld()
45+
startTheWorld(stw)
4646
}

src/runtime/export_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ var ReadUnaligned32 = readUnaligned32
383383
var ReadUnaligned64 = readUnaligned64
384384

385385
func CountPagesInUse() (pagesInUse, counted uintptr) {
386-
stopTheWorld(stwForTestCountPagesInUse)
386+
stw := stopTheWorld(stwForTestCountPagesInUse)
387387

388388
pagesInUse = mheap_.pagesInUse.Load()
389389

@@ -393,7 +393,7 @@ func CountPagesInUse() (pagesInUse, counted uintptr) {
393393
}
394394
}
395395

396-
startTheWorld()
396+
startTheWorld(stw)
397397

398398
return
399399
}
@@ -426,7 +426,7 @@ func (p *ProfBuf) Close() {
426426
}
427427

428428
func ReadMetricsSlow(memStats *MemStats, samplesp unsafe.Pointer, len, cap int) {
429-
stopTheWorld(stwForTestReadMetricsSlow)
429+
stw := stopTheWorld(stwForTestReadMetricsSlow)
430430

431431
// Initialize the metrics beforehand because this could
432432
// allocate and skew the stats.
@@ -461,13 +461,13 @@ func ReadMetricsSlow(memStats *MemStats, samplesp unsafe.Pointer, len, cap int)
461461
})
462462
metricsUnlock()
463463

464-
startTheWorld()
464+
startTheWorld(stw)
465465
}
466466

467467
// ReadMemStatsSlow returns both the runtime-computed MemStats and
468468
// MemStats accumulated by scanning the heap.
469469
func ReadMemStatsSlow() (base, slow MemStats) {
470-
stopTheWorld(stwForTestReadMemStatsSlow)
470+
stw := stopTheWorld(stwForTestReadMemStatsSlow)
471471

472472
// Run on the system stack to avoid stack growth allocation.
473473
systemstack(func() {
@@ -544,7 +544,7 @@ func ReadMemStatsSlow() (base, slow MemStats) {
544544
getg().m.mallocing--
545545
})
546546

547-
startTheWorld()
547+
startTheWorld(stw)
548548
return
549549
}
550550

@@ -1324,7 +1324,7 @@ func CheckScavengedBitsCleared(mismatches []BitsMismatch) (n int, ok bool) {
13241324
}
13251325

13261326
func PageCachePagesLeaked() (leaked uintptr) {
1327-
stopTheWorld(stwForTestPageCachePagesLeaked)
1327+
stw := stopTheWorld(stwForTestPageCachePagesLeaked)
13281328

13291329
// Walk over destroyed Ps and look for unflushed caches.
13301330
deadp := allp[len(allp):cap(allp)]
@@ -1336,7 +1336,7 @@ func PageCachePagesLeaked() (leaked uintptr) {
13361336
}
13371337
}
13381338

1339-
startTheWorld()
1339+
startTheWorld(stw)
13401340
return
13411341
}
13421342

src/runtime/heapdump.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020

2121
//go:linkname runtime_debug_WriteHeapDump runtime/debug.WriteHeapDump
2222
func runtime_debug_WriteHeapDump(fd uintptr) {
23-
stopTheWorld(stwWriteHeapDump)
23+
stw := stopTheWorld(stwWriteHeapDump)
2424

2525
// Keep m on this G's stack instead of the system stack.
2626
// Both readmemstats_m and writeheapdump_m have pretty large
@@ -37,7 +37,7 @@ func runtime_debug_WriteHeapDump(fd uintptr) {
3737
writeheapdump_m(fd, &m)
3838
})
3939

40-
startTheWorld()
40+
startTheWorld(stw)
4141
}
4242

4343
const (

src/runtime/histogram.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ func (h *timeHistogram) record(duration int64) {
134134
h.counts[bucket*timeHistNumSubBuckets+subBucket].Add(1)
135135
}
136136

137+
// write dumps the histogram to the passed metricValue as a float64 histogram.
138+
func (h *timeHistogram) write(out *metricValue) {
139+
hist := out.float64HistOrInit(timeHistBuckets)
140+
// The bottom-most bucket, containing negative values, is tracked
141+
// separately as underflow, so fill that in manually and then iterate
142+
// over the rest.
143+
hist.counts[0] = h.underflow.Load()
144+
for i := range h.counts {
145+
hist.counts[i+1] = h.counts[i].Load()
146+
}
147+
hist.counts[len(hist.counts)-1] = h.overflow.Load()
148+
}
149+
137150
const (
138151
fInf = 0x7FF0000000000000
139152
fNegInf = 0xFFF0000000000000

src/runtime/metrics.go

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -317,15 +317,8 @@ func initMetrics() {
317317
},
318318
"/gc/pauses:seconds": {
319319
compute: func(_ *statAggregate, out *metricValue) {
320-
hist := out.float64HistOrInit(timeHistBuckets)
321-
// The bottom-most bucket, containing negative values, is tracked
322-
// as a separately as underflow, so fill that in manually and then
323-
// iterate over the rest.
324-
hist.counts[0] = memstats.gcPauseDist.underflow.Load()
325-
for i := range memstats.gcPauseDist.counts {
326-
hist.counts[i+1] = memstats.gcPauseDist.counts[i].Load()
327-
}
328-
hist.counts[len(hist.counts)-1] = memstats.gcPauseDist.overflow.Load()
320+
// N.B. this is identical to /sched/pauses/total/gc:seconds.
321+
sched.stwTotalTimeGC.write(out)
329322
},
330323
},
331324
"/gc/stack/starting-size:bytes": {
@@ -451,12 +444,27 @@ func initMetrics() {
451444
},
452445
"/sched/latencies:seconds": {
453446
compute: func(_ *statAggregate, out *metricValue) {
454-
hist := out.float64HistOrInit(timeHistBuckets)
455-
hist.counts[0] = sched.timeToRun.underflow.Load()
456-
for i := range sched.timeToRun.counts {
457-
hist.counts[i+1] = sched.timeToRun.counts[i].Load()
458-
}
459-
hist.counts[len(hist.counts)-1] = sched.timeToRun.overflow.Load()
447+
sched.timeToRun.write(out)
448+
},
449+
},
450+
"/sched/pauses/stopping/gc:seconds": {
451+
compute: func(_ *statAggregate, out *metricValue) {
452+
sched.stwStoppingTimeGC.write(out)
453+
},
454+
},
455+
"/sched/pauses/stopping/other:seconds": {
456+
compute: func(_ *statAggregate, out *metricValue) {
457+
sched.stwStoppingTimeOther.write(out)
458+
},
459+
},
460+
"/sched/pauses/total/gc:seconds": {
461+
compute: func(_ *statAggregate, out *metricValue) {
462+
sched.stwTotalTimeGC.write(out)
463+
},
464+
},
465+
"/sched/pauses/total/other:seconds": {
466+
compute: func(_ *statAggregate, out *metricValue) {
467+
sched.stwTotalTimeOther.write(out)
460468
},
461469
},
462470
"/sync/mutex/wait/total:seconds": {

src/runtime/metrics/description.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ var allDesc = []Description{
9696
Description: "Estimated total CPU time spent with the application paused by " +
9797
"the GC. Even if only one thread is running during the pause, this is " +
9898
"computed as GOMAXPROCS times the pause latency because nothing else " +
99-
"can be executing. This is the exact sum of samples in /gc/pause:seconds " +
100-
"if each sample is multiplied by GOMAXPROCS at the time it is taken. " +
101-
"This metric is an overestimate, and not directly comparable to " +
102-
"system CPU time measurements. Compare only with other /cpu/classes " +
103-
"metrics.",
99+
"can be executing. This is the exact sum of samples in " +
100+
"/sched/pauses/total/gc:seconds if each sample is multiplied by " +
101+
"GOMAXPROCS at the time it is taken. This metric is an overestimate, " +
102+
"and not directly comparable to system CPU time measurements. Compare " +
103+
"only with other /cpu/classes metrics.",
104104
Kind: KindFloat64,
105105
Cumulative: true,
106106
},
@@ -289,7 +289,7 @@ var allDesc = []Description{
289289
},
290290
{
291291
Name: "/gc/pauses:seconds",
292-
Description: "Distribution of individual GC-related stop-the-world pause latencies. Bucket counts increase monotonically.",
292+
Description: "Deprecated. Prefer the identical /sched/pauses/total/gc:seconds.",
293293
Kind: KindFloat64Histogram,
294294
Cumulative: true,
295295
},
@@ -415,6 +415,30 @@ var allDesc = []Description{
415415
Kind: KindFloat64Histogram,
416416
Cumulative: true,
417417
},
418+
{
419+
Name: "/sched/pauses/stopping/gc:seconds",
420+
Description: "Distribution of individual GC-related stop-the-world stopping latencies. This is the time it takes from deciding to stop the world until all Ps are stopped. This is a subset of the total GC-related stop-the-world time (/sched/pauses/total/gc:seconds). During this time, some threads may be executing. Bucket counts increase monotonically.",
421+
Kind: KindFloat64Histogram,
422+
Cumulative: true,
423+
},
424+
{
425+
Name: "/sched/pauses/stopping/other:seconds",
426+
Description: "Distribution of individual non-GC-related stop-the-world stopping latencies. This is the time it takes from deciding to stop the world until all Ps are stopped. This is a subset of the total non-GC-related stop-the-world time (/sched/pauses/total/other:seconds). During this time, some threads may be executing. Bucket counts increase monotonically.",
427+
Kind: KindFloat64Histogram,
428+
Cumulative: true,
429+
},
430+
{
431+
Name: "/sched/pauses/total/gc:seconds",
432+
Description: "Distribution of individual GC-related stop-the-world pause latencies. This is the time from deciding to stop the world until the world is started again. Some of this time is spent getting all threads to stop (this is measured directly in /sched/pauses/stopping/gc:seconds), during which some threads may still be running. Bucket counts increase monotonically.",
433+
Kind: KindFloat64Histogram,
434+
Cumulative: true,
435+
},
436+
{
437+
Name: "/sched/pauses/total/other:seconds",
438+
Description: "Distribution of individual non-GC-related stop-the-world pause latencies. This is the time from deciding to stop the world until the world is started again. Some of this time is spent getting all threads to stop (measured directly in /sched/pauses/stopping/other:seconds). Bucket counts increase monotonically.",
439+
Kind: KindFloat64Histogram,
440+
Cumulative: true,
441+
},
418442
{
419443
Name: "/sync/mutex/wait/total:seconds",
420444
Description: "Approximate cumulative time goroutines have spent blocked on a sync.Mutex or sync.RWMutex. This metric is useful for identifying global changes in lock contention. Collect a mutex or block profile using the runtime/pprof package for more detailed contention data.",

src/runtime/metrics/doc.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ Below is the full list of supported metrics, ordered lexicographically.
8383
the GC. Even if only one thread is running during the pause,
8484
this is computed as GOMAXPROCS times the pause latency because
8585
nothing else can be executing. This is the exact sum of samples
86-
in /gc/pause:seconds if each sample is multiplied by GOMAXPROCS
87-
at the time it is taken. This metric is an overestimate,
88-
and not directly comparable to system CPU time measurements.
89-
Compare only with other /cpu/classes metrics.
86+
in /sched/pauses/total/gc:seconds if each sample is multiplied
87+
by GOMAXPROCS at the time it is taken. This metric is an
88+
overestimate, and not directly comparable to system CPU time
89+
measurements. Compare only with other /cpu/classes metrics.
9090
9191
/cpu/classes/gc/total:cpu-seconds
9292
Estimated total CPU time spent performing GC tasks. This metric
@@ -211,8 +211,7 @@ Below is the full list of supported metrics, ordered lexicographically.
211211
1, so a value of 0 indicates that it was never enabled.
212212
213213
/gc/pauses:seconds
214-
Distribution of individual GC-related stop-the-world pause
215-
latencies. Bucket counts increase monotonically.
214+
Deprecated. Prefer the identical /sched/pauses/total/gc:seconds.
216215
217216
/gc/scan/globals:bytes
218217
The total amount of global variable space that is scannable.
@@ -411,6 +410,38 @@ Below is the full list of supported metrics, ordered lexicographically.
411410
in a runnable state before actually running. Bucket counts
412411
increase monotonically.
413412
413+
/sched/pauses/stopping/gc:seconds
414+
Distribution of individual GC-related stop-the-world stopping
415+
latencies. This is the time it takes from deciding to stop the
416+
world until all Ps are stopped. This is a subset of the total
417+
GC-related stop-the-world time (/sched/pauses/total/gc:seconds).
418+
During this time, some threads may be executing. Bucket counts
419+
increase monotonically.
420+
421+
/sched/pauses/stopping/other:seconds
422+
Distribution of individual non-GC-related stop-the-world
423+
stopping latencies. This is the time it takes from deciding
424+
to stop the world until all Ps are stopped. This is a
425+
subset of the total non-GC-related stop-the-world time
426+
(/sched/pauses/total/other:seconds). During this time, some
427+
threads may be executing. Bucket counts increase monotonically.
428+
429+
/sched/pauses/total/gc:seconds
430+
Distribution of individual GC-related stop-the-world pause
431+
latencies. This is the time from deciding to stop the world
432+
until the world is started again. Some of this time is spent
433+
getting all threads to stop (this is measured directly in
434+
/sched/pauses/stopping/gc:seconds), during which some threads
435+
may still be running. Bucket counts increase monotonically.
436+
437+
/sched/pauses/total/other:seconds
438+
Distribution of individual non-GC-related stop-the-world
439+
pause latencies. This is the time from deciding to stop the
440+
world until the world is started again. Some of this time
441+
is spent getting all threads to stop (measured directly in
442+
/sched/pauses/stopping/other:seconds). Bucket counts increase
443+
monotonically.
444+
414445
/sync/mutex/wait/total:seconds
415446
Approximate cumulative time goroutines have spent blocked
416447
on a sync.Mutex or sync.RWMutex. This metric is useful for

0 commit comments

Comments
 (0)