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