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.
This commit is contained in:
Ubuntu 2026-04-22 01:35:51 +00:00
parent d7916d4369
commit 927ad0aef4
4 changed files with 86 additions and 168 deletions

View File

@ -6,134 +6,51 @@
package version
import (
"bytes"
"encoding/hex"
"errors"
"io"
"os"
"path"
"runtime"
"runtime/debug"
"strings"
"sync"
)
// CmdName returns either the base name of the current binary
// using os.Executable. If os.Executable fails (it shouldn't), then
// "cmd" is returned.
func CmdName() string {
e, err := os.Executable()
if err != nil {
return "cmd"
//
// The result is computed once per process and cached. It is recovered
// from the Go module info embedded in the running binary via
// [runtime/debug.ReadBuildInfo], which reads an already-resident
// string maintained by the runtime; no filesystem I/O is performed.
// This is materially cheaper than inferring the command name from
// the on-disk executable, which was previously done by scanning the
// entire binary for magic bytes on every call. CmdName is called at
// least twice during tailscaled startup on Windows (by logpolicy).
func CmdName() string { return cmdNameCached() }
var cmdNameCached = sync.OnceValue(func() string {
// fallbackName is derived from os.Executable and used if we cannot
// recover a package path from the binary's embedded build info.
var fallbackName string
if e, err := os.Executable(); err == nil {
fallbackName = prepExeNameForCmp(e, runtime.GOARCH)
} else {
fallbackName = "cmd"
}
return cmdName(e)
}
func cmdName(exe string) string {
// fallbackName, the lowercase basename of the executable, is what we return if
// we can't find the Go module metadata embedded in the file.
fallbackName := prepExeNameForCmp(exe, runtime.GOARCH)
var ret string
info, err := findModuleInfo(exe)
if err != nil {
bi, ok := debug.ReadBuildInfo()
if !ok || bi.Path == "" {
return fallbackName
}
// v is like:
// "path\ttailscale.com/cmd/tailscale\nmod\ttailscale.com\t(devel)\t\ndep\tgithub.com/apenwarr/fixconsole\tv0.0.0-20191012055117-5a9f6489cc29\th1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=\ndep\tgithub....
for line := range strings.SplitSeq(info, "\n") {
if goPkg, ok := strings.CutPrefix(line, "path\t"); ok { // like "tailscale.com/cmd/tailscale"
ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath
break
}
}
// bi.Path is the main package import path, e.g.
// "tailscale.com/cmd/tailscaled". Go import paths are always
// forward-slash separated, so use path.Base, not filepath.Base.
ret := path.Base(bi.Path)
if runtime.GOOS == "windows" && strings.HasPrefix(ret, "gui") && checkPreppedExeNameForGUI(fallbackName) {
// The GUI binary for internal build system packaging reasons
// has a path of "tailscale.io/win/gui".
// Ignore that name and use fallbackName instead.
return fallbackName
}
if ret == "" {
// The GUI binary, for internal build-system packaging reasons,
// has a path of "tailscale.io/win/gui". Ignore that name and
// use fallbackName instead.
return fallbackName
}
return ret
}
// findModuleInfo returns the Go module info from the executable file.
func findModuleInfo(file string) (s string, err error) {
f, err := os.Open(file)
if err != nil {
return "", err
}
defer f.Close()
// Scan through f until we find infoStart.
buf := make([]byte, 65536)
start, err := findOffset(f, buf, infoStart)
if err != nil {
return "", err
}
start += int64(len(infoStart))
// Seek to the end of infoStart and scan for infoEnd.
_, err = f.Seek(start, io.SeekStart)
if err != nil {
return "", err
}
end, err := findOffset(f, buf, infoEnd)
if err != nil {
return "", err
}
length := end - start
// As of Aug 2021, tailscaled's mod info was about 2k.
if length > int64(len(buf)) {
return "", errors.New("mod info too large")
}
// We have located modinfo. Read it into buf.
buf = buf[:length]
_, err = f.Seek(start, io.SeekStart)
if err != nil {
return "", err
}
_, err = io.ReadFull(f, buf)
if err != nil {
return "", err
}
return string(buf), nil
}
// findOffset finds the absolute offset of needle in f,
// starting at f's current read position,
// using temporary buffer buf.
func findOffset(f *os.File, buf, needle []byte) (int64, error) {
for {
// Fill buf and look within it.
n, err := f.Read(buf)
if err != nil {
return -1, err
}
i := bytes.Index(buf[:n], needle)
if i < 0 {
// Not found. Rewind a little bit in case we happened to end halfway through needle.
rewind, err := f.Seek(int64(-len(needle)), io.SeekCurrent)
if err != nil {
return -1, err
}
// If we're at EOF and rewound exactly len(needle) bytes, return io.EOF.
_, err = f.ReadAt(buf[:1], rewind+int64(len(needle)))
if err == io.EOF {
return -1, err
}
continue
}
// Found! Figure out exactly where.
cur, err := f.Seek(0, io.SeekCurrent)
if err != nil {
return -1, err
}
return cur - int64(n) + int64(i), nil
}
}
// These constants are taken from rsc.io/goversion.
var (
infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
)
})

54
version/cmdname_test.go Normal file
View File

@ -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)
}
}

View File

@ -4,9 +4,7 @@
package version
var (
ExportParse = parse
ExportFindModuleInfo = findModuleInfo
ExportCmdName = cmdName
ExportParse = parse
)
type (

View File

@ -1,51 +0,0 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package version_test
import (
"flag"
"os/exec"
"path/filepath"
"strings"
"testing"
"tailscale.com/version"
)
var (
findModuleInfo = version.ExportFindModuleInfo
cmdName = version.ExportCmdName
)
func TestFindModuleInfo(t *testing.T) {
dir := t.TempDir()
name := filepath.Join(dir, "tailscaled-version-test")
out, err := exec.Command("go", "build", "-o", name, "tailscale.com/cmd/tailscaled").CombinedOutput()
if err != nil {
t.Fatalf("failed to build tailscaled: %v\n%s", err, out)
}
modinfo, err := findModuleInfo(name)
if err != nil {
t.Fatal(err)
}
prefix := "path\ttailscale.com/cmd/tailscaled\nmod\ttailscale.com"
if !strings.HasPrefix(modinfo, prefix) {
t.Errorf("unexpected modinfo contents %q", modinfo)
}
}
var findModuleInfoName = flag.String("module-info-file", "", "if non-empty, test findModuleInfo against this filename")
func TestFindModuleInfoManual(t *testing.T) {
exe := *findModuleInfoName
if exe == "" {
t.Skip("skipping without --module-info-file filename")
}
cmd := cmdName(exe)
mod, err := findModuleInfo(exe)
if err != nil {
t.Fatal(err)
}
t.Logf("Got %q from: %s", cmd, mod)
}