| package driver |
| |
| import ( |
| "fmt" |
| "net/url" |
| "reflect" |
| "strconv" |
| "strings" |
| "sync" |
| ) |
| |
| // config holds settings for a single named config. |
| // The JSON tag name for a field is used both for JSON encoding and as |
| // a named variable. |
| type config struct { |
| // Filename for file-based output formats, stdout by default. |
| Output string `json:"-"` |
| |
| // Display options. |
| CallTree bool `json:"call_tree,omitempty"` |
| RelativePercentages bool `json:"relative_percentages,omitempty"` |
| Unit string `json:"unit,omitempty"` |
| CompactLabels bool `json:"compact_labels,omitempty"` |
| SourcePath string `json:"-"` |
| TrimPath string `json:"-"` |
| IntelSyntax bool `json:"intel_syntax,omitempty"` |
| Mean bool `json:"mean,omitempty"` |
| SampleIndex string `json:"-"` |
| DivideBy float64 `json:"-"` |
| Normalize bool `json:"normalize,omitempty"` |
| Sort string `json:"sort,omitempty"` |
| |
| // Label pseudo stack frame generation options |
| TagRoot string `json:"tagroot,omitempty"` |
| TagLeaf string `json:"tagleaf,omitempty"` |
| |
| // Filtering options |
| DropNegative bool `json:"drop_negative,omitempty"` |
| NodeCount int `json:"nodecount,omitempty"` |
| NodeFraction float64 `json:"nodefraction,omitempty"` |
| EdgeFraction float64 `json:"edgefraction,omitempty"` |
| Trim bool `json:"trim,omitempty"` |
| Focus string `json:"focus,omitempty"` |
| Ignore string `json:"ignore,omitempty"` |
| PruneFrom string `json:"prune_from,omitempty"` |
| Hide string `json:"hide,omitempty"` |
| Show string `json:"show,omitempty"` |
| ShowFrom string `json:"show_from,omitempty"` |
| TagFocus string `json:"tagfocus,omitempty"` |
| TagIgnore string `json:"tagignore,omitempty"` |
| TagShow string `json:"tagshow,omitempty"` |
| TagHide string `json:"taghide,omitempty"` |
| NoInlines bool `json:"noinlines,omitempty"` |
| |
| // Output granularity |
| Granularity string `json:"granularity,omitempty"` |
| } |
| |
| // defaultConfig returns the default configuration values; it is unaffected by |
| // flags and interactive assignments. |
| func defaultConfig() config { |
| return config{ |
| Unit: "minimum", |
| NodeCount: -1, |
| NodeFraction: 0.005, |
| EdgeFraction: 0.001, |
| Trim: true, |
| DivideBy: 1.0, |
| Sort: "flat", |
| Granularity: "functions", |
| } |
| } |
| |
| // currentConfig holds the current configuration values; it is affected by |
| // flags and interactive assignments. |
| var currentCfg = defaultConfig() |
| var currentMu sync.Mutex |
| |
| func currentConfig() config { |
| currentMu.Lock() |
| defer currentMu.Unlock() |
| return currentCfg |
| } |
| |
| func setCurrentConfig(cfg config) { |
| currentMu.Lock() |
| defer currentMu.Unlock() |
| currentCfg = cfg |
| } |
| |
| // configField contains metadata for a single configuration field. |
| type configField struct { |
| name string // JSON field name/key in variables |
| urlparam string // URL parameter name |
| saved bool // Is field saved in settings? |
| field reflect.StructField // Field in config |
| choices []string // Name Of variables in group |
| defaultValue string // Default value for this field. |
| } |
| |
| var ( |
| configFields []configField // Precomputed metadata per config field |
| |
| // configFieldMap holds an entry for every config field as well as an |
| // entry for every valid choice for a multi-choice field. |
| configFieldMap map[string]configField |
| ) |
| |
| func init() { |
| // Config names for fields that are not saved in settings and therefore |
| // do not have a JSON name. |
| notSaved := map[string]string{ |
| // Not saved in settings, but present in URLs. |
| "SampleIndex": "sample_index", |
| |
| // Following fields are also not placed in URLs. |
| "Output": "output", |
| "SourcePath": "source_path", |
| "TrimPath": "trim_path", |
| "DivideBy": "divide_by", |
| } |
| |
| // choices holds the list of allowed values for config fields that can |
| // take on one of a bounded set of values. |
| choices := map[string][]string{ |
| "sort": {"cum", "flat"}, |
| "granularity": {"functions", "filefunctions", "files", "lines", "addresses"}, |
| } |
| |
| // urlparam holds the mapping from a config field name to the URL |
| // parameter used to hold that config field. If no entry is present for |
| // a name, the corresponding field is not saved in URLs. |
| urlparam := map[string]string{ |
| "drop_negative": "dropneg", |
| "call_tree": "calltree", |
| "relative_percentages": "rel", |
| "unit": "unit", |
| "compact_labels": "compact", |
| "intel_syntax": "intel", |
| "nodecount": "n", |
| "nodefraction": "nf", |
| "edgefraction": "ef", |
| "trim": "trim", |
| "focus": "f", |
| "ignore": "i", |
| "prune_from": "prunefrom", |
| "hide": "h", |
| "show": "s", |
| "show_from": "sf", |
| "tagfocus": "tf", |
| "tagignore": "ti", |
| "tagshow": "ts", |
| "taghide": "th", |
| "mean": "mean", |
| "sample_index": "si", |
| "normalize": "norm", |
| "sort": "sort", |
| "granularity": "g", |
| "noinlines": "noinlines", |
| } |
| |
| def := defaultConfig() |
| configFieldMap = map[string]configField{} |
| t := reflect.TypeOf(config{}) |
| for i, n := 0, t.NumField(); i < n; i++ { |
| field := t.Field(i) |
| js := strings.Split(field.Tag.Get("json"), ",") |
| if len(js) == 0 { |
| continue |
| } |
| // Get the configuration name for this field. |
| name := js[0] |
| if name == "-" { |
| name = notSaved[field.Name] |
| if name == "" { |
| // Not a configurable field. |
| continue |
| } |
| } |
| f := configField{ |
| name: name, |
| urlparam: urlparam[name], |
| saved: (name == js[0]), |
| field: field, |
| choices: choices[name], |
| } |
| f.defaultValue = def.get(f) |
| configFields = append(configFields, f) |
| configFieldMap[f.name] = f |
| for _, choice := range f.choices { |
| configFieldMap[choice] = f |
| } |
| } |
| } |
| |
| // fieldPtr returns a pointer to the field identified by f in *cfg. |
| func (cfg *config) fieldPtr(f configField) interface{} { |
| // reflect.ValueOf: converts to reflect.Value |
| // Elem: dereferences cfg to make *cfg |
| // FieldByIndex: fetches the field |
| // Addr: takes address of field |
| // Interface: converts back from reflect.Value to a regular value |
| return reflect.ValueOf(cfg).Elem().FieldByIndex(f.field.Index).Addr().Interface() |
| } |
| |
| // get returns the value of field f in cfg. |
| func (cfg *config) get(f configField) string { |
| switch ptr := cfg.fieldPtr(f).(type) { |
| case *string: |
| return *ptr |
| case *int: |
| return fmt.Sprint(*ptr) |
| case *float64: |
| return fmt.Sprint(*ptr) |
| case *bool: |
| return fmt.Sprint(*ptr) |
| } |
| panic(fmt.Sprintf("unsupported config field type %v", f.field.Type)) |
| } |
| |
| // set sets the value of field f in cfg to value. |
| func (cfg *config) set(f configField, value string) error { |
| switch ptr := cfg.fieldPtr(f).(type) { |
| case *string: |
| if len(f.choices) > 0 { |
| // Verify that value is one of the allowed choices. |
| for _, choice := range f.choices { |
| if choice == value { |
| *ptr = value |
| return nil |
| } |
| } |
| return fmt.Errorf("invalid %q value %q", f.name, value) |
| } |
| *ptr = value |
| case *int: |
| v, err := strconv.Atoi(value) |
| if err != nil { |
| return err |
| } |
| *ptr = v |
| case *float64: |
| v, err := strconv.ParseFloat(value, 64) |
| if err != nil { |
| return err |
| } |
| *ptr = v |
| case *bool: |
| v, err := stringToBool(value) |
| if err != nil { |
| return err |
| } |
| *ptr = v |
| default: |
| panic(fmt.Sprintf("unsupported config field type %v", f.field.Type)) |
| } |
| return nil |
| } |
| |
| // isConfigurable returns true if name is either the name of a config field, or |
| // a valid value for a multi-choice config field. |
| func isConfigurable(name string) bool { |
| _, ok := configFieldMap[name] |
| return ok |
| } |
| |
| // isBoolConfig returns true if name is either name of a boolean config field, |
| // or a valid value for a multi-choice config field. |
| func isBoolConfig(name string) bool { |
| f, ok := configFieldMap[name] |
| if !ok { |
| return false |
| } |
| if name != f.name { |
| return true // name must be one possible value for the field |
| } |
| var cfg config |
| _, ok = cfg.fieldPtr(f).(*bool) |
| return ok |
| } |
| |
| // completeConfig returns the list of configurable names starting with prefix. |
| func completeConfig(prefix string) []string { |
| var result []string |
| for v := range configFieldMap { |
| if strings.HasPrefix(v, prefix) { |
| result = append(result, v) |
| } |
| } |
| return result |
| } |
| |
| // configure stores the name=value mapping into the current config, correctly |
| // handling the case when name identifies a particular choice in a field. |
| func configure(name, value string) error { |
| currentMu.Lock() |
| defer currentMu.Unlock() |
| f, ok := configFieldMap[name] |
| if !ok { |
| return fmt.Errorf("unknown config field %q", name) |
| } |
| if f.name == name { |
| return currentCfg.set(f, value) |
| } |
| // name must be one of the choices. If value is true, set field-value |
| // to name. |
| if v, err := strconv.ParseBool(value); v && err == nil { |
| return currentCfg.set(f, name) |
| } |
| return fmt.Errorf("unknown config field %q", name) |
| } |
| |
| // resetTransient sets all transient fields in *cfg to their currently |
| // configured values. |
| func (cfg *config) resetTransient() { |
| current := currentConfig() |
| cfg.Output = current.Output |
| cfg.SourcePath = current.SourcePath |
| cfg.TrimPath = current.TrimPath |
| cfg.DivideBy = current.DivideBy |
| cfg.SampleIndex = current.SampleIndex |
| } |
| |
| // applyURL updates *cfg based on params. |
| func (cfg *config) applyURL(params url.Values) error { |
| for _, f := range configFields { |
| var value string |
| if f.urlparam != "" { |
| value = params.Get(f.urlparam) |
| } |
| if value == "" { |
| continue |
| } |
| if err := cfg.set(f, value); err != nil { |
| return fmt.Errorf("error setting config field %s: %v", f.name, err) |
| } |
| } |
| return nil |
| } |
| |
| // makeURL returns a URL based on initialURL that contains the config contents |
| // as parameters. The second result is true iff a parameter value was changed. |
| func (cfg *config) makeURL(initialURL url.URL) (url.URL, bool) { |
| q := initialURL.Query() |
| changed := false |
| for _, f := range configFields { |
| if f.urlparam == "" || !f.saved { |
| continue |
| } |
| v := cfg.get(f) |
| if v == f.defaultValue { |
| v = "" // URL for of default value is the empty string. |
| } else if f.field.Type.Kind() == reflect.Bool { |
| // Shorten bool values to "f" or "t" |
| v = v[:1] |
| } |
| if q.Get(f.urlparam) == v { |
| continue |
| } |
| changed = true |
| if v == "" { |
| q.Del(f.urlparam) |
| } else { |
| q.Set(f.urlparam, v) |
| } |
| } |
| if changed { |
| initialURL.RawQuery = q.Encode() |
| } |
| return initialURL, changed |
| } |