| | package driver |
| |
|
| | import ( |
| | "fmt" |
| | "net/url" |
| | "reflect" |
| | "slices" |
| | "strconv" |
| | "strings" |
| | "sync" |
| | ) |
| |
|
| | |
| | |
| | |
| | type config struct { |
| | |
| | Output string `json:"-"` |
| |
|
| | |
| | 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"` |
| |
|
| | |
| | TagRoot string `json:"tagroot,omitempty"` |
| | TagLeaf string `json:"tagleaf,omitempty"` |
| |
|
| | |
| | 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"` |
| | ShowColumns bool `json:"showcolumns,omitempty"` |
| |
|
| | |
| | Granularity string `json:"granularity,omitempty"` |
| | } |
| |
|
| | |
| | |
| | func defaultConfig() config { |
| | return config{ |
| | Unit: "minimum", |
| | NodeCount: -1, |
| | NodeFraction: 0.005, |
| | EdgeFraction: 0.001, |
| | Trim: true, |
| | DivideBy: 1.0, |
| | Sort: "flat", |
| | Granularity: "", |
| | } |
| | } |
| |
|
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | type configField struct { |
| | name string |
| | urlparam string |
| | saved bool |
| | field reflect.StructField |
| | choices []string |
| | defaultValue string |
| | } |
| |
|
| | var ( |
| | configFields []configField |
| |
|
| | |
| | |
| | configFieldMap map[string]configField |
| | ) |
| |
|
| | func init() { |
| | |
| | |
| | notSaved := map[string]string{ |
| | |
| | "SampleIndex": "sample_index", |
| |
|
| | |
| | "Output": "output", |
| | "SourcePath": "source_path", |
| | "TrimPath": "trim_path", |
| | "DivideBy": "divide_by", |
| | } |
| |
|
| | |
| | |
| | choices := map[string][]string{ |
| | "sort": {"cum", "flat"}, |
| | "granularity": {"functions", "filefunctions", "files", "lines", "addresses"}, |
| | } |
| |
|
| | |
| | |
| | |
| | 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", |
| | "showcolumns": "showcolumns", |
| | } |
| |
|
| | def := defaultConfig() |
| | configFieldMap = map[string]configField{} |
| | t := reflect.TypeFor[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 |
| | } |
| | |
| | name := js[0] |
| | if name == "-" { |
| | name = notSaved[field.Name] |
| | if name == "" { |
| | |
| | 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 |
| | } |
| | } |
| | } |
| |
|
| | |
| | func (cfg *config) fieldPtr(f configField) interface{} { |
| | |
| | |
| | |
| | |
| | |
| | return reflect.ValueOf(cfg).Elem().FieldByIndex(f.field.Index).Addr().Interface() |
| | } |
| |
|
| | |
| | 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)) |
| | } |
| |
|
| | |
| | func (cfg *config) set(f configField, value string) error { |
| | switch ptr := cfg.fieldPtr(f).(type) { |
| | case *string: |
| | if len(f.choices) > 0 { |
| | |
| | if slices.Contains(f.choices, 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 |
| | } |
| |
|
| | |
| | |
| | func isConfigurable(name string) bool { |
| | _, ok := configFieldMap[name] |
| | return ok |
| | } |
| |
|
| | |
| | |
| | func isBoolConfig(name string) bool { |
| | f, ok := configFieldMap[name] |
| | if !ok { |
| | return false |
| | } |
| | if name != f.name { |
| | return true |
| | } |
| | var cfg config |
| | _, ok = cfg.fieldPtr(f).(*bool) |
| | return ok |
| | } |
| |
|
| | |
| | func completeConfig(prefix string) []string { |
| | var result []string |
| | for v := range configFieldMap { |
| | if strings.HasPrefix(v, prefix) { |
| | result = append(result, v) |
| | } |
| | } |
| | return result |
| | } |
| |
|
| | |
| | |
| | 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) |
| | } |
| | |
| | |
| | if v, err := strconv.ParseBool(value); v && err == nil { |
| | return currentCfg.set(f, name) |
| | } |
| | return fmt.Errorf("unknown config field %q", name) |
| | } |
| |
|
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | 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 = "" |
| | } else if f.field.Type.Kind() == reflect.Bool { |
| | |
| | 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 |
| | } |
| |
|