whatcanGOwrong
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2024 The Go 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 telemetry
|
||||
|
||||
// TODO(rfindley): replace uses of DateOnly with time.DateOnly once we no
|
||||
// longer support building gopls with go 1.19.
|
||||
const DateOnly = "2006-01-02"
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
// Copyright 2023 The Go 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 telemetry manages the telemetry mode file.
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Default is the default directory containing Go telemetry configuration and
|
||||
// data.
|
||||
//
|
||||
// If Default is uninitialized, Default.Mode will be "off". As a consequence,
|
||||
// no data should be written to the directory, and so the path values of
|
||||
// LocalDir, UploadDir, etc. must not matter.
|
||||
//
|
||||
// Default is a global for convenience and testing, but should not be mutated
|
||||
// outside of tests.
|
||||
//
|
||||
// TODO(rfindley): it would be nice to completely eliminate this global state,
|
||||
// or at least push it in the golang.org/x/telemetry package
|
||||
var Default Dir
|
||||
|
||||
// A Dir holds paths to telemetry data inside a directory.
|
||||
type Dir struct {
|
||||
dir, local, upload, debug, modefile string
|
||||
}
|
||||
|
||||
// NewDir creates a new Dir encapsulating paths in the given dir.
|
||||
//
|
||||
// NewDir does not create any new directories or files--it merely encapsulates
|
||||
// the telemetry directory layout.
|
||||
func NewDir(dir string) Dir {
|
||||
return Dir{
|
||||
dir: dir,
|
||||
local: filepath.Join(dir, "local"),
|
||||
upload: filepath.Join(dir, "upload"),
|
||||
debug: filepath.Join(dir, "debug"),
|
||||
modefile: filepath.Join(dir, "mode"),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
Default = NewDir(filepath.Join(cfgDir, "go", "telemetry"))
|
||||
}
|
||||
|
||||
func (d Dir) Dir() string {
|
||||
return d.dir
|
||||
}
|
||||
|
||||
func (d Dir) LocalDir() string {
|
||||
return d.local
|
||||
}
|
||||
|
||||
func (d Dir) UploadDir() string {
|
||||
return d.upload
|
||||
}
|
||||
|
||||
func (d Dir) DebugDir() string {
|
||||
return d.debug
|
||||
}
|
||||
|
||||
func (d Dir) ModeFile() string {
|
||||
return d.modefile
|
||||
}
|
||||
|
||||
// SetMode updates the telemetry mode with the given mode.
|
||||
// Acceptable values for mode are "on", "off", or "local".
|
||||
//
|
||||
// SetMode always writes the mode file, and explicitly records the date at
|
||||
// which the modefile was updated. This means that calling SetMode with "on"
|
||||
// effectively resets the timeout before the next telemetry report is uploaded.
|
||||
func (d Dir) SetMode(mode string) error {
|
||||
return d.SetModeAsOf(mode, time.Now())
|
||||
}
|
||||
|
||||
// SetModeAsOf is like SetMode, but accepts an explicit time to use to
|
||||
// back-date the mode state. This exists only for testing purposes.
|
||||
func (d Dir) SetModeAsOf(mode string, asofTime time.Time) error {
|
||||
mode = strings.TrimSpace(mode)
|
||||
switch mode {
|
||||
case "on", "off", "local":
|
||||
default:
|
||||
return fmt.Errorf("invalid telemetry mode: %q", mode)
|
||||
}
|
||||
if d.modefile == "" {
|
||||
return fmt.Errorf("cannot determine telemetry mode file name")
|
||||
}
|
||||
// TODO(rfindley): why is this not 777, consistent with the use of 666 below?
|
||||
if err := os.MkdirAll(filepath.Dir(d.modefile), 0755); err != nil {
|
||||
return fmt.Errorf("cannot create a telemetry mode file: %w", err)
|
||||
}
|
||||
|
||||
asof := asofTime.UTC().Format(DateOnly)
|
||||
// Defensively guarantee that we can parse the asof time.
|
||||
if _, err := time.Parse(DateOnly, asof); err != nil {
|
||||
return fmt.Errorf("internal error: invalid mode date %q: %v", asof, err)
|
||||
}
|
||||
|
||||
data := []byte(mode + " " + asof)
|
||||
return os.WriteFile(d.modefile, data, 0666)
|
||||
}
|
||||
|
||||
// Mode returns the current telemetry mode, as well as the time that the mode
|
||||
// was effective.
|
||||
//
|
||||
// If there is no effective time, the second result is the zero time.
|
||||
//
|
||||
// If Mode is "off", no data should be written to the telemetry directory, and
|
||||
// the other paths values referenced by Dir should be considered undefined.
|
||||
// This accounts for the case where initializing [Default] fails, and therefore
|
||||
// local telemetry paths are unknown.
|
||||
func (d Dir) Mode() (string, time.Time) {
|
||||
if d.modefile == "" {
|
||||
return "off", time.Time{} // it's likely LocalDir/UploadDir are empty too. Turn off telemetry.
|
||||
}
|
||||
data, err := os.ReadFile(d.modefile)
|
||||
if err != nil {
|
||||
return "local", time.Time{} // default
|
||||
}
|
||||
mode := string(data)
|
||||
mode = strings.TrimSpace(mode)
|
||||
|
||||
// Forward compatibility for https://go.dev/issue/63142#issuecomment-1734025130
|
||||
//
|
||||
// If the modefile contains a date, return it.
|
||||
if idx := strings.Index(mode, " "); idx >= 0 {
|
||||
d, err := time.Parse(DateOnly, mode[idx+1:])
|
||||
if err != nil {
|
||||
d = time.Time{}
|
||||
}
|
||||
return mode[:idx], d
|
||||
}
|
||||
|
||||
return mode, time.Time{}
|
||||
}
|
||||
|
||||
// DisabledOnPlatform indicates whether telemetry is disabled
|
||||
// due to bugs in the current platform.
|
||||
//
|
||||
// TODO(rfindley): move to a more appropriate file.
|
||||
const DisabledOnPlatform = false ||
|
||||
// The following platforms could potentially be supported in the future:
|
||||
runtime.GOOS == "openbsd" || // #60614
|
||||
runtime.GOOS == "solaris" || // #60968 #60970
|
||||
runtime.GOOS == "android" || // #60967
|
||||
runtime.GOOS == "illumos" || // #65544
|
||||
// These platforms fundamentally can't be supported:
|
||||
runtime.GOOS == "js" || // #60971
|
||||
runtime.GOOS == "wasip1" || // #60971
|
||||
runtime.GOOS == "plan9" || // https://github.com/golang/go/issues/57540#issuecomment-1470766639
|
||||
runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" // mips lacks cross-process 64-bit atomics
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2023 The Go 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 telemetrymode manages the telemetry mode file.
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDefaults(t *testing.T) {
|
||||
defaultDirMissing := false
|
||||
if _, err := os.UserConfigDir(); err != nil {
|
||||
defaultDirMissing = true
|
||||
}
|
||||
if defaultDirMissing {
|
||||
if Default.LocalDir() != "" || Default.UploadDir() != "" || Default.ModeFile() != "" {
|
||||
t.Errorf("DefaultSetting: (%q, %q, %q), want empty LocalDir/UploadDir/ModeFile", Default.LocalDir(), Default.UploadDir(), Default.ModeFile())
|
||||
}
|
||||
} else {
|
||||
if Default.LocalDir() == "" || Default.UploadDir() == "" || Default.ModeFile() == "" {
|
||||
t.Errorf("DefaultSetting: (%q, %q, %q), want non-empty LocalDir/UploadDir/ModeFile", Default.LocalDir(), Default.UploadDir(), Default.ModeFile())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelemetryModeWithNoModeConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
dir Dir
|
||||
want string
|
||||
}{
|
||||
{NewDir(t.TempDir()), "local"},
|
||||
{Dir{}, "off"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got, _ := tt.dir.Mode(); got != tt.want {
|
||||
t.Errorf("Dir{modefile=%q}.Mode() = %v, want %v", tt.dir.ModeFile(), got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
wantErr bool // want error when setting.
|
||||
}{
|
||||
{"on", false},
|
||||
{"off", false},
|
||||
{"local", false},
|
||||
{"https://mytelemetry.com", true},
|
||||
{"http://insecure.com", true},
|
||||
{"bogus", true},
|
||||
{"", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run("mode="+tt.in, func(t *testing.T) {
|
||||
dir := NewDir(t.TempDir())
|
||||
setErr := dir.SetMode(tt.in)
|
||||
if (setErr != nil) != tt.wantErr {
|
||||
t.Fatalf("Set() error = %v, wantErr %v", setErr, tt.wantErr)
|
||||
}
|
||||
if setErr != nil {
|
||||
return
|
||||
}
|
||||
if got, _ := dir.Mode(); got != tt.in {
|
||||
t.Errorf("LookupMode() = %q, want %q", got, tt.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
wantMode string
|
||||
wantTime time.Time
|
||||
}{
|
||||
{"on", "on", time.Time{}},
|
||||
{"on 2023-09-26", "on", time.Date(2023, time.September, 26, 0, 0, 0, 0, time.UTC)},
|
||||
{"off", "off", time.Time{}},
|
||||
{"local", "local", time.Time{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run("mode="+tt.in, func(t *testing.T) {
|
||||
dir := NewDir(t.TempDir())
|
||||
if err := os.WriteFile(dir.ModeFile(), []byte(tt.in), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Note: the checks below intentionally do not use time.Equal:
|
||||
// we want this exact representation of time.
|
||||
if gotMode, gotTime := dir.Mode(); gotMode != tt.wantMode || gotTime != tt.wantTime {
|
||||
t.Errorf("ModeFilePath(contents=%s).Mode() = %q, %v, want %q, %v", tt.in, gotMode, gotTime, tt.wantMode, tt.wantTime)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright 2024 The Go 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 telemetry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsToolchainProgram reports whether a program with the given path is a Go
|
||||
// toolchain program.
|
||||
func IsToolchainProgram(progPath string) bool {
|
||||
return strings.HasPrefix(progPath, "cmd/")
|
||||
}
|
||||
|
||||
// ProgramInfo extracts the go version, program package path, and program
|
||||
// version to use for counter files.
|
||||
//
|
||||
// For programs in the Go toolchain, the program version will be the same as
|
||||
// the Go version, and will typically be of the form "go1.2.3", not a semantic
|
||||
// version of the form "v1.2.3". Go versions may also include spaces and
|
||||
// special characters.
|
||||
func ProgramInfo(info *debug.BuildInfo) (goVers, progPath, progVers string) {
|
||||
goVers = info.GoVersion
|
||||
// TODO(matloob): Use go/version.IsValid instead of checking for X: once the telemetry
|
||||
// module can be upgraded to require Go 1.22.
|
||||
if strings.Contains(goVers, "devel") || strings.Contains(goVers, "-") || strings.Contains(goVers, "X:") {
|
||||
goVers = "devel"
|
||||
}
|
||||
|
||||
progPath = info.Path
|
||||
if progPath == "" {
|
||||
progPath = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
|
||||
}
|
||||
|
||||
// Main module version information is not populated for the cmd module, but
|
||||
// we can re-use the Go version here.
|
||||
if IsToolchainProgram(progPath) {
|
||||
progVers = goVers
|
||||
} else {
|
||||
progVers = info.Main.Version
|
||||
if strings.Contains(progVers, "devel") || strings.Count(progVers, "-") > 1 {
|
||||
// Heuristically mark all pseudo-version-like version strings as "devel"
|
||||
// to avoid creating too many counter files.
|
||||
// We should not use regexp that pulls in large dependencies.
|
||||
// Pseudo-versions have at least three parts (https://go.dev/ref/mod#pseudo-versions).
|
||||
// This heuristic still allows use to track prerelease
|
||||
// versions (e.g. gopls@v0.16.0-pre.1, vscgo@v0.42.0-rc.1).
|
||||
progVers = "devel"
|
||||
}
|
||||
}
|
||||
|
||||
return goVers, progPath, progVers
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// Copyright 2023 The Go 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 telemetry_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/telemetry/internal/telemetry"
|
||||
)
|
||||
|
||||
func TestProgramInfo_ProgramVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
version string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
path: "golang.org/x/tools/gopls",
|
||||
version: "(devel)",
|
||||
want: "devel",
|
||||
},
|
||||
{
|
||||
path: "golang.org/x/tools/gopls",
|
||||
version: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
path: "golang.org/x/tools/gopls",
|
||||
version: "v0.14.0-pre.1",
|
||||
want: "v0.14.0-pre.1",
|
||||
},
|
||||
{
|
||||
path: "golang.org/x/tools/gopls",
|
||||
version: "v0.0.0-20231207172801-3c8b0df0c3fd",
|
||||
want: "devel",
|
||||
},
|
||||
{
|
||||
path: "cmd/go",
|
||||
version: "",
|
||||
want: "go1.23.0", // hard-coded below
|
||||
},
|
||||
{
|
||||
path: "cmd/compile",
|
||||
version: "",
|
||||
want: "go1.23.0", // hard-coded below
|
||||
},
|
||||
}
|
||||
buildInfo, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
t.Fatal("cannot use debug.ReadBuildInfo")
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
name := fmt.Sprintf("%s@%s", path.Base(tt.path), tt.version)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
in := *buildInfo
|
||||
in.GoVersion = "go1.23.0"
|
||||
in.Path = tt.path
|
||||
in.Main.Version = tt.version
|
||||
_, _, got := telemetry.ProgramInfo(&in)
|
||||
if got != tt.want {
|
||||
t.Errorf("program version = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgramInfo_GoVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
goVersion string
|
||||
wantGoVers string
|
||||
}{
|
||||
{
|
||||
"go1.23.0-bigcorp",
|
||||
"devel",
|
||||
},
|
||||
{
|
||||
"go1.23.0",
|
||||
"go1.23.0",
|
||||
},
|
||||
{
|
||||
"devel go1.24-0d6bb68f48 Thu Jul 25 23:27:41 2024 -0600",
|
||||
"devel",
|
||||
},
|
||||
{
|
||||
"go1.23rc2 X:aliastypeparams",
|
||||
"devel",
|
||||
},
|
||||
}
|
||||
buildInfo, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
t.Fatal("cannot use debug.ReadBuildInfo")
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.goVersion, func(t *testing.T) {
|
||||
in := *buildInfo
|
||||
in.GoVersion = tt.goVersion
|
||||
in.Path = "cmd/go"
|
||||
in.Main.Version = tt.goVersion
|
||||
gotGoVers, _, gotProgVers := telemetry.ProgramInfo((&in))
|
||||
if gotGoVers != tt.wantGoVers {
|
||||
t.Errorf("go version = %q, want %q", gotGoVers, tt.wantGoVers)
|
||||
}
|
||||
if gotProgVers != tt.wantGoVers {
|
||||
t.Errorf("program version = %q, want %q", gotProgVers, tt.wantGoVers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// Copyright 2023 The Go 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 telemetry
|
||||
|
||||
// Common types and directories used by multiple packages.
|
||||
|
||||
// An UploadConfig controls what data is uploaded.
|
||||
type UploadConfig struct {
|
||||
GOOS []string
|
||||
GOARCH []string
|
||||
GoVersion []string
|
||||
SampleRate float64
|
||||
Programs []*ProgramConfig
|
||||
}
|
||||
|
||||
type ProgramConfig struct {
|
||||
// the counter names may have to be
|
||||
// repeated for each program. (e.g., if the counters are in a package
|
||||
// that is used in more than one program.)
|
||||
Name string
|
||||
Versions []string // versions present in a counterconfig
|
||||
Counters []CounterConfig `json:",omitempty"`
|
||||
Stacks []CounterConfig `json:",omitempty"`
|
||||
}
|
||||
|
||||
type CounterConfig struct {
|
||||
Name string
|
||||
Rate float64 // If X <= Rate, report this counter
|
||||
Depth int `json:",omitempty"` // for stack counters
|
||||
}
|
||||
|
||||
// A Report is the weekly aggregate of counters.
|
||||
type Report struct {
|
||||
Week string // End day this report covers (YYYY-MM-DD)
|
||||
LastWeek string // Week field from latest previous report uploaded
|
||||
X float64 // A random probability used to determine which counters are uploaded
|
||||
Programs []*ProgramReport
|
||||
Config string // version of UploadConfig used
|
||||
}
|
||||
|
||||
type ProgramReport struct {
|
||||
Program string // Package path of the program.
|
||||
Version string // Program version. Go version if the program is part of the go distribution. Module version, otherwise.
|
||||
GoVersion string // Go version used to build the program.
|
||||
GOOS string
|
||||
GOARCH string
|
||||
Counters map[string]int64
|
||||
Stacks map[string]int64
|
||||
}
|
||||
Reference in New Issue
Block a user