1package driver 2 3import ( 4 "fmt" 5 "net/url" 6 "reflect" 7 "strconv" 8 "strings" 9 "sync" 10) 11 12// config holds settings for a single named config. 13// The JSON tag name for a field is used both for JSON encoding and as 14// a named variable. 15type config struct { 16 // Filename for file-based output formats, stdout by default. 17 Output string `json:"-"` 18 19 // Display options. 20 CallTree bool `json:"call_tree,omitempty"` 21 RelativePercentages bool `json:"relative_percentages,omitempty"` 22 Unit string `json:"unit,omitempty"` 23 CompactLabels bool `json:"compact_labels,omitempty"` 24 SourcePath string `json:"-"` 25 TrimPath string `json:"-"` 26 IntelSyntax bool `json:"intel_syntax,omitempty"` 27 Mean bool `json:"mean,omitempty"` 28 SampleIndex string `json:"-"` 29 DivideBy float64 `json:"-"` 30 Normalize bool `json:"normalize,omitempty"` 31 Sort string `json:"sort,omitempty"` 32 33 // Filtering options 34 DropNegative bool `json:"drop_negative,omitempty"` 35 NodeCount int `json:"nodecount,omitempty"` 36 NodeFraction float64 `json:"nodefraction,omitempty"` 37 EdgeFraction float64 `json:"edgefraction,omitempty"` 38 Trim bool `json:"trim,omitempty"` 39 Focus string `json:"focus,omitempty"` 40 Ignore string `json:"ignore,omitempty"` 41 PruneFrom string `json:"prune_from,omitempty"` 42 Hide string `json:"hide,omitempty"` 43 Show string `json:"show,omitempty"` 44 ShowFrom string `json:"show_from,omitempty"` 45 TagFocus string `json:"tagfocus,omitempty"` 46 TagIgnore string `json:"tagignore,omitempty"` 47 TagShow string `json:"tagshow,omitempty"` 48 TagHide string `json:"taghide,omitempty"` 49 NoInlines bool `json:"noinlines,omitempty"` 50 51 // Output granularity 52 Granularity string `json:"granularity,omitempty"` 53} 54 55// defaultConfig returns the default configuration values; it is unaffected by 56// flags and interactive assignments. 57func defaultConfig() config { 58 return config{ 59 Unit: "minimum", 60 NodeCount: -1, 61 NodeFraction: 0.005, 62 EdgeFraction: 0.001, 63 Trim: true, 64 DivideBy: 1.0, 65 Sort: "flat", 66 Granularity: "functions", 67 } 68} 69 70// currentConfig holds the current configuration values; it is affected by 71// flags and interactive assignments. 72var currentCfg = defaultConfig() 73var currentMu sync.Mutex 74 75func currentConfig() config { 76 currentMu.Lock() 77 defer currentMu.Unlock() 78 return currentCfg 79} 80 81func setCurrentConfig(cfg config) { 82 currentMu.Lock() 83 defer currentMu.Unlock() 84 currentCfg = cfg 85} 86 87// configField contains metadata for a single configuration field. 88type configField struct { 89 name string // JSON field name/key in variables 90 urlparam string // URL parameter name 91 saved bool // Is field saved in settings? 92 field reflect.StructField // Field in config 93 choices []string // Name Of variables in group 94 defaultValue string // Default value for this field. 95} 96 97var ( 98 configFields []configField // Precomputed metadata per config field 99 100 // configFieldMap holds an entry for every config field as well as an 101 // entry for every valid choice for a multi-choice field. 102 configFieldMap map[string]configField 103) 104 105func init() { 106 // Config names for fields that are not saved in settings and therefore 107 // do not have a JSON name. 108 notSaved := map[string]string{ 109 // Not saved in settings, but present in URLs. 110 "SampleIndex": "sample_index", 111 112 // Following fields are also not placed in URLs. 113 "Output": "output", 114 "SourcePath": "source_path", 115 "TrimPath": "trim_path", 116 "DivideBy": "divide_by", 117 } 118 119 // choices holds the list of allowed values for config fields that can 120 // take on one of a bounded set of values. 121 choices := map[string][]string{ 122 "sort": {"cum", "flat"}, 123 "granularity": {"functions", "filefunctions", "files", "lines", "addresses"}, 124 } 125 126 // urlparam holds the mapping from a config field name to the URL 127 // parameter used to hold that config field. If no entry is present for 128 // a name, the corresponding field is not saved in URLs. 129 urlparam := map[string]string{ 130 "drop_negative": "dropneg", 131 "call_tree": "calltree", 132 "relative_percentages": "rel", 133 "unit": "unit", 134 "compact_labels": "compact", 135 "intel_syntax": "intel", 136 "nodecount": "n", 137 "nodefraction": "nf", 138 "edgefraction": "ef", 139 "trim": "trim", 140 "focus": "f", 141 "ignore": "i", 142 "prune_from": "prunefrom", 143 "hide": "h", 144 "show": "s", 145 "show_from": "sf", 146 "tagfocus": "tf", 147 "tagignore": "ti", 148 "tagshow": "ts", 149 "taghide": "th", 150 "mean": "mean", 151 "sample_index": "si", 152 "normalize": "norm", 153 "sort": "sort", 154 "granularity": "g", 155 "noinlines": "noinlines", 156 } 157 158 def := defaultConfig() 159 configFieldMap = map[string]configField{} 160 t := reflect.TypeOf(config{}) 161 for i, n := 0, t.NumField(); i < n; i++ { 162 field := t.Field(i) 163 js := strings.Split(field.Tag.Get("json"), ",") 164 if len(js) == 0 { 165 continue 166 } 167 // Get the configuration name for this field. 168 name := js[0] 169 if name == "-" { 170 name = notSaved[field.Name] 171 if name == "" { 172 // Not a configurable field. 173 continue 174 } 175 } 176 f := configField{ 177 name: name, 178 urlparam: urlparam[name], 179 saved: (name == js[0]), 180 field: field, 181 choices: choices[name], 182 } 183 f.defaultValue = def.get(f) 184 configFields = append(configFields, f) 185 configFieldMap[f.name] = f 186 for _, choice := range f.choices { 187 configFieldMap[choice] = f 188 } 189 } 190} 191 192// fieldPtr returns a pointer to the field identified by f in *cfg. 193func (cfg *config) fieldPtr(f configField) interface{} { 194 // reflect.ValueOf: converts to reflect.Value 195 // Elem: dereferences cfg to make *cfg 196 // FieldByIndex: fetches the field 197 // Addr: takes address of field 198 // Interface: converts back from reflect.Value to a regular value 199 return reflect.ValueOf(cfg).Elem().FieldByIndex(f.field.Index).Addr().Interface() 200} 201 202// get returns the value of field f in cfg. 203func (cfg *config) get(f configField) string { 204 switch ptr := cfg.fieldPtr(f).(type) { 205 case *string: 206 return *ptr 207 case *int: 208 return fmt.Sprint(*ptr) 209 case *float64: 210 return fmt.Sprint(*ptr) 211 case *bool: 212 return fmt.Sprint(*ptr) 213 } 214 panic(fmt.Sprintf("unsupported config field type %v", f.field.Type)) 215} 216 217// set sets the value of field f in cfg to value. 218func (cfg *config) set(f configField, value string) error { 219 switch ptr := cfg.fieldPtr(f).(type) { 220 case *string: 221 if len(f.choices) > 0 { 222 // Verify that value is one of the allowed choices. 223 for _, choice := range f.choices { 224 if choice == value { 225 *ptr = value 226 return nil 227 } 228 } 229 return fmt.Errorf("invalid %q value %q", f.name, value) 230 } 231 *ptr = value 232 case *int: 233 v, err := strconv.Atoi(value) 234 if err != nil { 235 return err 236 } 237 *ptr = v 238 case *float64: 239 v, err := strconv.ParseFloat(value, 64) 240 if err != nil { 241 return err 242 } 243 *ptr = v 244 case *bool: 245 v, err := stringToBool(value) 246 if err != nil { 247 return err 248 } 249 *ptr = v 250 default: 251 panic(fmt.Sprintf("unsupported config field type %v", f.field.Type)) 252 } 253 return nil 254} 255 256// isConfigurable returns true if name is either the name of a config field, or 257// a valid value for a multi-choice config field. 258func isConfigurable(name string) bool { 259 _, ok := configFieldMap[name] 260 return ok 261} 262 263// isBoolConfig returns true if name is either name of a boolean config field, 264// or a valid value for a multi-choice config field. 265func isBoolConfig(name string) bool { 266 f, ok := configFieldMap[name] 267 if !ok { 268 return false 269 } 270 if name != f.name { 271 return true // name must be one possible value for the field 272 } 273 var cfg config 274 _, ok = cfg.fieldPtr(f).(*bool) 275 return ok 276} 277 278// completeConfig returns the list of configurable names starting with prefix. 279func completeConfig(prefix string) []string { 280 var result []string 281 for v := range configFieldMap { 282 if strings.HasPrefix(v, prefix) { 283 result = append(result, v) 284 } 285 } 286 return result 287} 288 289// configure stores the name=value mapping into the current config, correctly 290// handling the case when name identifies a particular choice in a field. 291func configure(name, value string) error { 292 currentMu.Lock() 293 defer currentMu.Unlock() 294 f, ok := configFieldMap[name] 295 if !ok { 296 return fmt.Errorf("unknown config field %q", name) 297 } 298 if f.name == name { 299 return currentCfg.set(f, value) 300 } 301 // name must be one of the choices. If value is true, set field-value 302 // to name. 303 if v, err := strconv.ParseBool(value); v && err == nil { 304 return currentCfg.set(f, name) 305 } 306 return fmt.Errorf("unknown config field %q", name) 307} 308 309// resetTransient sets all transient fields in *cfg to their currently 310// configured values. 311func (cfg *config) resetTransient() { 312 current := currentConfig() 313 cfg.Output = current.Output 314 cfg.SourcePath = current.SourcePath 315 cfg.TrimPath = current.TrimPath 316 cfg.DivideBy = current.DivideBy 317 cfg.SampleIndex = current.SampleIndex 318} 319 320// applyURL updates *cfg based on params. 321func (cfg *config) applyURL(params url.Values) error { 322 for _, f := range configFields { 323 var value string 324 if f.urlparam != "" { 325 value = params.Get(f.urlparam) 326 } 327 if value == "" { 328 continue 329 } 330 if err := cfg.set(f, value); err != nil { 331 return fmt.Errorf("error setting config field %s: %v", f.name, err) 332 } 333 } 334 return nil 335} 336 337// makeURL returns a URL based on initialURL that contains the config contents 338// as parameters. The second result is true iff a parameter value was changed. 339func (cfg *config) makeURL(initialURL url.URL) (url.URL, bool) { 340 q := initialURL.Query() 341 changed := false 342 for _, f := range configFields { 343 if f.urlparam == "" || !f.saved { 344 continue 345 } 346 v := cfg.get(f) 347 if v == f.defaultValue { 348 v = "" // URL for of default value is the empty string. 349 } else if f.field.Type.Kind() == reflect.Bool { 350 // Shorten bool values to "f" or "t" 351 v = v[:1] 352 } 353 if q.Get(f.urlparam) == v { 354 continue 355 } 356 changed = true 357 if v == "" { 358 q.Del(f.urlparam) 359 } else { 360 q.Set(f.urlparam, v) 361 } 362 } 363 if changed { 364 initialURL.RawQuery = q.Encode() 365 } 366 return initialURL, changed 367} 368