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,85 @@
// 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.
// The chartconfig package defines the ChartConfig type, representing telemetry
// chart configuration, as well as utilities for parsing and validating this
// configuration.
//
// Chart configuration defines the set of aggregations active on the telemetry
// server, and are used to derive which data needs to be uploaded by users.
// See the original blog post for more details:
//
// https://research.swtch.com/telemetry-design#configuration
//
// The record format defined in this package differs slightly from that of the
// blog post. This format is still experimental, and subject to change.
//
// Configuration records consist of fields, comments, and whitespace. A field
// is defined by a line starting with a valid key, followed immediately by ":",
// and then a textual value, which cannot include the comment separator '#'.
//
// Comments start with '#', and extend to the end of the line.
//
// The following keys are supported. Any entry not marked as (optional) must be
// provided.
//
// - title: the chart title.
// - description: (optional) a longer description of the chart.
// - issue: a go issue tracker URL proposing the chart configuration.
// Multiple issues may be provided by including additional 'issue:' lines.
// All proposals must be in the 'accepted' state.
// - type: the chart type: currently only partition, histogram, and stack are
// supported.
// - program: the package path of the program for which this chart applies.
// - version: (optional) the first version for which this chart applies. Must
// be a valid semver value.
// - counter: the primary counter this chart illustrates, including buckets
// for histogram and partition charts.
// - depth: (optional) stack counters only; the maximum stack depth to collect
// - error: (optional) the desired error rate for this chart, which
// determines collection rate
//
// Multiple records are separated by "---" lines.
//
// For example:
//
// # This config defines an ordinary counter.
// counter: gopls/editor:{emacs,vim,vscode,other} # TODO(golang/go#34567): add more editors
// title: Editor Distribution
// description: measure editor distribution for gopls users.
// type: partition
// issue: https://go.dev/issue/12345
// program: golang.org/x/tools/gopls
// version: v1.0.0
// version: [v2.0.0, v2.3.4]
// version: [v3.0.0, ]
//
// ---
//
// # This config defines a stack counter.
// counter: gopls/bug
// title: Gopls bug reports.
// description: Stacks of bugs encountered on the gopls server.
// issue: https://go.dev/12345
// issue: https://go.dev/23456 # increase stack depth
// type: stack
// program: golang.org/x/tools/gopls
// depth: 10
package chartconfig
// A ChartConfig defines the configuration for a single chart/collection on the
// telemetry server.
//
// See the package documentation for field definitions.
type ChartConfig struct {
Title string
Description string
Issue []string
Type string
Program string
Counter string
Depth int
Error float64 // TODO(rfindley) is Error still useful?
Version string
}
@@ -0,0 +1,148 @@
# Note: these are approved chart configs, used to generate the upload config.
# For the chart config file format, see chartconfig.go.
title: Editor Distribution
counter: gopls/client:{vscode,vscodium,vscode-insiders,code-server,eglot,govim,neovim,coc.nvim,sublimetext,other}
description: measure editor distribution for gopls users.
type: partition
issue: https://go.dev/issue/61038
issue: https://go.dev/issue/62214 # add vscode-insiders
program: golang.org/x/tools/gopls
version: v0.13.0 # temporarily back-version to demonstrate config generation.
---
title: Go versions in use for gopls views
counter: gopls/goversion:{1.16,1.17,1.18,1.19,1.20,1.21,1.22,1.23,1.24,1.25,1.26,1.27,1.28,1.29,1.30}
description: measure go version usage distribution.
type: partition
issue: https://go.dev/issue/62248
program: golang.org/x/tools/gopls
version: v0.13.0
---
title: Number of bug report calls
counter: gopls/bug
description: count the bugs reported through gopls/internal/bug APIs.
type: stack
issue: https://go.dev/issue/62249
program: golang.org/x/tools/gopls
depth: 16
version: v0.13.0
---
counter: crash/crash
title: Unexpected Go crashes
description: stacks of goroutines running when the Go program crashed
type: stack
issue: https://go.dev/issue/65696
program: golang.org/x/tools/gopls
depth: 16
version: v0.15.0
---
counter: crash/malformed
title: Failure to parse runtime crash output
description: count of runtime crash messages that failed to parse
type: partition
issue: https://go.dev/issue/65696
program: golang.org/x/tools/gopls
version: v0.15.0
---
counter: crash/no-running-goroutine
title: Failure to identify any running goroutine in the crash output
description: count of runtime crash messages that don't have a running goroutine (e.g. deadlock)
type: partition
issue: https://go.dev/issue/65696
program: golang.org/x/tools/gopls
version: v0.15.0
---
counter: go/invocations
title: cmd/go invocations
description: Number of invocations of the go command
type: partition
issue: https://go.dev/issue/67244
program: cmd/go
version: go1.23rc1
---
counter: go/build/flag:{
buildmode
}
title: cmd/go flags
description: Flag names of flags provided to the go command
type: partition
issue: https://go.dev/issue/67244
program: cmd/go
version: go1.23rc1
---
counter: go/build/flag/buildmode:{
archive,
c-archive,
c-shared,
default,
exe,
pie,
shared,
plugin
}
title: cmd/go buildmode values
description: Buildmode values for the go command
type: partition
issue: https://go.dev/issue/67244
program: cmd/go
version: go1.23rc1
---
counter: compile/invocations
title: cmd/compile invocations
description: Number of invocations of the go compiler
type: partition
issue: https://go.dev/issue/67244
program: cmd/compile
version: go1.23rc1
---
title: Compiler bug report calls
counter: compile/bug
description: count stacks for cases where cmd/compile has a fatal error
type: stack
issue: https://go.dev/issue/67244
program: cmd/compile
depth: 16
version: go1.23rc1
---
counter: govulncheck/scan:{symbol,package,module}
title: Scan Level Distribution
description: measure govulncheck scan level distribution
type: partition
issue: https://go.dev/issue/67678
program: golang.org/x/vuln/cmd/govulncheck
---
counter: govulncheck/mode:{source,binary,extract,query,convert}
title: Scan Mode Distribution
description: measure govulncheck scan mode distribution
type: partition
issue: https://go.dev/issue/67678
program: golang.org/x/vuln/cmd/govulncheck
---
counter: govulncheck/format:{text,json,sarif,openvex}
title: Output Format Distribution
description: measure govulncheck output format distribution
type: partition
issue: https://go.dev/issue/67678
program: golang.org/x/vuln/cmd/govulncheck
---
counter: govulncheck/show:{none,traces,color,verbose,version}
title: Show Options Distribution
description: measure govulncheck show flag distribution
type: partition
issue: https://go.dev/issue/67678
program: golang.org/x/vuln/cmd/govulncheck
---
counter: govulncheck/assumptions:{multi-patterns,no-binary-platform,no-relative-path,no-go-root,local-replace,unknown-pkg-mod-path}
title: Code Invariants Distribution
description: measure distribution of failed govulncheck internal assumptions
type: partition
issue: https://go.dev/issue/67678
program: golang.org/x/vuln/cmd/govulncheck
---
counter: gopls/gotoolchain:{auto,path,local,other}
title: GOTOOLCHAIN types used with gopls
description: measure the types of GOTOOLCHAIN values used with gopls
type: partition
issue: https://go.dev/issue/68771
program: golang.org/x/tools/gopls
version: v0.16.0
@@ -0,0 +1,219 @@
// 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 chartconfig
import (
_ "embed"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"unicode"
)
//go:embed config.txt
var chartConfig []byte
func Raw() []byte {
return chartConfig
}
// Load loads and parses the current chart config.
func Load() ([]ChartConfig, error) {
return Parse(chartConfig)
}
// Parse parses ChartConfig records from the provided raw data, returning an
// error if the config has invalid syntax. See the package documentation for a
// description of the record syntax.
//
// Even with correct syntax, the resulting chart config may not meet all the
// requirements described in the package doc. Call [Validate] to check whether
// the config data is coherent.
func Parse(data []byte) ([]ChartConfig, error) {
// Collect field information for the record type.
var (
prefixes []string // for parse errors
fields = make(map[string]reflect.StructField) // key -> struct field
)
{
typ := reflect.TypeOf(ChartConfig{})
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
key := strings.ToLower(f.Name)
if _, ok := fieldParsers[key]; !ok {
panic(fmt.Sprintf("no parser for field %q", f.Name))
}
prefixes = append(prefixes, "'"+key+":'")
fields[key] = f
}
sort.Strings(prefixes)
}
// Read records, separated by '---'
var (
records []ChartConfig
inProgress = new(ChartConfig) // record value currently being parsed
set = make(map[string]bool) // fields that are set so far; empty records are skipped
)
flushRecord := func() {
if len(set) > 0 { // only flush non-empty records
records = append(records, *inProgress)
}
inProgress = new(ChartConfig)
set = make(map[string]bool)
}
// Within bucket braces in counter fields, newlines are ignored.
// if we're in the middle of a multiline counter field, accumulatedCounterText
// contains the joined lines of the field up to the current line. Once
// a line containing an end brace is reached, line will be set to the
// joined lines of accumulatedCounterText and processed as a single line.
var accumulatedCounterText string
for lineNum, line := range strings.Split(string(data), "\n") {
if line == "---" {
if accumulatedCounterText != "" {
return nil, fmt.Errorf("line %d: reached end of record while processing multiline counter field", lineNum)
}
flushRecord()
continue
}
text, _, _ := strings.Cut(line, "#") // trim comments
// Processing of counter fields which can appear across multiple lines.
// See comment on accumulatedCounterText.
if accumulatedCounterText == "" {
if oi := strings.Index(text, "{"); oi >= 0 {
if strings.Contains(text[:oi], "}") {
return nil, fmt.Errorf("line %d: invalid line %q: unexpected '}'", lineNum, line)
}
if strings.Contains(text[oi+len("{"):], "{") {
return nil, fmt.Errorf("line %d: invalid line %q: unexpected '{'", lineNum, line)
}
if !strings.HasPrefix(text, "counter:") {
return nil, fmt.Errorf("line %d: invalid line %q: '{' is only allowed to appear within a counter field", lineNum, line)
}
accumulatedCounterText = strings.TrimRightFunc(text, unicode.IsSpace)
// Don't continue here. If the counter field is a single line
// the check for the close brace below will close the line
// and process it as text. Set text to "" so when it's appended to
// accumulatedCounterText we don't add the line twice.
text = ""
} else if strings.Contains(text, "}") {
return nil, fmt.Errorf("line %d: invalid line %q: unexpected '}'", lineNum, line)
}
}
if accumulatedCounterText != "" {
if strings.Contains(text, "{") {
return nil, fmt.Errorf("line %d: invalid line %q: '{' is only allowed to appear once within a counter field", lineNum, line)
}
accumulatedCounterText += strings.TrimSpace(text)
if ci := strings.Index(accumulatedCounterText, "}"); ci >= 0 {
if strings.Contains(accumulatedCounterText[ci+len("}"):], "}") {
return nil, fmt.Errorf("line %d: invalid line %q: unexpected '}'", lineNum, line)
}
if ci > 0 && strings.HasSuffix(accumulatedCounterText[:ci], ",") {
return nil, fmt.Errorf("line %d: invalid line %q: unexpected '}' after ','", lineNum, line)
}
text = accumulatedCounterText
accumulatedCounterText = ""
} else {
// We're in the middle of a multiline counter field. Continue
// processing.
continue
}
}
var key string
for k := range fields {
prefix := k + ":"
if strings.HasPrefix(text, prefix) {
key = k
text = text[len(prefix):]
break
}
}
text = strings.TrimSpace(text)
if text == "" {
// Check for empty lines before the field == nil check below.
// Lines consisting only of whitespace and comments are OK.
continue
}
if key == "" {
return nil, fmt.Errorf("line %d: invalid line %q: lines must be '---', consist only of whitespace/comments, or start with %s", lineNum, line, strings.Join(prefixes, ", "))
}
field := fields[key]
v := reflect.ValueOf(inProgress).Elem().FieldByName(field.Name)
if set[key] && field.Type.Kind() != reflect.Slice {
return nil, fmt.Errorf("line %d: field %s may not be repeated", lineNum, strings.ToLower(field.Name))
}
parser := fieldParsers[key]
if err := parser(v, text); err != nil {
return nil, fmt.Errorf("line %d: field %q: %v", lineNum, field.Name, err)
}
set[key] = true
}
if accumulatedCounterText != "" {
return nil, fmt.Errorf("reached end of file while processing multiline counter field")
}
flushRecord()
return records, nil
}
// A fieldParser parses the provided input and writes to v, which must be
// addressable.
type fieldParser func(v reflect.Value, input string) error
var fieldParsers = map[string]fieldParser{
"title": parseString,
"description": parseString,
"issue": parseSlice(parseString),
"type": parseString,
"program": parseString,
"counter": parseString,
"depth": parseInt,
"error": parseFloat,
"version": parseString,
}
func parseString(v reflect.Value, input string) error {
v.SetString(input)
return nil
}
func parseInt(v reflect.Value, input string) error {
i, err := strconv.ParseInt(input, 10, 64)
if err != nil {
return fmt.Errorf("invalid int value %q", input)
}
v.SetInt(i)
return nil
}
func parseFloat(v reflect.Value, input string) error {
f, err := strconv.ParseFloat(input, 64)
if err != nil {
return fmt.Errorf("invalid float value %q", input)
}
v.SetFloat(f)
return nil
}
func parseSlice(elemParser fieldParser) fieldParser {
return func(v reflect.Value, input string) error {
elem := reflect.New(v.Type().Elem()).Elem()
v.Set(reflect.Append(v, elem))
elem = v.Index(v.Len() - 1)
if err := elemParser(elem, input); err != nil {
return err
}
return nil
}
}
@@ -0,0 +1,268 @@
// 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 chartconfig_test
import (
"reflect"
"testing"
"golang.org/x/telemetry/internal/chartconfig"
)
func TestLoad(t *testing.T) {
// Test that we can actually load the chart config.
if _, err := chartconfig.Load(); err != nil {
t.Errorf("Load() failed: %v", err)
}
}
func TestParse(t *testing.T) {
tests := []struct {
name string
input string
want []chartconfig.ChartConfig
}{
{"empty", "", nil},
{"single field", "title: A", []chartconfig.ChartConfig{{Title: "A"}}},
{
"basic", `
title: A
description: B
type: C
program: D
counter: E
issue: F1
issue: F2
depth: 2
error: 0.1
version: v2.0.0
`,
[]chartconfig.ChartConfig{{
Title: "A",
Description: "B",
Type: "C",
Program: "D",
Counter: "E",
Issue: []string{"F1", "F2"},
Depth: 2,
Error: 0.1,
Version: "v2.0.0",
}},
},
{
"partial", `
title: A
description: B
`,
[]chartconfig.ChartConfig{
{Title: "A", Description: "B"},
},
},
{
"comments and whitespace", `
# A comment
title: A # a line comment
# Another comment
description: B
`,
[]chartconfig.ChartConfig{
{Title: "A", Description: "B"},
},
},
{
"multi-record", `
# Empty records are skipped
---
title: A
description: B
---
title: C
description: D
`,
[]chartconfig.ChartConfig{
{Title: "A", Description: "B"},
{Title: "C", Description: "D"},
},
},
{
"example", `
title: Editor Distribution
counter: gopls/editor:{emacs,vim,vscode,other}
description: measure editor distribution for gopls users.
type: partition
issue: TBD
program: golang.org/x/tools/gopls
`,
[]chartconfig.ChartConfig{
{
Title: "Editor Distribution",
Description: "measure editor distribution for gopls users.",
Counter: "gopls/editor:{emacs,vim,vscode,other}",
Type: "partition",
Issue: []string{"TBD"},
Program: "golang.org/x/tools/gopls",
},
},
},
{
"multiline counter field", `
counter: foo:{
bar,
baz
}
`,
[]chartconfig.ChartConfig{
{Counter: "foo:{bar,baz}"},
},
},
{
"multiline counter field with braces immediately next to text", `
counter: foo:{bar,
baz}
`,
[]chartconfig.ChartConfig{
{Counter: "foo:{bar,baz}"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := chartconfig.Parse([]byte(test.input))
if err != nil {
t.Fatalf("Parse(...) failed: %v", err)
}
if len(got) != len(test.want) {
t.Fatalf("Parse(...) returned %d records, want %d", len(got), len(test.want))
}
for i, got := range got {
want := test.want[i]
if !reflect.DeepEqual(got, want) {
t.Errorf("Parse(...): record %d = %#v, want %#v", i, got, want)
}
}
})
}
}
func TestParseErrors(t *testing.T) {
tests := []struct {
name string
input string
}{
{
"leading space",
`
title: foo
`,
},
{
"unknown key",
`
foo: bar
`,
},
{
"bad separator",
`
title: foo
--- # comments aren't allowed after separators
title: bar
`,
},
{
"invalid depth",
`
depth: notanint
`,
},
{
"open curly brace not in counter field",
`
title: {
`,
},
{
"close curly brace not in counter field",
`
title: }
`,
},
{
"end of record within multiline counter field",
`
counter: foo{
bar
---
title: baz
`,
},
{
"end of file within multiline counter field",
`
counter: foo{
bar
`,
},
{
"close curly before open curly",
`
counter: }foo{
bar
}`,
},
{
"open curly after close curly",
`
counter: foo{
bar
} {`,
},
{
"open curly after open curly same line",
`
counter: foo{{
bar
}`,
},
{
"open curly after open curly different line",
`
counter: foo{
{bar
}`,
},
{
"close curly after close curly",
`
counter: foo{
bar
} }`,
},
{
"comma right before close curly",
`
counter: foo{
bar,
baz,
} }`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := chartconfig.Parse([]byte(test.input))
if err == nil {
t.Fatalf("Parse(...) succeeded unexpectedly")
}
})
}
}