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