| // 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 config provides methods for loading and querying a |
| // telemetry upload config file. |
| package config |
| |
| import ( |
| "encoding/json" |
| "os" |
| "strings" |
| |
| "golang.org/x/telemetry/internal/telemetry" |
| ) |
| |
| // Config is a wrapper around telemetry.UploadConfig that provides some |
| // convenience methods for checking the contents of a report. |
| type Config struct { |
| *telemetry.UploadConfig |
| program map[string]bool |
| goos map[string]bool |
| goarch map[string]bool |
| goversion map[string]bool |
| pgversion map[pgkey]bool |
| pgcounter map[pgkey]bool |
| pgcounterprefix map[pgkey]bool |
| pgstack map[pgkey]bool |
| rate map[pgkey]float64 |
| } |
| |
| type pgkey struct { |
| program, key string |
| } |
| |
| func ReadConfig(file string) (*Config, error) { |
| data, err := os.ReadFile(file) |
| if err != nil { |
| return nil, err |
| } |
| var cfg telemetry.UploadConfig |
| if err := json.Unmarshal(data, &cfg); err != nil { |
| return nil, err |
| } |
| return NewConfig(&cfg), nil |
| } |
| |
| func NewConfig(cfg *telemetry.UploadConfig) *Config { |
| ucfg := Config{UploadConfig: cfg} |
| ucfg.goos = set(ucfg.GOOS) |
| ucfg.goarch = set(ucfg.GOARCH) |
| ucfg.goversion = set(ucfg.GoVersion) |
| ucfg.program = make(map[string]bool, len(ucfg.Programs)) |
| ucfg.pgversion = make(map[pgkey]bool, len(ucfg.Programs)) |
| ucfg.pgcounter = make(map[pgkey]bool, len(ucfg.Programs)) |
| ucfg.pgcounterprefix = make(map[pgkey]bool, len(ucfg.Programs)) |
| ucfg.pgstack = make(map[pgkey]bool, len(ucfg.Programs)) |
| ucfg.rate = make(map[pgkey]float64) |
| for _, p := range ucfg.Programs { |
| ucfg.program[p.Name] = true |
| for _, v := range p.Versions { |
| ucfg.pgversion[pgkey{p.Name, v}] = true |
| } |
| for _, c := range p.Counters { |
| for _, e := range Expand(c.Name) { |
| ucfg.pgcounter[pgkey{p.Name, e}] = true |
| ucfg.rate[pgkey{p.Name, e}] = c.Rate |
| } |
| prefix, _, found := strings.Cut(c.Name, ":") |
| if found { |
| ucfg.pgcounterprefix[pgkey{p.Name, prefix}] = true |
| } |
| } |
| for _, s := range p.Stacks { |
| ucfg.pgstack[pgkey{p.Name, s.Name}] = true |
| ucfg.rate[pgkey{p.Name, s.Name}] = s.Rate |
| } |
| } |
| return &ucfg |
| } |
| |
| func (r *Config) HasProgram(s string) bool { |
| return r.program[s] |
| } |
| |
| func (r *Config) HasGOOS(s string) bool { |
| return r.goos[s] |
| } |
| |
| func (r *Config) HasGOARCH(s string) bool { |
| return r.goarch[s] |
| } |
| |
| func (r *Config) HasGoVersion(s string) bool { |
| return r.goversion[s] |
| } |
| |
| func (r *Config) HasVersion(program, version string) bool { |
| return r.pgversion[pgkey{program, version}] |
| } |
| |
| func (r *Config) HasCounter(program, counter string) bool { |
| return r.pgcounter[pgkey{program, counter}] |
| } |
| |
| func (r *Config) HasCounterPrefix(program, prefix string) bool { |
| return r.pgcounterprefix[pgkey{program, prefix}] |
| } |
| |
| func (r *Config) HasStack(program, stack string) bool { |
| return r.pgstack[pgkey{program, stack}] |
| } |
| |
| func (r *Config) Rate(program, name string) float64 { |
| return r.rate[pgkey{program, name}] |
| } |
| |
| func set(slice []string) map[string]bool { |
| s := make(map[string]bool, len(slice)) |
| for _, v := range slice { |
| s[v] = true |
| } |
| return s |
| } |
| |
| // Expand takes a counter defined with buckets and expands it into distinct |
| // strings for each bucket. |
| func Expand(counter string) []string { |
| prefix, rest, hasBuckets := strings.Cut(counter, "{") |
| var counters []string |
| if hasBuckets { |
| buckets := strings.Split(strings.TrimSuffix(rest, "}"), ",") |
| for _, b := range buckets { |
| counters = append(counters, prefix+b) |
| } |
| } else { |
| counters = append(counters, prefix) |
| } |
| return counters |
| } |