mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 09:52:06 +08:00
374 lines
10 KiB
Go
374 lines
10 KiB
Go
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"`
|
|
ShowColumns bool `json:"showcolumns,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: "", // Default depends on the display format
|
|
}
|
|
}
|
|
|
|
// 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",
|
|
"showcolumns": "showcolumns",
|
|
}
|
|
|
|
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
|
|
}
|