Skip to content

[tailscale] runtime: add TailscaleReadStackStats for per-goroutine stack info - #185

Merged
bradfitz merged 1 commit into
tailscale.go1.27from
bradfitz/stackstats
Sep 11, 2026
Merged

bradfitz merged 1 commit into
tailscale.go1.27from
bradfitz/stackstats

Conversation

@bradfitz

Copy link
Copy Markdown
Member

We're working on reducing the standing memory of servers holding
millions of idle long-polling connections. A big part of that is
knowing which stack size class those goroutines' stacks are in, how
much of the stack they actually use while parked, and whether the
stacks grew during connection setup and were later shrunk back by the
GC. The runtime doesn't expose any of that.

Add runtime.TailscaleReadStackStats, modeled on ReadMemStats, which
fills in a TailscaleStackStats for the calling goroutine: the current
stack size (its size class), the bytes in use at the point of the
call, the largest size the stack has ever been, and saturating counts
of growths and shrinks.

The runtime doesn't track a true high-water mark of stack usage, and
doing so would cost work on every call, so MaxSize (the peak allocated
size) is the proxy: a stack only doubles when the previous size
overflowed, so peak usage was at least roughly MaxSize/2. Comparing it
against Size shows whether the GC has already shrunk a goroutine's
stack back down.

The three backing fields on g are uint8s placed in what was alignment
padding before g.sig, so g does not grow. They're reset in newproc1
because gs are recycled through the free list. Growths are recorded
in newstack and shrinks in shrinkstack. The call itself takes no locks
and costs about 7ns.

Updates #184

…ack info

We're working on reducing the standing memory of servers holding
millions of idle long-polling connections. A big part of that is
knowing which stack size class those goroutines' stacks are in, how
much of the stack they actually use while parked, and whether the
stacks grew during connection setup and were later shrunk back by the
GC. The runtime doesn't expose any of that.

Add runtime.TailscaleReadStackStats, modeled on ReadMemStats, which
fills in a TailscaleStackStats for the calling goroutine: the current
stack size (its size class), the bytes in use at the point of the
call, the largest size the stack has ever been, and saturating counts
of growths and shrinks.

The runtime doesn't track a true high-water mark of stack usage, and
doing so would cost work on every call, so MaxSize (the peak allocated
size) is the proxy: a stack only doubles when the previous size
overflowed, so peak usage was at least roughly MaxSize/2. Comparing it
against Size shows whether the GC has already shrunk a goroutine's
stack back down.

The three backing fields on g are uint8s placed in what was alignment
padding before g.sig, so g does not grow. They're reset in newproc1
because gs are recycled through the free list. Growths are recorded
in newstack and shrinks in shrinkstack. The call itself takes no locks
and costs about 7ns.

Updates #184

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I7c2e9b41f6d8a05e3b9f1c4d2a8e6f7b0c1d3e5a

@tomhjp tomhjp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tests/benchmark! LGTM

@bradfitz
bradfitz merged commit 7b6f155 into tailscale.go1.27 Sep 11, 2026
5 checks passed
@bradfitz
bradfitz deleted the bradfitz/stackstats branch September 11, 2026 16:53
bradfitz added a commit to tailscale/tailscale that referenced this pull request Sep 11, 2026
Updates tailscale/go#185
Updates tailscale/corp#29053
Updates #21064

Change-Id: Ic127c83b3ed7b472a23f62ec1a67f08b0de31556
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants