Source file src/cmd/vendor/github.com/google/pprof/internal/driver/config.go

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

View as plain text