123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- 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"`
-
- // 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
- }
|