godev/cmd/worker: skip empty partition in charts
When there is no data to draw, do not include in the charts data.
Previously, this resulted in adding null to chartdata > Programs >
Charts and caused the frontend javascript to throw an exception.
Update charts.ts to handle chart json files that contain null charts.
While we are here,
* remove the exception for cmd/go (which does not have version).
After CL 585198 go toolchain programs will produce counter files
with the program field filled with the go version.
* fix the program version handling since now toolchains will have
version strings that are not semver.
Fixes golang/go#67397
Change-Id: I0c037afbbfd931b70ada310dba9d67b49521ad94
Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/585479
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/godev/cmd/worker/main.go b/godev/cmd/worker/main.go
index 81cb70b..31951a3 100644
--- a/godev/cmd/worker/main.go
+++ b/godev/cmd/worker/main.go
@@ -55,7 +55,7 @@
mux := http.NewServeMux()
mux.Handle("/", cserv)
- mux.Handle("/merge/", handleMerge(ucfg, buckets))
+ mux.Handle("/merge/", handleMerge(buckets))
mux.Handle("/chart/", handleChart(ucfg, buckets))
mux.Handle("/queue-tasks/", handleTasks(cfg))
@@ -135,7 +135,7 @@
}
// TODO: monitor duration and processed data volume.
-func handleMerge(cfg *tconfig.Config, s *storage.API) content.HandlerFunc {
+func handleMerge(s *storage.API) content.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
date := r.URL.Query().Get("date")
@@ -271,22 +271,23 @@
for _, p := range cfg.Programs {
prog := &program{ID: "charts:" + p.Name, Name: p.Name}
result.Programs = append(result.Programs, prog)
- if p.Name != "cmd/go" {
- prog.Charts = append(prog.Charts,
- partition(d, p.Name, "Version", p.Versions, xs),
- )
+ var charts []*chart
+ if !telemetry.IsToolchainProgram(p.Name) {
+ charts = append(charts, partition(d, p.Name, "Version", p.Versions))
}
- prog.Charts = append(prog.Charts,
- partition(d, p.Name, "GOOS", cfg.GOOS, xs),
- partition(d, p.Name, "GOARCH", cfg.GOARCH, xs),
- partition(d, p.Name, "GoVersion", cfg.GoVersion, xs),
- )
+ charts = append(charts,
+ partition(d, p.Name, "GOOS", cfg.GOOS),
+ partition(d, p.Name, "GOARCH", cfg.GOARCH),
+ partition(d, p.Name, "GoVersion", cfg.GoVersion))
for _, c := range p.Counters {
// TODO: add support for histogram counters by getting the counter type
// from the chart config.
- prog.Charts = append(prog.Charts,
- partition(d, p.Name, c.Name, tconfig.Expand(c.Name), xs),
- )
+ charts = append(charts, partition(d, p.Name, c.Name, tconfig.Expand(c.Name)))
+ }
+ for _, p := range charts {
+ if p != nil {
+ prog.Charts = append(prog.Charts, p)
+ }
}
}
return result
@@ -300,7 +301,7 @@
}
for wk := range dat {
for _, c := range counters {
- prefix, _ := parts(c)
+ prefix, _ := splitCounterName(c)
pk := programKey{program}
gk := graphKey{prefix}
ck := counterKey{c}
@@ -311,7 +312,7 @@
xk := xKey{x}
v := dat[wk][pk][gk][ck][xk]
total := dat[wk][pk][gk][counterKey{gk.prefix}][xk]
- _, bucket := parts(c)
+ _, bucket := splitCounterName(c)
if total == 0 {
d := &datum{
Week: wk.date,
@@ -332,14 +333,16 @@
return count
}
-func partition(dat data, program, name string, counters []string, xs []float64) *chart {
+// partition builds a chart for the program and the counter. It can return nil
+// if there is no data for the counter in dat.
+func partition(dat data, program, counterPrefix string, counters []string) *chart {
count := &chart{
- ID: "charts:" + program + ":" + name,
- Name: name,
+ ID: "charts:" + program + ":" + counterPrefix,
+ Name: counterPrefix,
Type: "partition",
}
pk := programKey{program}
- prefix, _ := parts(name)
+ prefix, _ := splitCounterName(counterPrefix)
gk := graphKey{prefix}
for wk := range dat {
// TODO: when should this be number of reports?
@@ -352,7 +355,8 @@
// major minor versions we've already added to the dataset.
seen := make(map[string]bool)
for _, b := range counters {
- counter := fixCounter(name, b)
+ // TODO(hyangah): let caller normalize names in counters.
+ counter := normalizeCounterName(counterPrefix, b)
if seen[counter] {
continue
}
@@ -360,7 +364,7 @@
ck := counterKey{counter}
// number of reports where count prefix:bucket > 0
n := len(dat[wk][pk][gk][ck])
- _, bucket := parts(counter)
+ _, bucket := splitCounterName(counter)
d := &datum{
Week: wk.date,
Key: bucket,
@@ -394,11 +398,13 @@
type data map[weekKey]map[programKey]map[graphKey]map[counterKey]map[xKey]int64
+// Names of special counters.
+// Unlike other counters, these are constructed from the metadata in the report.
const (
- version = "Version"
- goos = "GOOS"
- goarch = "GOARCH"
- goVersion = "GoVersion"
+ versionCounter = "Version"
+ goosCounter = "GOOS"
+ goarchCounter = "GOARCH"
+ goversionCounter = "GoVersion"
)
// nest groups the report data by week, program, prefix, counter, and x value
@@ -407,12 +413,12 @@
result := make(data)
for _, r := range reports {
for _, p := range r.Programs {
- writeCount(result, r.Week, p.Program, version, p.Version, r.X, 1)
- writeCount(result, r.Week, p.Program, goos, p.GOOS, r.X, 1)
- writeCount(result, r.Week, p.Program, goarch, p.GOARCH, r.X, 1)
- writeCount(result, r.Week, p.Program, goVersion, p.GoVersion, r.X, 1)
+ writeCount(result, r.Week, p.Program, versionCounter, p.Version, r.X, 1)
+ writeCount(result, r.Week, p.Program, goosCounter, p.GOOS, r.X, 1)
+ writeCount(result, r.Week, p.Program, goarchCounter, p.GOARCH, r.X, 1)
+ writeCount(result, r.Week, p.Program, goversionCounter, p.GoVersion, r.X, 1)
for c, value := range p.Counters {
- name, _ := parts(c)
+ name, _ := splitCounterName(c)
writeCount(result, r.Week, p.Program, name, c, r.X, value)
}
}
@@ -436,7 +442,8 @@
if _, ok := result[wk][pk][gk]; !ok {
result[wk][pk][gk] = make(map[counterKey]map[xKey]int64)
}
- counter = fixCounter(prefix, counter)
+ // TODO(hyangah): let caller pass the normalized counter name.
+ counter = normalizeCounterName(prefix, counter)
ck := counterKey{counter}
if _, ok := result[wk][pk][gk][ck]; !ok {
result[wk][pk][gk][ck] = make(map[xKey]int64)
@@ -454,29 +461,38 @@
}
}
-// fixCounter groups counters for version and go version by the major minor
-// version. It also adds a prefix to counters derived from report metadata.
-func fixCounter(prefix, counter string) string {
+// normalizeCounterName normalizes the counter name.
+// More specifically, program version, goos, goarch, and goVersion
+// are not a real counter, but information from the metadata in the report.
+// This function constructs pseudo counter names to handle them
+// like other normal counters in aggregation and chart drawing.
+// To limit the cardinality of version and goVersion, this function
+// uses only major and minor version numbers in the pseudo-counter names.
+// If the counter is a normal counter name, it is returned as is.
+func normalizeCounterName(prefix, counter string) string {
switch prefix {
- case version:
+ case versionCounter:
if counter == "devel" {
return prefix + ":" + counter
}
+ if strings.HasPrefix(counter, "go") {
+ return prefix + ":" + goMajorMinor(counter)
+ }
return prefix + ":" + semver.MajorMinor(counter)
- case goos:
+ case goosCounter:
return prefix + ":" + counter
- case goarch:
+ case goarchCounter:
return prefix + ":" + counter
- case goVersion:
+ case goversionCounter:
return prefix + ":" + goMajorMinor(counter)
}
return counter
}
-// parts gets splits the prefix and bucket parts of a counter name
+// splitCounterName gets splits the prefix and bucket splitCounterName of a counter name
// or a bucket name. For an input with no bucket part prefix and bucket
// are the same.
-func parts(name string) (prefix, bucket string) {
+func splitCounterName(name string) (prefix, bucket string) {
prefix, bucket, found := strings.Cut(name, ":")
if !found {
bucket = prefix
@@ -486,6 +502,8 @@
// goMajorMinor gets the go<Maj>,<Min> version for a given go version.
// For example, go1.20.1 -> go1.20.
+// TODO(hyangah): replace with go/version.Lang (available from go1.22)
+// after our builders stop running go1.21.
func goMajorMinor(v string) string {
v = v[2:]
maj, x, ok := cutInt(v)
@@ -512,7 +530,6 @@
}
return x[:i], x[i:], true
}
-
func fsys(fromOS bool) fs.FS {
var f fs.FS = contentfs.FS
if fromOS {
diff --git a/godev/cmd/worker/main_test.go b/godev/cmd/worker/main_test.go
index 0b64a51..352b7f2 100644
--- a/godev/cmd/worker/main_test.go
+++ b/godev/cmd/worker/main_test.go
@@ -8,6 +8,8 @@
"reflect"
"testing"
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/telemetry/internal/config"
"golang.org/x/telemetry/internal/telemetry"
)
@@ -123,6 +125,40 @@
X: 0.123456789,
Programs: []*telemetry.ProgramReport{
{
+ Program: "cmd/go",
+ Version: "go1.2.3",
+ GoVersion: "go1.2.3",
+ GOOS: "darwin",
+ GOARCH: "arm64",
+ Counters: map[string]int64{
+ "main": 1,
+ },
+ },
+ {
+ Program: "example.com/mod/pkg",
+ Version: "v2.3.4",
+ GoVersion: "go1.2.3",
+ GOOS: "darwin",
+ GOARCH: "arm64",
+ Counters: map[string]int64{
+ "main": 1,
+ "flag:a": 2,
+ "flag:b": 3,
+ },
+ // TODO: add support for stacks
+ Stacks: map[string]int64{
+ "panic": 4,
+ },
+ },
+ },
+ Config: "v0.0.1",
+ },
+ {
+ Week: "2999-01-01",
+ LastWeek: "2998-01-01",
+ X: 0.123456789,
+ Programs: []*telemetry.ProgramReport{
+ {
Program: "example.com/mod/pkg",
Version: "v1.2.3",
GoVersion: "go1.2.3",
@@ -321,9 +357,150 @@
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := partition(dat, tt.args.program, tt.args.name, tt.args.buckets, tt.args.xs); !reflect.DeepEqual(got, tt.want) {
+ if got := partition(dat, tt.args.program, tt.args.name, tt.args.buckets); !reflect.DeepEqual(got, tt.want) {
t.Errorf("histogram() = %v, want %v", got, tt.want)
}
})
}
}
+
+func Test_charts(t *testing.T) {
+ dat := nest(reports)
+ cfg := &config.Config{
+ UploadConfig: &telemetry.UploadConfig{
+ GOOS: []string{"darwin"},
+ GOARCH: []string{"amd64"},
+ GoVersion: []string{"go1.2.3"},
+ SampleRate: 1,
+ Programs: []*telemetry.ProgramConfig{
+ {
+ Name: "cmd/go",
+ Versions: []string{"go1.2.3"},
+ Counters: []telemetry.CounterConfig{{
+ Name: "main",
+ }},
+ },
+ {
+ Name: "cmd/compiler",
+ Versions: []string{"go1.2.3"},
+ Counters: []telemetry.CounterConfig{{
+ Name: "count1",
+ }},
+ },
+ {
+ Name: "example.com/mod/pkg",
+ Versions: []string{"v0.15.0"},
+ Counters: []telemetry.CounterConfig{{
+ Name: "count2",
+ }},
+ },
+ },
+ },
+ }
+ want := &chartdata{
+ DateRange: [2]string{"2999-01-01", "2999-01-01"},
+ Programs: []*program{
+ {
+ ID: "charts:cmd/go",
+ Name: "cmd/go",
+ Charts: []*chart{
+ {
+ ID: "charts:cmd/go:GOOS",
+ Name: "GOOS",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "darwin",
+ Value: 1,
+ }},
+ },
+ {
+ ID: "charts:cmd/go:GOARCH",
+ Name: "GOARCH",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "amd64",
+ Value: 0,
+ }},
+ },
+ {
+ ID: "charts:cmd/go:GoVersion",
+ Name: "GoVersion",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "go1.2",
+ Value: 1,
+ }},
+ },
+ {
+ ID: "charts:cmd/go:main",
+ Name: "main",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "main",
+ Value: 1,
+ }},
+ },
+ },
+ },
+ {
+ ID: "charts:cmd/compiler",
+ Name: "cmd/compiler",
+ },
+ {
+ ID: "charts:example.com/mod/pkg",
+ Name: "example.com/mod/pkg",
+ Charts: []*chart{
+ {
+ ID: "charts:example.com/mod/pkg:Version",
+ Name: "Version",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "v0.15",
+ Value: 0,
+ }},
+ },
+ {
+ ID: "charts:example.com/mod/pkg:GOOS",
+ Name: "GOOS",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "darwin",
+ Value: 0.5,
+ }},
+ },
+ {
+ ID: "charts:example.com/mod/pkg:GOARCH",
+ Name: "GOARCH",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "amd64",
+ Value: 0.5,
+ }},
+ },
+ {
+ ID: "charts:example.com/mod/pkg:GoVersion",
+ Name: "GoVersion",
+ Type: "partition",
+ Data: []*datum{{
+ Week: "2999-01-01",
+ Key: "go1.2",
+ Value: 1,
+ }},
+ },
+ },
+ },
+ },
+ NumReports: 1,
+ }
+ got := charts(cfg, "2999-01-01", dat, []float64{0.12345})
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("charts = %+v\n, (-want +got): %v", got, diff)
+ }
+}
diff --git a/godev/go.mod b/godev/go.mod
index 895a3bc..7b5a3dd 100644
--- a/godev/go.mod
+++ b/godev/go.mod
@@ -1,6 +1,6 @@
module golang.org/x/telemetry/godev
-go 1.20
+go 1.21.0
require (
cloud.google.com/go/cloudtasks v1.12.4
diff --git a/godev/go.sum b/godev/go.sum
index ad83c9c..5d9688b 100644
--- a/godev/go.sum
+++ b/godev/go.sum
@@ -51,6 +51,7 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
diff --git a/internal/content/telemetrygodev/charts.ts b/internal/content/telemetrygodev/charts.ts
index 3fef6b4..0616586 100644
--- a/internal/content/telemetrygodev/charts.ts
+++ b/internal/content/telemetrygodev/charts.ts
@@ -38,6 +38,9 @@
for (const program of Page.Charts.Programs) {
for (const counter of program.Charts) {
+ if (!counter) {
+ continue;
+ }
switch (counter.Type) {
case "partition":
document