summaryrefslogtreecommitdiffhomepage
path: root/version/cmdname_test.go
diff options
context:
space:
mode:
authorUbuntu <james@tailscale.com>2026-04-22 01:35:51 +0000
committerUbuntu <james@tailscale.com>2026-04-22 01:35:51 +0000
commit927ad0aef4cea18afb013bd49be4b84d6b2e3aac (patch)
tree81dab29c9d1c80b9c11367d2a009c14d00c234c6 /version/cmdname_test.go
parentd7916d4369bd2b880281e38edac2f600d5ab2673 (diff)
downloadtailscale-raggi/cmdname.tar.xz
tailscale-raggi/cmdname.zip
version: compute CmdName from in-memory build info, not on-disk scanraggi/cmdname
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.
Diffstat (limited to 'version/cmdname_test.go')
-rw-r--r--version/cmdname_test.go54
1 files changed, 54 insertions, 0 deletions
diff --git a/version/cmdname_test.go b/version/cmdname_test.go
new file mode 100644
index 000000000..94f4e984d
--- /dev/null
+++ b/version/cmdname_test.go
@@ -0,0 +1,54 @@
+// 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)
+ }
+}