No Description

config.go 10KB

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