Skip to content

Commit e15f502

Browse files
authored
backend: improve ParseIntervalStringToTimeDuration method performance in gtime package by ~93% (#1237)
* introduce benchmark test PureNumber-10 1674 ns/op 2377 B/op 35 allocs/op Seconds-10 1646 ns/op 2369 B/op 34 allocs/op Minutes-10 1635 ns/op 2369 B/op 34 allocs/op Hours-10 1633 ns/op 2369 B/op 34 allocs/op Days-10 1719 ns/op 2513 B/op 36 allocs/op Weeks-10 1713 ns/op 2513 B/op 36 allocs/op Months-10 1710 ns/op 2513 B/op 36 allocs/op Years-10 1727 ns/op 2513 B/op 36 allocs/op Complex-10 1660 ns/op 2369 B/op 34 allocs/op WithBrackets-10 1723 ns/op 2385 B/op 36 allocs/op * improve performance by ~89% with precompiled regex * improve sec/op by ~25% more. B/op and alloc/op stayed same. * less allocation for bracket duration and ~8% faster execution * 0 allocation for pure number case * rename and slight cpu performance gain
1 parent e5c676b commit e15f502

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
lines changed

backend/gtime/gtime.go

+28-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"regexp"
66
"strconv"
7-
"strings"
87
"time"
98

109
"github.com/grafana/grafana-plugin-sdk-go/backend"
@@ -154,15 +153,37 @@ func GetIntervalFrom(dsInterval, queryInterval string, queryIntervalMS int64, de
154153
// ParseIntervalStringToTimeDuration converts a string representation of a expected (i.e. 1m30s) to time.Duration
155154
// this method copied from grafana/grafana/pkg/tsdb/intervalv2.go
156155
func ParseIntervalStringToTimeDuration(interval string) (time.Duration, error) {
157-
formattedInterval := strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1)
158-
isPureNum, err := regexp.MatchString(`^\d+$`, formattedInterval)
159-
if err != nil {
160-
return time.Duration(0), err
156+
if len(interval) == 0 {
157+
return 0, backend.DownstreamError(fmt.Errorf("invalid interval"))
158+
}
159+
160+
// extract the interval if it is inside brackets i.e. <10m>
161+
if interval[0] == '<' {
162+
interval = interval[1:]
163+
}
164+
if len(interval) > 0 && interval[len(interval)-1] == '>' {
165+
interval = interval[:len(interval)-1]
161166
}
167+
168+
// Check if string contains only digits
169+
isPureNum := true
170+
for _, c := range interval {
171+
if c < '0' || c > '9' {
172+
isPureNum = false
173+
break
174+
}
175+
}
176+
177+
// if it is number than return it immediately
162178
if isPureNum {
163-
formattedInterval += "s"
179+
num, err := strconv.ParseInt(interval, 10, 64)
180+
if err != nil {
181+
return 0, err
182+
}
183+
return time.Duration(num) * time.Second, nil
164184
}
165-
parsedInterval, err := ParseDuration(formattedInterval)
185+
186+
parsedInterval, err := ParseDuration(interval)
166187
if err != nil {
167188
return time.Duration(0), err
168189
}

backend/gtime/gtime_bench_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package gtime
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// go test -benchmem -run=^$ -bench=BenchmarkParseIntervalStringToTimeDuration$ github.
8+
// com/grafana/grafana-plugin-sdk-go/backend/gtime/ -memprofile p_mem.out -count 6 | tee p_mem.txt
9+
func BenchmarkParseIntervalStringToTimeDuration(b *testing.B) {
10+
testCases := []struct {
11+
name string
12+
interval string
13+
}{
14+
{"PureNumber", "30"},
15+
{"Seconds", "30s"},
16+
{"Minutes", "5m"},
17+
{"Hours", "2h"},
18+
{"Days", "7d"},
19+
{"Weeks", "2w"},
20+
{"Months", "3M"},
21+
{"Years", "1y"},
22+
{"Complex", "1h30m"},
23+
{"WithBrackets", "<30s>"},
24+
}
25+
26+
for _, tc := range testCases {
27+
b.Run(tc.name, func(b *testing.B) {
28+
for i := 0; i < b.N; i++ {
29+
_, err := ParseIntervalStringToTimeDuration(tc.interval)
30+
if err != nil {
31+
b.Fatal(err)
32+
}
33+
}
34+
})
35+
}
36+
}

backend/gtime/gtime_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ func TestParseIntervalStringToTimeDuration(t *testing.T) {
152152
{inp: "<10s>", duration: 10 * time.Second},
153153
{inp: "10s>", duration: 10 * time.Second},
154154
{inp: "<10s", duration: 10 * time.Second},
155+
{inp: "", err: regexp.MustCompile(`invalid interval`)},
155156
}
156157
for i, tc := range tcs {
157158
t.Run(fmt.Sprintf("testcase %d", i), func(t *testing.T) {

0 commit comments

Comments
 (0)