1// Copyright 2023 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// package config provides methods for loading and querying a 6// telemetry upload config file. 7package config 8 9import ( 10 "encoding/json" 11 "os" 12 "strings" 13 14 "golang.org/x/telemetry/internal/telemetry" 15) 16 17// Config is a wrapper around telemetry.UploadConfig that provides some 18// convenience methods for checking the contents of a report. 19type Config struct { 20 *telemetry.UploadConfig 21 program map[string]bool 22 goos map[string]bool 23 goarch map[string]bool 24 goversion map[string]bool 25 pgversion map[pgkey]bool 26 pgcounter map[pgkey]bool 27 pgcounterprefix map[pgkey]bool 28 pgstack map[pgkey]bool 29 rate map[pgkey]float64 30} 31 32type pgkey struct { 33 program, key string 34} 35 36func ReadConfig(file string) (*Config, error) { 37 data, err := os.ReadFile(file) 38 if err != nil { 39 return nil, err 40 } 41 var cfg telemetry.UploadConfig 42 if err := json.Unmarshal(data, &cfg); err != nil { 43 return nil, err 44 } 45 return NewConfig(&cfg), nil 46} 47 48func NewConfig(cfg *telemetry.UploadConfig) *Config { 49 ucfg := Config{UploadConfig: cfg} 50 ucfg.goos = set(ucfg.GOOS) 51 ucfg.goarch = set(ucfg.GOARCH) 52 ucfg.goversion = set(ucfg.GoVersion) 53 ucfg.program = make(map[string]bool, len(ucfg.Programs)) 54 ucfg.pgversion = make(map[pgkey]bool, len(ucfg.Programs)) 55 ucfg.pgcounter = make(map[pgkey]bool, len(ucfg.Programs)) 56 ucfg.pgcounterprefix = make(map[pgkey]bool, len(ucfg.Programs)) 57 ucfg.pgstack = make(map[pgkey]bool, len(ucfg.Programs)) 58 ucfg.rate = make(map[pgkey]float64) 59 for _, p := range ucfg.Programs { 60 ucfg.program[p.Name] = true 61 for _, v := range p.Versions { 62 ucfg.pgversion[pgkey{p.Name, v}] = true 63 } 64 for _, c := range p.Counters { 65 for _, e := range Expand(c.Name) { 66 ucfg.pgcounter[pgkey{p.Name, e}] = true 67 ucfg.rate[pgkey{p.Name, e}] = c.Rate 68 } 69 prefix, _, found := strings.Cut(c.Name, ":") 70 if found { 71 ucfg.pgcounterprefix[pgkey{p.Name, prefix}] = true 72 } 73 } 74 for _, s := range p.Stacks { 75 ucfg.pgstack[pgkey{p.Name, s.Name}] = true 76 ucfg.rate[pgkey{p.Name, s.Name}] = s.Rate 77 } 78 } 79 return &ucfg 80} 81 82func (r *Config) HasProgram(s string) bool { 83 return r.program[s] 84} 85 86func (r *Config) HasGOOS(s string) bool { 87 return r.goos[s] 88} 89 90func (r *Config) HasGOARCH(s string) bool { 91 return r.goarch[s] 92} 93 94func (r *Config) HasGoVersion(s string) bool { 95 return r.goversion[s] 96} 97 98func (r *Config) HasVersion(program, version string) bool { 99 return r.pgversion[pgkey{program, version}] 100} 101 102func (r *Config) HasCounter(program, counter string) bool { 103 return r.pgcounter[pgkey{program, counter}] 104} 105 106func (r *Config) HasCounterPrefix(program, prefix string) bool { 107 return r.pgcounterprefix[pgkey{program, prefix}] 108} 109 110func (r *Config) HasStack(program, stack string) bool { 111 return r.pgstack[pgkey{program, stack}] 112} 113 114func (r *Config) Rate(program, name string) float64 { 115 return r.rate[pgkey{program, name}] 116} 117 118func set(slice []string) map[string]bool { 119 s := make(map[string]bool, len(slice)) 120 for _, v := range slice { 121 s[v] = true 122 } 123 return s 124} 125 126// Expand takes a counter defined with buckets and expands it into distinct 127// strings for each bucket 128func Expand(counter string) []string { 129 prefix, rest, hasBuckets := strings.Cut(counter, "{") 130 var counters []string 131 if hasBuckets { 132 buckets := strings.Split(strings.TrimSuffix(rest, "}"), ",") 133 for _, b := range buckets { 134 counters = append(counters, prefix+b) 135 } 136 } else { 137 counters = append(counters, prefix) 138 } 139 return counters 140} 141