mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-05 12:16:44 +02:00
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.
55 lines
1.8 KiB
Go
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)
|
|
}
|
|
}
|