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