From a4e19f22339d9d618dcf459ca5709b2701aada56 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 9 Aug 2021 17:31:29 -0700 Subject: [PATCH] version: remove rsc.io/goversion dependency rsc.io/goversion is really expensive. Running version.ReadExe on tailscaled on darwin allocates 47k objects, almost 11mb. All we want is the module info. For that, all we need to do is scan through the binary looking for the magic start/end strings and then grab the bytes in between them. We can do that easily and quickly with nothing but a 64k buffer. Signed-off-by: Josh Bleecher Snyder --- cmd/tailscale/depaware.txt | 15 ++---- cmd/tailscaled/depaware.txt | 13 ++---- go.mod | 1 - version/cmdname.go | 91 +++++++++++++++++++++++++++++++++++-- version/modinfo_test.go | 29 ++++++++++++ 5 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 version/modinfo_test.go diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 5d1a82974..cfd67c673 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -20,7 +20,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep go4.org/unsafe/assume-no-moving-gc from go4.org/intern W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+ inet.af/netaddr from tailscale.com/cmd/tailscale/cli+ - rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli+ tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+ @@ -101,9 +100,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/time/rate from tailscale.com/cmd/tailscale/cli+ bufio from compress/flate+ bytes from bufio+ - compress/flate from compress/gzip+ + compress/flate from compress/gzip compress/gzip from net/http - compress/zlib from debug/elf+ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdsa+ @@ -126,10 +124,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/tls from github.com/tcnksm/go-httpstat+ crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ - debug/dwarf from debug/elf+ - debug/elf from rsc.io/goversion/version - debug/macho from rsc.io/goversion/version - debug/pe from rsc.io/goversion/version embed from tailscale.com/cmd/tailscale/cli encoding from encoding/json+ encoding/asn1 from crypto/x509+ @@ -143,8 +137,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep expvar from tailscale.com/derp+ flag from github.com/peterbourgon/ff/v2+ fmt from compress/flate+ - hash from compress/zlib+ - hash/adler32 from compress/zlib + hash from crypto+ hash/crc32 from compress/gzip+ hash/maphash from go4.org/mem html from tailscale.com/ipn/ipnstate+ @@ -171,10 +164,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep os/exec from github.com/toqueteos/webbrowser+ os/signal from tailscale.com/cmd/tailscale/cli os/user from tailscale.com/util/groupmember - path from debug/dwarf+ + path from html/template+ path/filepath from crypto/x509+ reflect from crypto/x509+ - regexp from rsc.io/goversion/version+ + regexp from github.com/tailscale/goupnp/httpu+ regexp/syntax from regexp runtime/debug from golang.org/x/sync/singleflight sort from compress/flate+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 41dde5cca..4e5ab8c59 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -87,7 +87,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de inet.af/netstack/waiter from inet.af/netstack/tcpip+ inet.af/peercred from tailscale.com/ipn/ipnserver W 💣 inet.af/wf from tailscale.com/wf - rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/tailscale from tailscale.com/derp tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnlocal+ @@ -216,9 +215,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/time/rate from inet.af/netstack/tcpip/stack+ bufio from compress/flate+ bytes from bufio+ - compress/flate from compress/gzip+ + compress/flate from compress/gzip compress/gzip from internal/profile+ - compress/zlib from debug/elf+ container/heap from inet.af/netstack/tcpip/transport/tcp container/list from crypto/tls+ context from crypto/tls+ @@ -242,10 +240,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/tls from github.com/tcnksm/go-httpstat+ crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ - debug/dwarf from debug/elf+ - debug/elf from rsc.io/goversion/version - debug/macho from rsc.io/goversion/version - debug/pe from rsc.io/goversion/version embed from tailscale.com/net/dns+ encoding from encoding/json+ encoding/asn1 from crypto/x509+ @@ -259,8 +253,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de expvar from tailscale.com/derp+ flag from tailscale.com/cmd/tailscaled+ fmt from compress/flate+ - hash from compress/zlib+ - hash/adler32 from compress/zlib + hash from crypto+ hash/crc32 from compress/gzip+ hash/fnv from tailscale.com/wgengine/magicsock+ hash/maphash from go4.org/mem @@ -288,7 +281,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de os/exec from github.com/coreos/go-iptables/iptables+ os/signal from tailscale.com/cmd/tailscaled+ os/user from github.com/godbus/dbus/v5+ - path from debug/dwarf+ + path from github.com/godbus/dbus/v5+ path/filepath from crypto/x509+ reflect from crypto/x509+ regexp from github.com/coreos/go-iptables/iptables+ diff --git a/go.mod b/go.mod index cdcd59e4e..b5fd115e8 100644 --- a/go.mod +++ b/go.mod @@ -51,5 +51,4 @@ require ( inet.af/netstack v0.0.0-20210622165351-29b14ebc044e inet.af/peercred v0.0.0-20210318190834-4259e17bb763 inet.af/wf v0.0.0-20210516214145-a5343001b756 - rsc.io/goversion v1.2.0 ) diff --git a/version/cmdname.go b/version/cmdname.go index 9bccece03..791551d2e 100644 --- a/version/cmdname.go +++ b/version/cmdname.go @@ -8,12 +8,14 @@ package version import ( + "bytes" + "encoding/hex" + "errors" + "io" "os" "path" "path/filepath" "strings" - - "rsc.io/goversion/version" ) // CmdName returns either the base name of the current binary @@ -30,13 +32,13 @@ func CmdName() string { fallbackName := filepath.Base(strings.TrimSuffix(strings.ToLower(e), ".exe")) var ret string - v, err := version.ReadExe(e) + info, err := findModuleInfo(e) if err != nil { 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.Split(v.ModuleInfo, "\n") { + for _, line := range strings.Split(info, "\n") { if strings.HasPrefix(line, "path\t") { goPkg := strings.TrimPrefix(line, "path\t") // like "tailscale.com/cmd/tailscale" ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath @@ -48,3 +50,84 @@ func CmdName() string { } 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") +) diff --git a/version/modinfo_test.go b/version/modinfo_test.go new file mode 100644 index 000000000..c7c1a14a3 --- /dev/null +++ b/version/modinfo_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package version + +import ( + "os/exec" + "path/filepath" + "strings" + "testing" +) + +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) + } +}