From 006d7e180e4a46112b1905f1e2c2e1060ef31d8b Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 22 Apr 2026 04:38:55 +0000 Subject: [PATCH] version: use debug.ReadBuildInfo in CmdName on non-Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CmdName was re-opening the running executable and scanning it in 64KiB chunks for the Go modinfo markers on every call. The same modinfo is already parsed at startup and exposed via runtime/debug.ReadBuildInfo, so prefer that on non-Windows. Windows still takes the scanning path because its GUI-binary override keys off the on-disk executable name. benchstat of BenchmarkCmdName (Linux, before vs after): goos: linux goarch: amd64 pkg: tailscale.com/version cpu: Intel(R) Xeon(R) 6975P-C │ /tmp/old.txt │ /tmp/new.txt │ │ sec/op │ sec/op vs base │ CmdName-16 556045.5n ± 1% 825.6n ± 1% -99.85% (p=0.000 n=10) │ /tmp/old.txt │ /tmp/new.txt │ │ B/op │ B/op vs base │ CmdName-16 64.587Ki ± 0% 1.156Ki ± 0% -98.21% (p=0.000 n=10) │ /tmp/old.txt │ /tmp/new.txt │ │ allocs/op │ allocs/op vs base │ CmdName-16 8.000 ± 0% 7.000 ± 0% -12.50% (p=0.000 n=10) Fixes #19486 Change-Id: I925c5e28b64815a602459beb6c8dab8779339a6c Signed-off-by: Brad Fitzpatrick --- version/cmdname.go | 10 ++++++++++ version/version_test.go | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/version/cmdname.go b/version/cmdname.go index 5a0b84875..8e6adb047 100644 --- a/version/cmdname.go +++ b/version/cmdname.go @@ -13,6 +13,7 @@ import ( "os" "path" "runtime" + "runtime/debug" "strings" ) @@ -20,6 +21,15 @@ import ( // using os.Executable. If os.Executable fails (it shouldn't), then // "cmd" is returned. func CmdName() string { + // On non-Windows, the modinfo embedded in the running binary is + // authoritative and avoids re-reading the executable from disk. + // Windows needs the executable-name-based GUI override in cmdName, + // so it still takes the slower path. + if runtime.GOOS != "windows" { + if info, ok := debug.ReadBuildInfo(); ok && info.Path != "" { + return path.Base(info.Path) + } + } e, err := os.Executable() if err != nil { return "cmd" diff --git a/version/version_test.go b/version/version_test.go index 42bcf2163..01fcd47ec 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -6,6 +6,8 @@ package version_test import ( "bytes" "os" + "path" + "runtime/debug" "testing" ts "tailscale.com" @@ -49,3 +51,21 @@ func TestShortAllocs(t *testing.T) { t.Errorf("allocs = %v; want 0", allocs) } } + +func BenchmarkCmdName(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + _ = version.CmdName() + } +} + +func BenchmarkReadBuildInfo(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + info, ok := debug.ReadBuildInfo() + if !ok { + b.Fatal("ReadBuildInfo failed") + } + _ = path.Base(info.Path) + } +}