mirror of
https://github.com/langhuihui/monibuca.git
synced 2026-04-25 03:45:38 +08:00
feat: add pprof
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user