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