whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,146 @@
// 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 counter
// The implementation of this package and tests are located in
// internal/counter, which can be shared with the upload package.
// TODO(hyangah): use of type aliases prevents nice documentation
// rendering in go doc or pkgsite. Fix this either by avoiding
// type aliasing or restructuring the internal/counter package.
import (
"flag"
"path"
"runtime/debug"
"golang.org/x/telemetry/internal/counter"
"golang.org/x/telemetry/internal/telemetry"
)
// Inc increments the counter with the given name.
func Inc(name string) {
New(name).Inc()
}
// Add adds n to the counter with the given name.
func Add(name string, n int64) {
New(name).Add(n)
}
// New returns a counter with the given name.
// New can be called in global initializers and will be compiled down to
// linker-initialized data. That is, calling New to initialize a global
// has no cost at program startup.
//
// See "Counter Naming" in the package doc for a description of counter naming
// conventions.
func New(name string) *Counter {
// Note: not calling DefaultFile.New in order to keep this
// function something the compiler can inline and convert
// into static data initializations, with no init-time footprint.
// TODO(hyangah): is it trivial enough for the compiler to inline?
return counter.New(name)
}
// A Counter is a single named event counter.
// A Counter is safe for use by multiple goroutines simultaneously.
//
// Counters should typically be created using New
// and stored as global variables, like:
//
// package mypackage
// var errorCount = counter.New("mypackage/errors")
//
// (The initialization of errorCount in this example is handled
// entirely by the compiler and linker; this line executes no code
// at program startup.)
//
// Then code can call Add to increment the counter
// each time the corresponding event is observed.
//
// Although it is possible to use New to create
// a Counter each time a particular event needs to be recorded,
// that usage fails to amortize the construction cost over
// multiple calls to Add, so it is more expensive and not recommended.
type Counter = counter.Counter
// A StackCounter is the in-memory knowledge about a stack counter.
// StackCounters are more expensive to use than regular Counters,
// requiring, at a minimum, a call to runtime.Callers.
type StackCounter = counter.StackCounter
// NewStack returns a new stack counter with the given name and depth.
//
// See "Counter Naming" in the package doc for a description of counter naming
// conventions.
func NewStack(name string, depth int) *StackCounter {
return counter.NewStack(name, depth)
}
// Open prepares telemetry counters for recording to the file system.
//
// If the telemetry mode is "off", Open is a no-op. Otherwise, it opens the
// counter file on disk and starts to mmap telemetry counters to the file.
// Open also persists any counters already created in the current process.
//
// Open should only be called from short-lived processes such as command line
// tools. If your process is long-running, use [OpenAndRotate].
func Open() {
counter.Open(false)
}
// OpenAndRotate is like [Open], but also schedules a rotation of the counter
// file when it expires.
//
// See golang/go#68497 for background on why [OpenAndRotate] is a separate API.
//
// TODO(rfindley): refactor Open and OpenAndRotate for Go 1.24.
func OpenAndRotate() {
counter.Open(true)
}
// OpenDir prepares telemetry counters for recording to the file system, using
// the specified telemetry directory, if it is not the empty string.
//
// If the telemetry mode is "off", Open is a no-op. Otherwise, it opens the
// counter file on disk and starts to mmap telemetry counters to the file.
// Open also persists any counters already created in the current process.
func OpenDir(telemetryDir string) {
if telemetryDir != "" {
telemetry.Default = telemetry.NewDir(telemetryDir)
}
counter.Open(false)
}
// CountFlags creates a counter for every flag that is set
// and increments the counter. The name of the counter is
// the concatenation of prefix and the flag name.
//
// For instance, CountFlags("gopls/flag:", *flag.CommandLine)
func CountFlags(prefix string, fs flag.FlagSet) {
fs.Visit(func(f *flag.Flag) {
New(prefix + f.Name).Inc()
})
}
// CountCommandLineFlags creates a counter for every flag
// that is set in the default flag.CommandLine FlagSet using
// the counter name binaryName+"/flag:"+flagName where
// binaryName is the base name of the Path embedded in the
// binary's build info. If the binary does not have embedded build
// info, the "flag:"+flagName counter will be incremented.
//
// CountCommandLineFlags must be called after flags are parsed
// with flag.Parse.
//
// For instance, if the -S flag is passed to cmd/compile and
// CountCommandLineFlags is called after flags are parsed,
// the "compile/flag:S" counter will be incremented.
func CountCommandLineFlags() {
prefix := "flag:"
if buildInfo, ok := debug.ReadBuildInfo(); ok && buildInfo.Path != "" {
prefix = path.Base(buildInfo.Path) + "/" + prefix
}
CountFlags(prefix, *flag.CommandLine)
}
@@ -0,0 +1,44 @@
// 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 counter_test
import (
"os"
"os/exec"
"testing"
"golang.org/x/telemetry/counter"
"golang.org/x/telemetry/internal/telemetry"
"golang.org/x/telemetry/internal/testenv"
)
const telemetryDirEnvVar = "_COUNTER_TEST_TELEMETRY_DIR"
func TestMain(m *testing.M) {
if dir := os.Getenv(telemetryDirEnvVar); dir != "" {
// Run for TestOpenAPIMisuse.
telemetry.Default = telemetry.NewDir(dir)
counter.Open()
counter.OpenAndRotate() // should panic
os.Exit(0)
}
os.Exit(m.Run())
}
func TestOpenAPIMisuse(t *testing.T) {
testenv.SkipIfUnsupportedPlatform(t)
// Test that Open and OpenAndRotate cannot be used simultaneously.
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(exe)
cmd.Env = append(os.Environ(), telemetryDirEnvVar+"="+t.TempDir())
if err := cmd.Run(); err == nil {
t.Error("Failed to detect API misuse: no error from calling both Open and OpenAndRotate")
}
}
@@ -0,0 +1,63 @@
// 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.
// countertest provides testing utilities for counters.
// This package cannot be used except for testing.
package countertest
import (
"sync"
"golang.org/x/telemetry/counter"
ic "golang.org/x/telemetry/internal/counter"
"golang.org/x/telemetry/internal/telemetry"
)
var (
openedMu sync.Mutex
opened bool
)
// SupportedPlatform reports if this platform supports Open()
const SupportedPlatform = !telemetry.DisabledOnPlatform
func isOpen() bool {
openedMu.Lock()
defer openedMu.Unlock()
return opened
}
// Open enables telemetry data writing to disk.
// This is supposed to be called once during the program execution
// (i.e. typically in TestMain), and must not be used with
// golang.org/x/telemetry/counter.Open.
func Open(telemetryDir string) {
openedMu.Lock()
defer openedMu.Unlock()
if opened {
panic("Open was called more than once")
}
telemetry.Default = telemetry.NewDir(telemetryDir)
// TODO(rfindley): reinstate test coverage with counter rotation enabled.
// Before the [counter.Open] and [counter.OpenAndRotate] APIs were split,
// this called counter.Open (which rotated!).
counter.Open()
opened = true
}
// ReadCounter reads the given counter.
func ReadCounter(c *counter.Counter) (count uint64, _ error) {
return ic.Read(c)
}
// ReadStackCounter reads the given StackCounter.
func ReadStackCounter(c *counter.StackCounter) (stackCounts map[string]uint64, _ error) {
return ic.ReadStack(c)
}
// ReadFile reads the counters and stack counters from the given file.
func ReadFile(name string) (counters, stackCounters map[string]uint64, _ error) {
return ic.ReadFile(name)
}
@@ -0,0 +1,16 @@
// 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.
//go:build go1.21
package countertest
import "testing"
func init() {
// Extra safety check for go1.21+.
if !testing.Testing() {
panic("use of this package is disallowed in non-testing code")
}
}
@@ -0,0 +1,90 @@
// 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.
//go:build go1.21
package countertest
import (
"fmt"
"slices"
"strings"
"sync"
"testing"
"golang.org/x/telemetry/counter"
"golang.org/x/telemetry/internal/telemetry"
"golang.org/x/telemetry/internal/testenv"
)
func TestReadCounter(t *testing.T) {
testenv.SkipIfUnsupportedPlatform(t)
c := counter.New("foobar")
got, err := ReadCounter(c)
if err != nil {
t.Errorf("ReadCounter = (%d, %v), want (0,nil)", got, err)
}
if got != 0 {
t.Fatalf("ReadCounter = %d, want 0", got)
}
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
c.Inc()
wg.Done()
}()
}
wg.Wait()
if got, err := ReadCounter(c); err != nil || got != 100 {
t.Errorf("ReadCounter = (%v, %v), want (%v, nil)", got, err, 100)
}
}
func TestReadStackCounter(t *testing.T) {
testenv.SkipIfUnsupportedPlatform(t)
c := counter.NewStack("foobar", 8)
if got, err := ReadStackCounter(c); err != nil || len(got) != 0 {
t.Errorf("ReadStackCounter = (%q, %v), want (%v, nil)", got, err, 0)
}
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
c.Inc() // one stack!
wg.Done()
}()
}
wg.Wait()
got, err := ReadStackCounter(c)
if err != nil || len(got) != 1 {
t.Fatalf("ReadStackCounter = (%v, %v), want to read one entry", stringify(got), err)
}
for k, v := range got {
if !strings.Contains(k, t.Name()) || v != 100 {
t.Fatalf("ReadStackCounter = %v, want a stack counter with value 100", got)
}
}
}
func TestSupport(t *testing.T) {
if SupportedPlatform == telemetry.DisabledOnPlatform {
t.Errorf("supported mismatch: us %v, telemetry.internal.Disabled %v",
SupportedPlatform, telemetry.DisabledOnPlatform)
}
}
func stringify(m map[string]uint64) string {
kv := make([]string, 0, len(m))
for k, v := range m {
kv = append(kv, fmt.Sprintf("%q:%v", k, v))
}
slices.Sort(kv)
return "{" + strings.Join(kv, " ") + "}"
}
@@ -0,0 +1,58 @@
// 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 counter implements a simple counter system for collecting
// totally public telemetry data.
//
// There are two kinds of counters, basic counters and stack counters.
// Basic counters are created by [New].
// Stack counters are created by [NewStack].
// Both are incremented by calling Inc().
//
// Basic counters are very cheap. Stack counters are more expensive, as they
// require parsing the stack. (Stack counters are implemented as basic counters
// whose names are the concatenation of the name and the stack trace. There is
// an upper limit on the size of this name, about 4K bytes. If the name is too
// long the stack will be truncated and "truncated" appended.)
//
// When counter files expire they are turned into reports by the upload
// package. The first time any counter file is created for a user, a random day
// of the week is selected on which counter files will expire. For the first
// week, that day is more than 7 days (but not more than two weeks) in the
// future. After that the counter files expire weekly on the same day of the
// week.
//
// # Counter Naming
//
// Counter names passed to [New] and [NewStack] should follow these
// conventions:
//
// - Names cannot contain whitespace or newlines.
//
// - Names must be valid unicode, with no unprintable characters.
//
// - Names may contain at most one ':'. In the counter "foo:bar", we refer to
// "foo" as the "chart name" and "bar" as the "bucket name".
//
// - The '/' character should partition counter names into a hierarchy. The
// root of this hierarchy should identify the logical entity that "owns"
// the counter. This could be an application, such as "gopls" in the case
// of "gopls/client:vscode", or a shared library, such as "crash" in the
// case of the "crash/crash" counter owned by the crashmonitor library. If
// the entity name itself contains a '/', that's ok: "cmd/go/flag" is fine.
//
// - Words should be '-' separated, as in "gopls/completion/errors-latency"
//
// - Histograms should use bucket names identifying upper bounds with '<'.
// For example given two counters "gopls/completion/latency:<50ms" and
// "gopls/completion/latency:<100ms", the "<100ms" bucket counts events
// with latency in the half-open interval [50ms, 100ms).
//
// # Debugging
//
// The GODEBUG environment variable can enable printing of additional debug
// information for counters. Adding GODEBUG=countertrace=1 to the environment
// of a process using counters causes the x/telemetry/counter package to log
// counter information to stderr.
package counter