| // 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. |
| |
| // csv dumps all the active counters. The output is |
| // a sequence of lines |
| // value,"counter-name",program, version,go-version,goos, garch |
| // sorted by counter name. It looks at the files in |
| // telemetry.LocalDir that are counter files or local reports |
| // By design it pays no attention to dates. The combination |
| // of program version and go version are deemed sufficient. |
| package csv |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "log" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| "golang.org/x/telemetry/internal/counter" |
| "golang.org/x/telemetry/internal/telemetry" |
| ) |
| |
| type file struct { |
| path, name string |
| // one of counters or report is set |
| counters *counter.File |
| report *telemetry.Report |
| } |
| |
| func Csv() { |
| files, err := readdir(telemetry.LocalDir, nil) |
| if err != nil { |
| log.Fatal(err) |
| } |
| for _, f := range files { |
| if strings.HasSuffix(f.name, "v1.count") { |
| buf, err := os.ReadFile(f.path) |
| if err != nil { |
| log.Print(err) |
| continue |
| } |
| cf, err := counter.Parse(f.name, buf) |
| if err != nil { |
| log.Print(err) |
| continue |
| } |
| f.counters = cf |
| } else if strings.HasSuffix(f.name, ".json") { |
| buf, err := os.ReadFile(f.path) |
| if err != nil { |
| log.Print(err) |
| continue |
| } |
| var x telemetry.Report |
| if err := json.Unmarshal(buf, &x); err != nil { |
| log.Print(err) |
| continue |
| } |
| f.report = &x |
| } |
| } |
| printTable(files) |
| } |
| |
| type record struct { |
| goos, garch, program, version, goversion string |
| cntr string |
| count int |
| } |
| |
| func printTable(files []*file) { |
| lines := make(map[string]*record) |
| work := func(k string, v int64, rec *record) { |
| x, ok := lines[k] |
| if !ok { |
| x = new(record) |
| *x = *rec |
| x.cntr = k |
| } |
| x.count += int(v) |
| lines[k] = x |
| } |
| worku := func(k string, v uint64, rec *record) { |
| work(k, int64(v), rec) |
| } |
| for _, f := range files { |
| if f.counters != nil { |
| var rec record |
| rec.goos = f.counters.Meta["GOOS"] |
| rec.garch = f.counters.Meta["GOARCH"] |
| rec.program = f.counters.Meta["Program"] |
| rec.version = f.counters.Meta["Version"] |
| rec.goversion = f.counters.Meta["GoVersion"] |
| for k, v := range f.counters.Count { |
| worku(k, v, &rec) |
| } |
| } else if f.report != nil { |
| for _, p := range f.report.Programs { |
| var rec record |
| rec.goos = p.GOOS |
| rec.garch = p.GOARCH |
| rec.goversion = p.GoVersion |
| rec.program = p.Program |
| rec.version = p.Version |
| for k, v := range p.Counters { |
| work(k, v, &rec) |
| } |
| for k, v := range p.Stacks { |
| work(k, v, &rec) |
| } |
| } |
| } |
| } |
| keys := make([]string, 0, len(lines)) |
| for k := range lines { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| for _, k := range keys { |
| printRecord(lines[k]) |
| } |
| } |
| |
| func printRecord(r *record) { |
| fmt.Printf("%d,%q,%s,%s,%s,%s,%s\n", r.count, r.cntr, r.program, |
| r.version, r.goversion, r.goos, r.garch) |
| } |
| |
| func readdir(dir string, files []*file) ([]*file, error) { |
| fi, err := os.ReadDir(dir) |
| if err != nil { |
| return nil, err |
| } |
| for _, f := range fi { |
| files = append(files, &file{path: filepath.Join(dir, f.Name()), name: f.Name()}) |
| } |
| return files, nil |
| } |