2022-05-13 13:28:54 -05:00
|
|
|
package querydata_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-08-10 08:37:51 -05:00
|
|
|
"io"
|
2022-05-13 13:28:54 -05:00
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
2022-10-04 07:40:01 -05:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2022-05-13 13:28:54 -05:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
|
|
"github.com/stretchr/testify/require"
|
2023-01-30 02:38:51 -06:00
|
|
|
|
2024-03-11 11:22:33 -05:00
|
|
|
"github.com/grafana/grafana/pkg/promlib/models"
|
2022-05-13 13:28:54 -05:00
|
|
|
)
|
|
|
|
|
2022-10-04 07:40:01 -05:00
|
|
|
// when memory-profiling this benchmark, these commands are recommended:
|
2024-03-11 11:22:33 -05:00
|
|
|
// - go test -benchmem -run=^$ -bench ^BenchmarkExemplarJson$ github.com/grafana/grafana/pkg/promlib/querydata -memprofile memprofile.out -count 6 | tee old.txt
|
2022-10-04 07:40:01 -05:00
|
|
|
// - go tool pprof -http=localhost:6061 memprofile.out
|
|
|
|
func BenchmarkExemplarJson(b *testing.B) {
|
|
|
|
queryFileName := filepath.Join("../testdata", "exemplar.query.json")
|
|
|
|
query, err := loadStoredQuery(queryFileName)
|
|
|
|
require.NoError(b, err)
|
|
|
|
|
|
|
|
responseFileName := filepath.Join("../testdata", "exemplar.result.json")
|
|
|
|
|
|
|
|
// nolint:gosec
|
|
|
|
// We can ignore the gosec G304 warning since this is a test file
|
|
|
|
responseBytes, err := os.ReadFile(responseFileName)
|
|
|
|
require.NoError(b, err)
|
|
|
|
|
2023-08-24 09:47:19 -05:00
|
|
|
tCtx, err := setup()
|
2022-10-04 07:40:01 -05:00
|
|
|
require.NoError(b, err)
|
|
|
|
b.ResetTimer()
|
|
|
|
for n := 0; n < b.N; n++ {
|
|
|
|
res := http.Response{
|
|
|
|
StatusCode: 200,
|
|
|
|
Body: io.NopCloser(bytes.NewReader(responseBytes)),
|
|
|
|
}
|
|
|
|
tCtx.httpProvider.setResponse(&res)
|
2023-03-22 04:59:39 -05:00
|
|
|
resp, err := tCtx.queryData.Execute(context.Background(), query)
|
2022-10-04 07:40:01 -05:00
|
|
|
require.NoError(b, err)
|
2023-03-22 04:59:39 -05:00
|
|
|
for _, r := range resp.Responses {
|
|
|
|
require.NoError(b, r.Error)
|
|
|
|
}
|
2022-10-04 07:40:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var resp *backend.QueryDataResponse
|
|
|
|
|
2022-05-13 13:28:54 -05:00
|
|
|
// when memory-profiling this benchmark, these commands are recommended:
|
2024-03-11 11:22:33 -05:00
|
|
|
// - go test -benchmem -run=^$ -bench ^BenchmarkRangeJson$ github.com/grafana/grafana/pkg/promlib/querydata -memprofile memprofile.out -count 6 | tee old.txt
|
2022-05-13 13:28:54 -05:00
|
|
|
// - go tool pprof -http=localhost:6061 memprofile.out
|
2023-08-28 10:47:54 -05:00
|
|
|
// - benchstat old.txt new.txt
|
2022-10-04 07:40:01 -05:00
|
|
|
func BenchmarkRangeJson(b *testing.B) {
|
|
|
|
var (
|
|
|
|
r *backend.QueryDataResponse
|
|
|
|
err error
|
|
|
|
)
|
2022-05-13 13:28:54 -05:00
|
|
|
body, q := createJsonTestData(1642000000, 1, 300, 400)
|
2023-08-24 09:47:19 -05:00
|
|
|
tCtx, err := setup()
|
2022-07-04 04:18:45 -05:00
|
|
|
require.NoError(b, err)
|
|
|
|
|
2022-05-13 13:28:54 -05:00
|
|
|
b.ResetTimer()
|
2022-10-04 07:40:01 -05:00
|
|
|
|
2022-05-13 13:28:54 -05:00
|
|
|
for n := 0; n < b.N; n++ {
|
|
|
|
res := http.Response{
|
|
|
|
StatusCode: 200,
|
2022-08-10 08:37:51 -05:00
|
|
|
Body: io.NopCloser(bytes.NewReader(body)),
|
2022-05-13 13:28:54 -05:00
|
|
|
}
|
|
|
|
tCtx.httpProvider.setResponse(&res)
|
2022-10-04 07:40:01 -05:00
|
|
|
r, err = tCtx.queryData.Execute(context.Background(), q)
|
2022-05-13 13:28:54 -05:00
|
|
|
require.NoError(b, err)
|
|
|
|
}
|
2022-10-04 07:40:01 -05:00
|
|
|
|
|
|
|
resp = r
|
2022-05-13 13:28:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
const nanRate = 0.002
|
|
|
|
|
|
|
|
// we build the JSON file from strings,
|
|
|
|
// it was easier to write it this way.
|
|
|
|
func makeJsonTestMetric(index int) string {
|
|
|
|
return fmt.Sprintf(`{"server":"main","category":"maintenance","case":"%v"}`, index)
|
|
|
|
}
|
|
|
|
|
|
|
|
// return a value between -100 and +100, sometimes NaN, in string
|
|
|
|
func makeJsonTestValue(r *rand.Rand) string {
|
|
|
|
if r.Float64() < nanRate {
|
|
|
|
return "NaN"
|
|
|
|
} else {
|
|
|
|
return fmt.Sprintf("%f", (r.Float64()*200)-100)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create one time-series
|
|
|
|
func makeJsonTestSeries(start int64, step int64, timestampCount int, r *rand.Rand, seriesIndex int) string {
|
|
|
|
var values []string
|
|
|
|
for i := 0; i < timestampCount; i++ {
|
2022-05-25 22:32:55 -05:00
|
|
|
// create out of order timestamps to test sorting
|
|
|
|
if seriesIndex == 0 && i%2 == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2022-05-13 13:28:54 -05:00
|
|
|
value := fmt.Sprintf(`[%d,"%v"]`, start+(int64(i)*step), makeJsonTestValue(r))
|
|
|
|
values = append(values, value)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(`{"metric":%v,"values":[%v]}`, makeJsonTestMetric(seriesIndex), strings.Join(values, ","))
|
|
|
|
}
|
|
|
|
|
|
|
|
func createJsonTestData(start int64, step int64, timestampCount int, seriesCount int) ([]byte, *backend.QueryDataRequest) {
|
|
|
|
// we use random numbers as values, but they have to be the same numbers
|
|
|
|
// every time we call this, so we create a random source.
|
|
|
|
r := rand.New(rand.NewSource(42))
|
|
|
|
var allSeries []string
|
|
|
|
for i := 0; i < seriesCount; i++ {
|
|
|
|
allSeries = append(allSeries, makeJsonTestSeries(start, step, timestampCount, r, i))
|
|
|
|
}
|
|
|
|
bytes := []byte(fmt.Sprintf(`{"status":"success","data":{"resultType":"matrix","result":[%v]}}`, strings.Join(allSeries, ",")))
|
|
|
|
|
|
|
|
qm := models.QueryModel{
|
2024-03-04 12:23:32 -06:00
|
|
|
PrometheusQueryProperties: models.PrometheusQueryProperties{
|
|
|
|
Range: true,
|
2023-03-09 04:26:15 -06:00
|
|
|
Expr: "test",
|
|
|
|
},
|
2022-05-13 13:28:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
data, err := json.Marshal(&qm)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
res := backend.QueryDataRequest{
|
|
|
|
Queries: []backend.DataQuery{
|
|
|
|
{
|
|
|
|
RefID: "A",
|
|
|
|
TimeRange: backend.TimeRange{
|
|
|
|
From: time.Unix(start, 0),
|
|
|
|
To: time.Unix(start+((int64(timestampCount)-1)*step), 0),
|
|
|
|
},
|
|
|
|
Interval: time.Second * time.Duration(step),
|
|
|
|
JSON: data,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return bytes, &res
|
|
|
|
}
|