@@ -15,6 +15,7 @@ import (
15
15
"net/http"
16
16
"regexp"
17
17
"sort"
18
+ "strconv"
18
19
"strings"
19
20
"time"
20
21
@@ -105,7 +106,7 @@ func validateFluxString(s string) error {
105
106
var errBenchmarkNotFound = errors .New ("benchmark not found" )
106
107
107
108
// fetchNamedUnitBenchmark queries Influx for a specific name + unit benchmark.
108
- func fetchNamedUnitBenchmark (ctx context.Context , qc api.QueryAPI , name , unit string ) (* BenchmarkJSON , error ) {
109
+ func fetchNamedUnitBenchmark (ctx context.Context , qc api.QueryAPI , start , end time. Time , name , unit string ) (* BenchmarkJSON , error ) {
109
110
if err := validateFluxString (name ); err != nil {
110
111
return nil , fmt .Errorf ("invalid benchmark name: %w" , err )
111
112
}
@@ -115,7 +116,7 @@ func fetchNamedUnitBenchmark(ctx context.Context, qc api.QueryAPI, name, unit st
115
116
116
117
query := fmt .Sprintf (`
117
118
from(bucket: "perf")
118
- |> range(start: -30d )
119
+ |> range(start: %s, stop: %s )
119
120
|> filter(fn: (r) => r["_measurement"] == "benchmark-result")
120
121
|> filter(fn: (r) => r["name"] == "%s")
121
122
|> filter(fn: (r) => r["unit"] == "%s")
@@ -124,7 +125,7 @@ from(bucket: "perf")
124
125
|> filter(fn: (r) => r["goarch"] == "amd64")
125
126
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
126
127
|> yield(name: "last")
127
- ` , name , unit )
128
+ ` , start . Format ( time . RFC3339 ), end . Format ( time . RFC3339 ), name , unit )
128
129
129
130
res , err := qc .Query (ctx , query )
130
131
if err != nil {
@@ -145,7 +146,7 @@ from(bucket: "perf")
145
146
}
146
147
147
148
// fetchDefaultBenchmarks queries Influx for the default benchmark set.
148
- func fetchDefaultBenchmarks (ctx context.Context , qc api.QueryAPI ) ([]* BenchmarkJSON , error ) {
149
+ func fetchDefaultBenchmarks (ctx context.Context , qc api.QueryAPI , start , end time. Time ) ([]* BenchmarkJSON , error ) {
149
150
// Keep benchmarks with the same name grouped together, which is
150
151
// assumed by the JS.
151
152
benchmarks := []struct { name , unit string }{
@@ -205,7 +206,7 @@ func fetchDefaultBenchmarks(ctx context.Context, qc api.QueryAPI) ([]*BenchmarkJ
205
206
206
207
ret := make ([]* BenchmarkJSON , 0 , len (benchmarks ))
207
208
for _ , bench := range benchmarks {
208
- b , err := fetchNamedUnitBenchmark (ctx , qc , bench .name , bench .unit )
209
+ b , err := fetchNamedUnitBenchmark (ctx , qc , start , end , bench .name , bench .unit )
209
210
if err != nil {
210
211
return nil , fmt .Errorf ("error fetching benchmark %s/%s: %w" , bench .name , bench .unit , err )
211
212
}
@@ -217,22 +218,22 @@ func fetchDefaultBenchmarks(ctx context.Context, qc api.QueryAPI) ([]*BenchmarkJ
217
218
218
219
// fetchNamedBenchmark queries Influx for all benchmark results with the passed
219
220
// name (for all units).
220
- func fetchNamedBenchmark (ctx context.Context , qc api.QueryAPI , name string ) ([]* BenchmarkJSON , error ) {
221
+ func fetchNamedBenchmark (ctx context.Context , qc api.QueryAPI , start , end time. Time , name string ) ([]* BenchmarkJSON , error ) {
221
222
if err := validateFluxString (name ); err != nil {
222
223
return nil , fmt .Errorf ("invalid benchmark name: %w" , err )
223
224
}
224
225
225
226
query := fmt .Sprintf (`
226
227
from(bucket: "perf")
227
- |> range(start: -30d )
228
+ |> range(start: %s, stop: %s )
228
229
|> filter(fn: (r) => r["_measurement"] == "benchmark-result")
229
230
|> filter(fn: (r) => r["name"] == "%s")
230
231
|> filter(fn: (r) => r["branch"] == "master")
231
232
|> filter(fn: (r) => r["goos"] == "linux")
232
233
|> filter(fn: (r) => r["goarch"] == "amd64")
233
234
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
234
235
|> yield(name: "last")
235
- ` , name )
236
+ ` , start . Format ( time . RFC3339 ), end . Format ( time . RFC3339 ), name )
236
237
237
238
res , err := qc .Query (ctx , query )
238
239
if err != nil {
@@ -250,17 +251,17 @@ from(bucket: "perf")
250
251
}
251
252
252
253
// fetchAllBenchmarks queries Influx for all benchmark results.
253
- func fetchAllBenchmarks (ctx context.Context , qc api.QueryAPI ) ([]* BenchmarkJSON , error ) {
254
- const query = `
254
+ func fetchAllBenchmarks (ctx context.Context , qc api.QueryAPI , start , end time. Time ) ([]* BenchmarkJSON , error ) {
255
+ query := fmt . Sprintf ( `
255
256
from(bucket: "perf")
256
- |> range(start: -30d )
257
+ |> range(start: %s, stop: %s )
257
258
|> filter(fn: (r) => r["_measurement"] == "benchmark-result")
258
259
|> filter(fn: (r) => r["branch"] == "master")
259
260
|> filter(fn: (r) => r["goos"] == "linux")
260
261
|> filter(fn: (r) => r["goarch"] == "amd64")
261
262
|> pivot(columnKey: ["_field"], rowKey: ["_time"], valueColumn: "_value")
262
263
|> yield(name: "last")
263
- `
264
+ ` , start . Format ( time . RFC3339 ), end . Format ( time . RFC3339 ))
264
265
265
266
res , err := qc .Query (ctx , query )
266
267
if err != nil {
@@ -340,16 +341,56 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) {
340
341
return w .w .Write (b )
341
342
}
342
343
344
+ const (
345
+ defaultDays = 30
346
+ maxDays = 366
347
+ )
348
+
343
349
// search handles /dashboard/data.json.
344
350
//
345
351
// TODO(prattmic): Consider caching Influx results in-memory for a few mintures
346
352
// to reduce load on Influx.
347
353
func (a * App ) dashboardData (w http.ResponseWriter , r * http.Request ) {
348
354
ctx := r .Context ()
349
355
350
- start := time .Now ()
356
+ days := uint64 (defaultDays )
357
+ dayParam := r .FormValue ("days" )
358
+ if dayParam != "" {
359
+ var err error
360
+ days , err = strconv .ParseUint (dayParam , 10 , 32 )
361
+ if err != nil {
362
+ log .Printf ("Error parsing days %q: %v" , dayParam , err )
363
+ http .Error (w , fmt .Sprintf ("day parameter must be a positive integer less than or equal to %d" , maxDays ), http .StatusBadRequest )
364
+ return
365
+ }
366
+ if days == 0 || days > maxDays {
367
+ log .Printf ("days %d too large" , days )
368
+ http .Error (w , fmt .Sprintf ("day parameter must be a positive integer less than or equal to %d" , maxDays ), http .StatusBadRequest )
369
+ return
370
+ }
371
+ }
372
+
373
+ end := time .Now ()
374
+ endParam := r .FormValue ("end" )
375
+ if endParam != "" {
376
+ var err error
377
+ // Quirk: Browsers don't have an easy built-in way to deal with
378
+ // timezone in input boxes. The datetime input type yields a
379
+ // string in this form, with no timezone (either local or UTC).
380
+ // Thus, we just treat this as UTC.
381
+ end , err = time .Parse ("2006-01-02T15:04" , endParam )
382
+ if err != nil {
383
+ log .Printf ("Error parsing end %q: %v" , endParam , err )
384
+ http .Error (w , "end parameter must be a timestamp similar to RFC3339 without a time zone, like 2000-12-31T15:00" , http .StatusBadRequest )
385
+ return
386
+ }
387
+ }
388
+
389
+ start := end .Add (- 24 * time .Hour * time .Duration (days ))
390
+
391
+ methStart := time .Now ()
351
392
defer func () {
352
- log .Printf ("Dashboard total query time: %s" , time .Since (start ))
393
+ log .Printf ("Dashboard total query time: %s" , time .Since (methStart ))
353
394
}()
354
395
355
396
ifxc , err := a .influxClient (ctx )
@@ -365,11 +406,11 @@ func (a *App) dashboardData(w http.ResponseWriter, r *http.Request) {
365
406
benchmark := r .FormValue ("benchmark" )
366
407
var benchmarks []* BenchmarkJSON
367
408
if benchmark == "" {
368
- benchmarks , err = fetchDefaultBenchmarks (ctx , qc )
409
+ benchmarks , err = fetchDefaultBenchmarks (ctx , qc , start , end )
369
410
} else if benchmark == "all" {
370
- benchmarks , err = fetchAllBenchmarks (ctx , qc )
411
+ benchmarks , err = fetchAllBenchmarks (ctx , qc , start , end )
371
412
} else {
372
- benchmarks , err = fetchNamedBenchmark (ctx , qc , benchmark )
413
+ benchmarks , err = fetchNamedBenchmark (ctx , qc , start , end , benchmark )
373
414
}
374
415
if err == errBenchmarkNotFound {
375
416
log .Printf ("Benchmark not found: %q" , benchmark )
0 commit comments