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