tailscale/version/cmdname_test.go
Ubuntu 927ad0aef4 version: compute CmdName from in-memory build info, not on-disk scan
CmdName previously opened the running executable and streamed through
tens of MB searching for two 16-byte magic needles to find the Go
module info blob embedded by the linker, allocating a 64 KiB buffer on
every call. It was called at least twice during tailscaled startup
(logpolicy.LogsDir and logpolicy.Options.init on Windows) and once
more for tsweb's debug page title -- each call ~2.6 ms on a ~40 MB
tailscaled binary, and ~74 KB of allocator churn per call.

The same module info is exposed via runtime/debug.ReadBuildInfo, which
reads an already-resident string maintained by the Go runtime -- no
filesystem I/O. Use that, cached with sync.OnceValue so the lookup
happens at most once per process.

Benchmarks on Xeon 6975P-C (Linux amd64), against the version test
binary:

  BenchmarkCmdName before:  540,094 ns/op   66,136 B/op   8 allocs/op
  BenchmarkCmdName after:       2.5 ns/op        0 B/op   0 allocs/op

First-call (uncached) cost at tailscaled scale (71 deps in embedded
build info): 160 mallocs, ~11.5 KiB allocated, sub-microsecond. That
is smaller than the single 64 KiB scratch buffer the old code
allocated per call, and is now paid exactly once per process.

Stripped tailscaled binary size is unchanged: runtime/debug was
already imported transitively (including by sibling code in this same
package at version/version.go:121), so no new dependencies ship. The
hand-rolled byte scanner, 64 KiB scratch buffer, rsc.io/goversion-
derived magic constants, and the 32-second integration test that
built tailscaled to re-parse it are removed.

TestCmdNameNoAllocs asserts that, once primed, CmdName is 0 allocs
per call, guarding against regressions that reintroduce per-call
binary parsing. TestCmdNameFromBuildInfo verifies that CmdName is
pulling from the embedded build info rather than falling back to the
os.Executable basename.
2026-04-22 01:35:51 +00:00

55 lines
1.8 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ios
package version_test
import (
"testing"
"tailscale.com/tstest"
"tailscale.com/version"
)
// TestCmdNameFromBuildInfo asserts that CmdName recovers its result from the
// running binary's embedded Go module info (via runtime/debug.ReadBuildInfo)
// rather than returning the os.Executable-based fallback. When this test is
// run under "go test tailscale.com/version", the test binary's embedded
// build-info Path is "tailscale.com/version.test", so CmdName should return
// "version.test". The on-disk basename of the test binary (something like
// "version.test" in a go-build temp dir with random suffixes) is also
// typically "version.test", but the import-path derivation is what we care
// about: it is the only route by which a binary installed under an arbitrary
// name (e.g. "tailscaled-linux-amd64") still reports itself as "tailscaled".
func TestCmdNameFromBuildInfo(t *testing.T) {
if got, want := version.CmdName(), "version.test"; got != want {
t.Errorf("CmdName() = %q, want %q", got, want)
}
}
// BenchmarkCmdName measures the cost of the public, memoized CmdName.
// After a one-time warmup (which itself does no filesystem I/O, just an
// in-memory string lookup), this should be a trivial atomic load with zero
// allocations.
func BenchmarkCmdName(b *testing.B) {
_ = version.CmdName() // prime
b.ReportAllocs()
b.ResetTimer()
for range b.N {
_ = version.CmdName()
}
}
// TestCmdNameNoAllocs asserts that the public CmdName, once primed, performs
// no allocations. This guards against regressions that reintroduce per-call
// binary parsing.
func TestCmdNameNoAllocs(t *testing.T) {
_ = version.CmdName() // prime
if err := tstest.MinAllocsPerRun(t, 0, func() {
_ = version.CmdName()
}); err != nil {
t.Error(err)
}
}