Prometheus: Implement Streaming JSON Parser (#48477)

use `prometheusStreamingJSONParser` feature toggle to enable
This commit is contained in:
Todd Treece 2022-05-13 14:28:54 -04:00 committed by GitHub
parent e528f2e430
commit 87e8521591
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2877 additions and 34 deletions

View File

@ -61,4 +61,5 @@ export interface FeatureToggles {
datasourceQueryMultiStatus?: boolean;
azureMonitorExperimentalUI?: boolean;
traceToMetrics?: boolean;
prometheusStreamingJSONParser?: boolean;
}

View File

@ -254,5 +254,10 @@ var (
State: FeatureStateAlpha,
FrontendOnly: true,
},
{
Name: "prometheusStreamingJSONParser",
Description: "Enable streaming JSON parser for Prometheus datasource",
State: FeatureStateAlpha,
},
}
)

View File

@ -186,4 +186,8 @@ const (
// FlagTraceToMetrics
// Enable trace to metrics links
FlagTraceToMetrics = "traceToMetrics"
// FlagPrometheusStreamingJSONParser
// Enable streaming JSON parser for Prometheus datasource
FlagPrometheusStreamingJSONParser = "prometheusStreamingJSONParser"
)

View File

@ -28,7 +28,8 @@ func BenchmarkJson(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, _ = s.runQueries(context.Background(), api, []*PrometheusQuery{&query})
_, err := s.runQueries(context.Background(), api, []*PrometheusQuery{&query})
require.NoError(b, err)
}
}

View File

@ -567,6 +567,7 @@ func deviation(values []float64) float64 {
func newDataFrame(name string, typ string, fields ...*data.Field) *data.Frame {
frame := data.NewFrame(name, fields...)
frame.Meta = &data.FrameMeta{
Type: data.FrameTypeTimeSeriesMany,
Custom: map[string]string{
"resultType": typ,
},

View File

@ -0,0 +1,55 @@
package client
import (
"sort"
"strings"
lru "github.com/hashicorp/golang-lru"
)
type ProviderCache struct {
provider promClientProvider
cache *lru.Cache
}
type promClientProvider interface {
GetClient(map[string]string) (*Client, error)
}
func NewProviderCache(p promClientProvider) (*ProviderCache, error) {
cache, err := lru.New(500)
if err != nil {
return nil, err
}
return &ProviderCache{
provider: p,
cache: cache,
}, nil
}
func (c *ProviderCache) GetClient(headers map[string]string) (*Client, error) {
key := c.key(headers)
if client, ok := c.cache.Get(key); ok {
return client.(*Client), nil
}
client, err := c.provider.GetClient(headers)
if err != nil {
return nil, err
}
c.cache.Add(key, client)
return client, nil
}
func (c *ProviderCache) key(headers map[string]string) string {
vals := make([]string, len(headers))
var i int
for _, v := range headers {
vals[i] = v
i++
}
sort.Strings(vals)
return strings.Join(vals, "")
}

View File

@ -0,0 +1,135 @@
package client_test
import (
"errors"
"io/ioutil"
"net/http"
"sort"
"strings"
"testing"
"github.com/grafana/grafana/pkg/tsdb/prometheus/client"
"github.com/stretchr/testify/require"
)
func TestCache_GetClient(t *testing.T) {
t.Run("it caches the client for a set of auth headers", func(t *testing.T) {
tc := setupCacheContext()
c, err := tc.providerCache.GetClient(headers)
require.Nil(t, err)
c2, err := tc.providerCache.GetClient(headers)
require.Nil(t, err)
require.Equal(t, c, c2)
require.Equal(t, 1, tc.clientProvider.numCalls)
})
t.Run("it returns different clients when the headers differ", func(t *testing.T) {
tc := setupCacheContext()
h1 := map[string]string{"Authorization": "token", "X-ID-Token": "id-token"}
h2 := map[string]string{"Authorization": "token2", "X-ID-Token": "id-token"}
c, err := tc.providerCache.GetClient(h1)
require.Nil(t, err)
c2, err := tc.providerCache.GetClient(h2)
require.Nil(t, err)
require.NotEqual(t, c, c2)
require.Equal(t, 2, tc.clientProvider.numCalls)
})
t.Run("it returns from the cache when headers are the same", func(t *testing.T) {
tc := setupCacheContext()
h1 := map[string]string{"Authorization": "token", "X-ID-Token": "id-token"}
h2 := map[string]string{"Authorization": "token", "X-ID-Token": "id-token"}
c, err := tc.providerCache.GetClient(h1)
require.Nil(t, err)
c2, err := tc.providerCache.GetClient(h2)
require.Nil(t, err)
require.Equal(t, c, c2)
require.Equal(t, 1, tc.clientProvider.numCalls)
})
t.Run("it doesn't cache anything when an error occurs", func(t *testing.T) {
tc := setupCacheContext()
tc.clientProvider.errors <- errors.New("something bad")
_, err := tc.providerCache.GetClient(headers)
require.EqualError(t, err, "something bad")
c, err := tc.providerCache.GetClient(headers)
require.Nil(t, err)
require.NotNil(t, c)
require.Equal(t, 2, tc.clientProvider.numCalls)
})
}
type cacheTestContext struct {
providerCache *client.ProviderCache
clientProvider *fakeClientProvider
}
func setupCacheContext() *cacheTestContext {
fp := newFakePromClientProvider()
p, err := client.NewProviderCache(fp)
if err != nil {
panic(err)
}
return &cacheTestContext{
providerCache: p,
clientProvider: fp,
}
}
func newFakePromClientProvider() *fakeClientProvider {
return &fakeClientProvider{
errors: make(chan error, 1),
}
}
type fakeClientProvider struct {
headers map[string]string
numCalls int
errors chan error
}
func (p *fakeClientProvider) GetClient(h map[string]string) (*client.Client, error) {
p.headers = h
p.numCalls++
var err error
select {
case err = <-p.errors:
default:
}
var config []string
for _, v := range h {
config = append(config, v)
}
sort.Strings(config) //because map
res := &http.Response{
StatusCode: 200,
Header: http.Header{},
Body: ioutil.NopCloser(strings.NewReader(strings.Join(config, ","))),
}
c := &fakeClient{res: res}
return client.NewClient(c, "GET", "http://localhost:9090/"), err
}
type fakeClient struct {
res *http.Response
}
func (c *fakeClient) Do(req *http.Request) (*http.Response, error) {
return c.res, nil
}

View File

@ -0,0 +1,103 @@
package client
import (
"context"
"io/ioutil"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
)
type doer interface {
Do(req *http.Request) (*http.Response, error)
}
type Client struct {
doer doer
method string
baseUrl string
}
func NewClient(d doer, method, baseUrl string) *Client {
return &Client{doer: d, method: method, baseUrl: baseUrl}
}
func (c *Client) QueryRange(ctx context.Context, q *models.Query) (*http.Response, error) {
u, err := url.ParseRequestURI(c.baseUrl)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, "api/v1/query_range")
qs := u.Query()
qs.Set("query", q.Expr)
tr := q.TimeRange()
qs.Set("start", formatTime(tr.Start))
qs.Set("end", formatTime(tr.End))
qs.Set("step", strconv.FormatFloat(tr.Step.Seconds(), 'f', -1, 64))
return c.fetch(ctx, u, qs)
}
func (c *Client) QueryInstant(ctx context.Context, q *models.Query) (*http.Response, error) {
u, err := url.ParseRequestURI(c.baseUrl)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, "api/v1/query")
qs := u.Query()
qs.Set("query", q.Expr)
tr := q.TimeRange()
if !tr.End.IsZero() {
qs.Set("time", formatTime(tr.End))
}
return c.fetch(ctx, u, qs)
}
func (c *Client) QueryExemplars(ctx context.Context, q *models.Query) (*http.Response, error) {
u, err := url.ParseRequestURI(c.baseUrl)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, "api/v1/query_exemplars")
qs := u.Query()
tr := q.TimeRange()
qs.Set("query", q.Expr)
qs.Set("start", formatTime(tr.Start))
qs.Set("end", formatTime(tr.End))
return c.fetch(ctx, u, qs)
}
func (c *Client) fetch(ctx context.Context, u *url.URL, qs url.Values) (*http.Response, error) {
if strings.ToUpper(c.method) == http.MethodGet {
u.RawQuery = qs.Encode()
}
r, err := http.NewRequestWithContext(ctx, c.method, u.String(), nil)
if err != nil {
return nil, err
}
if strings.ToUpper(c.method) == http.MethodPost {
r.Body = ioutil.NopCloser(strings.NewReader(qs.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
return c.doer.Do(r)
}
func formatTime(t time.Time) string {
return strconv.FormatFloat(float64(t.Unix())+float64(t.Nanosecond())/1e9, 'f', -1, 64)
}

View File

@ -0,0 +1,88 @@
package client
import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/prometheus/middleware"
"github.com/grafana/grafana/pkg/util/maputil"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/log"
)
type Provider struct {
settings backend.DataSourceInstanceSettings
jsonData map[string]interface{}
httpMethod string
clientProvider httpclient.Provider
cfg *setting.Cfg
features featuremgmt.FeatureToggles
log log.Logger
}
func NewProvider(
settings backend.DataSourceInstanceSettings,
jsonData map[string]interface{},
clientProvider httpclient.Provider,
cfg *setting.Cfg,
features featuremgmt.FeatureToggles,
log log.Logger,
) *Provider {
httpMethod, _ := maputil.GetStringOptional(jsonData, "httpMethod")
return &Provider{
settings: settings,
jsonData: jsonData,
httpMethod: httpMethod,
clientProvider: clientProvider,
cfg: cfg,
features: features,
log: log,
}
}
func (p *Provider) GetClient(headers map[string]string) (*Client, error) {
opts, err := p.settings.HTTPClientOptions()
if err != nil {
return nil, err
}
opts.Middlewares = p.middlewares()
opts.Headers = reqHeaders(headers)
// Set SigV4 service namespace
if opts.SigV4 != nil {
opts.SigV4.Service = "aps"
}
// Azure authentication
err = p.configureAzureAuthentication(&opts)
if err != nil {
return nil, err
}
httpClient, err := p.clientProvider.New(opts)
if err != nil {
return nil, err
}
return NewClient(httpClient, p.httpMethod, p.settings.URL), nil
}
func (p *Provider) middlewares() []sdkhttpclient.Middleware {
middlewares := []sdkhttpclient.Middleware{
middleware.CustomQueryParameters(p.log),
sdkhttpclient.CustomHeadersMiddleware(),
}
return middlewares
}
func reqHeaders(headers map[string]string) map[string]string {
// copy to avoid changing the original map
h := make(map[string]string, len(headers))
for k, v := range headers {
h[k] = v
}
return h
}

View File

@ -0,0 +1,50 @@
package client
import (
"fmt"
"net/url"
"path"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azhttpclient"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/util/maputil"
)
func (p *Provider) configureAzureAuthentication(opts *sdkhttpclient.Options) error {
// Azure authentication is experimental (#35857)
if !p.features.IsEnabled(featuremgmt.FlagPrometheusAzureAuth) {
return nil
}
credentials, err := azcredentials.FromDatasourceData(p.jsonData, p.settings.DecryptedSecureJSONData)
if err != nil {
err = fmt.Errorf("invalid Azure credentials: %s", err)
return err
}
if credentials != nil {
resourceIdStr, err := maputil.GetStringOptional(p.jsonData, "azureEndpointResourceId")
if err != nil {
return err
} else if resourceIdStr == "" {
err := fmt.Errorf("endpoint resource ID (audience) not provided")
return err
}
resourceId, err := url.Parse(resourceIdStr)
if err != nil || resourceId.Scheme == "" || resourceId.Host == "" {
err := fmt.Errorf("endpoint resource ID (audience) '%s' invalid", resourceIdStr)
return err
}
resourceId.Path = path.Join(resourceId.Path, ".default")
scopes := []string{resourceId.String()}
azhttpclient.AddAzureAuthentication(opts, p.cfg.Azure, credentials, scopes)
}
return nil
}

View File

@ -0,0 +1,150 @@
package client
import (
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfigureAzureAuthentication(t *testing.T) {
cfg := &setting.Cfg{}
settings := backend.DataSourceInstanceSettings{}
t.Run("given feature flag enabled", func(t *testing.T) {
features := featuremgmt.WithFeatures(featuremgmt.FlagPrometheusAzureAuth)
t.Run("should set Azure middleware when JsonData contains valid credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
require.NoError(t, err)
require.NotNil(t, opts.Middlewares)
assert.Len(t, opts.Middlewares, 1)
})
t.Run("should not set Azure middleware when JsonData doesn't contain valid credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
require.NoError(t, err)
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should return error when JsonData contains invalid credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": "invalid",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
assert.Error(t, err)
})
t.Run("should set Azure middleware when JsonData contains credentials and valid audience", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
require.NoError(t, err)
require.NotNil(t, opts.Middlewares)
assert.Len(t, opts.Middlewares, 1)
})
t.Run("should not set Azure middleware when JsonData doesn't contain credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
require.NoError(t, err)
if opts.Middlewares != nil {
assert.Len(t, opts.Middlewares, 0)
}
})
t.Run("should return error when JsonData contains invalid audience", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "invalid",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
assert.Error(t, err)
})
})
t.Run("given feature flag not enabled", func(t *testing.T) {
features := featuremgmt.WithFeatures()
t.Run("should not set Azure Credentials even when JsonData contains credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
require.NoError(t, err)
if opts.Middlewares != nil {
assert.Len(t, opts.Middlewares, 0)
}
})
})
}

View File

@ -0,0 +1,125 @@
package client_test
import (
"encoding/json"
"net/http"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/prometheus/client"
"github.com/stretchr/testify/require"
)
var headers = map[string]string{"Authorization": "token", "X-ID-Token": "id-token"}
func TestGetClient(t *testing.T) {
t.Run("it sets the SigV4 service if it exists", func(t *testing.T) {
tc := setup(`{"sigV4Auth":true}`)
setting.SigV4AuthEnabled = true
defer func() { setting.SigV4AuthEnabled = false }()
_, err := tc.clientProvider.GetClient(headers)
require.Nil(t, err)
require.Equal(t, "aps", tc.httpProvider.opts.SigV4.Service)
})
t.Run("it always uses the custom params and custom headers middlewares", func(t *testing.T) {
tc := setup()
_, err := tc.clientProvider.GetClient(headers)
require.Nil(t, err)
require.Len(t, tc.httpProvider.middlewares(), 2)
require.Contains(t, tc.httpProvider.middlewares(), "prom-custom-query-parameters")
require.Contains(t, tc.httpProvider.middlewares(), "CustomHeaders")
})
t.Run("extra headers", func(t *testing.T) {
t.Run("it sets the headers when 'oauthPassThru' is true and auth headers are passed", func(t *testing.T) {
tc := setup(`{"oauthPassThru":true}`)
_, err := tc.clientProvider.GetClient(headers)
require.Nil(t, err)
require.Equal(t, headers, tc.httpProvider.opts.Headers)
})
t.Run("it sets all headers", func(t *testing.T) {
withNonAuth := map[string]string{"X-Not-Auth": "stuff"}
tc := setup(`{"oauthPassThru":true}`)
_, err := tc.clientProvider.GetClient(withNonAuth)
require.Nil(t, err)
require.Equal(t, map[string]string{"X-Not-Auth": "stuff"}, tc.httpProvider.opts.Headers)
})
t.Run("it does not error when headers are nil", func(t *testing.T) {
tc := setup(`{"oauthPassThru":true}`)
_, err := tc.clientProvider.GetClient(nil)
require.Nil(t, err)
})
})
}
func setup(jsonData ...string) *testContext {
var rawData []byte
if len(jsonData) > 0 {
rawData = []byte(jsonData[0])
}
var jd map[string]interface{}
_ = json.Unmarshal(rawData, &jd)
cfg := &setting.Cfg{}
settings := backend.DataSourceInstanceSettings{URL: "test-url", JSONData: rawData}
features := featuremgmt.WithFeatures()
hp := &fakeHttpClientProvider{}
p := client.NewProvider(settings, jd, hp, cfg, features, nil)
return &testContext{
httpProvider: hp,
clientProvider: p,
}
}
type testContext struct {
httpProvider *fakeHttpClientProvider
clientProvider *client.Provider
}
type fakeHttpClientProvider struct {
httpclient.Provider
opts sdkhttpclient.Options
}
func (p *fakeHttpClientProvider) New(opts ...sdkhttpclient.Options) (*http.Client, error) {
p.opts = opts[0]
return sdkhttpclient.New(opts[0])
}
func (p *fakeHttpClientProvider) GetTransport(opts ...sdkhttpclient.Options) (http.RoundTripper, error) {
p.opts = opts[0]
return http.DefaultTransport, nil
}
func (p *fakeHttpClientProvider) middlewares() []string {
var middlewareNames []string
for _, m := range p.opts.Middlewares {
mw, ok := m.(sdkhttpclient.MiddlewareName)
if !ok {
panic("unexpected middleware type")
}
middlewareNames = append(middlewareNames, mw.MiddlewareName())
}
return middlewareNames
}

View File

@ -0,0 +1,230 @@
package models
import (
"encoding/json"
"math"
"strconv"
"strings"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
)
//Internal interval and range variables
const (
varInterval = "$__interval"
varIntervalMs = "$__interval_ms"
varRange = "$__range"
varRangeS = "$__range_s"
varRangeMs = "$__range_ms"
varRateInterval = "$__rate_interval"
)
//Internal interval and range variables with {} syntax
//Repetitive code, we should have functionality to unify these
const (
varIntervalAlt = "${__interval}"
varIntervalMsAlt = "${__interval_ms}"
varRangeAlt = "${__range}"
varRangeSAlt = "${__range_s}"
varRangeMsAlt = "${__range_ms}"
varRateIntervalAlt = "${__rate_interval}"
)
type TimeSeriesQueryType string
const (
RangeQueryType TimeSeriesQueryType = "range"
InstantQueryType TimeSeriesQueryType = "instant"
ExemplarQueryType TimeSeriesQueryType = "exemplar"
UnknownQueryType TimeSeriesQueryType = "unknown"
)
var safeResolution = 11000
type QueryModel struct {
Expr string `json:"expr"`
LegendFormat string `json:"legendFormat"`
Interval string `json:"interval"`
IntervalMS int64 `json:"intervalMS"`
StepMode string `json:"stepMode"`
RangeQuery bool `json:"range"`
InstantQuery bool `json:"instant"`
ExemplarQuery bool `json:"exemplar"`
IntervalFactor int64 `json:"intervalFactor"`
UtcOffsetSec int64 `json:"utcOffsetSec"`
}
type TimeRange struct {
Start time.Time
End time.Time
Step time.Duration
}
type Query struct {
Expr string
Step time.Duration
LegendFormat string
Start time.Time
End time.Time
RefId string
InstantQuery bool
RangeQuery bool
ExemplarQuery bool
UtcOffsetSec int64
}
func Parse(query backend.DataQuery, timeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool) (*Query, error) {
model := &QueryModel{}
if err := json.Unmarshal(query.JSON, model); err != nil {
return nil, err
}
//Final interval value
interval, err := calculatePrometheusInterval(model, timeInterval, query, intervalCalculator)
if err != nil {
return nil, err
}
// Interpolate variables in expr
timeRange := query.TimeRange.To.Sub(query.TimeRange.From)
expr := interpolateVariables(model, interval, timeRange, intervalCalculator, timeInterval)
rangeQuery := model.RangeQuery
if !model.InstantQuery && !model.RangeQuery {
// In older dashboards, we were not setting range query param and !range && !instant was run as range query
rangeQuery = true
}
// We never want to run exemplar query for alerting
exemplarQuery := model.ExemplarQuery
if fromAlert {
exemplarQuery = false
}
return &Query{
Expr: expr,
Step: interval,
LegendFormat: model.LegendFormat,
Start: query.TimeRange.From,
End: query.TimeRange.To,
RefId: query.RefID,
InstantQuery: model.InstantQuery,
RangeQuery: rangeQuery,
ExemplarQuery: exemplarQuery,
UtcOffsetSec: model.UtcOffsetSec,
}, nil
}
func (query *Query) Type() TimeSeriesQueryType {
if query.InstantQuery {
return InstantQueryType
}
if query.RangeQuery {
return RangeQueryType
}
if query.ExemplarQuery {
return ExemplarQueryType
}
return UnknownQueryType
}
func (query *Query) TimeRange() TimeRange {
return TimeRange{
Step: query.Step,
// Align query range to step. It rounds start and end down to a multiple of step.
Start: alignTimeRange(query.Start, query.Step, query.UtcOffsetSec),
End: alignTimeRange(query.End, query.Step, query.UtcOffsetSec),
}
}
func calculatePrometheusInterval(model *QueryModel, timeInterval string, query backend.DataQuery, intervalCalculator intervalv2.Calculator) (time.Duration, error) {
queryInterval := model.Interval
//If we are using variable for interval/step, we will replace it with calculated interval
if isVariableInterval(queryInterval) {
queryInterval = ""
}
minInterval, err := intervalv2.GetIntervalFrom(timeInterval, queryInterval, model.IntervalMS, 15*time.Second)
if err != nil {
return time.Duration(0), err
}
calculatedInterval := intervalCalculator.Calculate(query.TimeRange, minInterval, query.MaxDataPoints)
safeInterval := intervalCalculator.CalculateSafeInterval(query.TimeRange, int64(safeResolution))
adjustedInterval := safeInterval.Value
if calculatedInterval.Value > safeInterval.Value {
adjustedInterval = calculatedInterval.Value
}
if model.Interval == varRateInterval || model.Interval == varRateIntervalAlt {
// Rate interval is final and is not affected by resolution
return calculateRateInterval(adjustedInterval, timeInterval, intervalCalculator), nil
} else {
intervalFactor := model.IntervalFactor
if intervalFactor == 0 {
intervalFactor = 1
}
return time.Duration(int64(adjustedInterval) * intervalFactor), nil
}
}
func calculateRateInterval(interval time.Duration, scrapeInterval string, intervalCalculator intervalv2.Calculator) time.Duration {
scrape := scrapeInterval
if scrape == "" {
scrape = "15s"
}
scrapeIntervalDuration, err := intervalv2.ParseIntervalStringToTimeDuration(scrape)
if err != nil {
return time.Duration(0)
}
rateInterval := time.Duration(int(math.Max(float64(interval+scrapeIntervalDuration), float64(4)*float64(scrapeIntervalDuration))))
return rateInterval
}
func interpolateVariables(model *QueryModel, interval time.Duration, timeRange time.Duration, intervalCalculator intervalv2.Calculator, timeInterval string) string {
expr := model.Expr
rangeMs := timeRange.Milliseconds()
rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0))
var rateInterval time.Duration
if model.Interval == varRateInterval || model.Interval == varRateIntervalAlt {
rateInterval = interval
} else {
rateInterval = calculateRateInterval(interval, timeInterval, intervalCalculator)
}
expr = strings.ReplaceAll(expr, varIntervalMs, strconv.FormatInt(int64(interval/time.Millisecond), 10))
expr = strings.ReplaceAll(expr, varInterval, intervalv2.FormatDuration(interval))
expr = strings.ReplaceAll(expr, varRangeMs, strconv.FormatInt(rangeMs, 10))
expr = strings.ReplaceAll(expr, varRangeS, strconv.FormatInt(rangeSRounded, 10))
expr = strings.ReplaceAll(expr, varRange, strconv.FormatInt(rangeSRounded, 10)+"s")
expr = strings.ReplaceAll(expr, varRateInterval, rateInterval.String())
// Repetitive code, we should have functionality to unify these
expr = strings.ReplaceAll(expr, varIntervalMsAlt, strconv.FormatInt(int64(interval/time.Millisecond), 10))
expr = strings.ReplaceAll(expr, varIntervalAlt, intervalv2.FormatDuration(interval))
expr = strings.ReplaceAll(expr, varRangeMsAlt, strconv.FormatInt(rangeMs, 10))
expr = strings.ReplaceAll(expr, varRangeSAlt, strconv.FormatInt(rangeSRounded, 10))
expr = strings.ReplaceAll(expr, varRangeAlt, strconv.FormatInt(rangeSRounded, 10)+"s")
expr = strings.ReplaceAll(expr, varRateIntervalAlt, rateInterval.String())
return expr
}
func isVariableInterval(interval string) bool {
if interval == varInterval || interval == varIntervalMs || interval == varRateInterval {
return true
}
//Repetitive code, we should have functionality to unify these
if interval == varIntervalAlt || interval == varIntervalMsAlt || interval == varRateIntervalAlt {
return true
}
return false
}
func alignTimeRange(t time.Time, step time.Duration, offset int64) time.Time {
return time.Unix(int64(math.Floor((float64(t.Unix()+offset)/step.Seconds()))*step.Seconds()-float64(offset)), 0)
}

View File

@ -0,0 +1,451 @@
package models_test
import (
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
"github.com/stretchr/testify/require"
)
var (
now = time.Now()
intervalCalculator = intervalv2.NewCalculator()
)
func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) {
t.Run("parsing query from unified alerting", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(12 * time.Hour),
}
queryJson := `{
"expr": "go_goroutines",
"refId": "A",
"exemplar": true
}`
q := backend.DataQuery{
JSON: []byte(queryJson),
TimeRange: timeRange,
RefID: "A",
}
res, err := models.Parse(q, "15s", intervalCalculator, true)
require.NoError(t, err)
require.Equal(t, false, res.ExemplarQuery)
})
t.Run("parsing query model with step", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(12 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, time.Second*30, res.Step)
})
t.Run("parsing query model without step parameter", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(1 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, time.Second*15, res.Step)
})
t.Run("parsing query model with high intervalFactor", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 10,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, time.Minute*20, res.Step)
})
t.Run("parsing query model with low intervalFactor", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, time.Minute*2, res.Step)
})
t.Run("parsing query model specified scrape-interval in the data source", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "240s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, time.Minute*4, res.Step)
})
t.Run("parsing query model with $__interval variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__interval]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr)
})
t.Run("parsing query model with ${__interval} variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [${__interval}]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr)
})
t.Run("parsing query model with $__interval_ms variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__interval_ms]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [120000]})", res.Expr)
})
t.Run("parsing query model with $__interval_ms and $__interval variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__interval_ms]}) + rate(ALERTS{job=\"test\" [$__interval]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr)
})
t.Run("parsing query model with ${__interval_ms} and ${__interval} variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [${__interval_ms}]}) + rate(ALERTS{job=\"test\" [${__interval}]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr)
})
t.Run("parsing query model with $__range variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__range]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800s]})", res.Expr)
})
t.Run("parsing query model with $__range_s variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__range_s]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800]})", res.Expr)
})
t.Run("parsing query model with ${__range_s} variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [${__range_s}s]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800s]})", res.Expr)
})
t.Run("parsing query model with $__range_s variable below 0.5s", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(40 * time.Millisecond),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__range_s]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [0]})", res.Expr)
})
t.Run("parsing query model with $__range_s variable between 1-0.5s", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(800 * time.Millisecond),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__range_s]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [1]})", res.Expr)
})
t.Run("parsing query model with $__range_ms variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__range_ms]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800000]})", res.Expr)
})
t.Run("parsing query model with $__range_ms variable below 1s", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(20 * time.Millisecond),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__range_ms]})",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [20]})", res.Expr)
})
t.Run("parsing query model with $__rate_interval variable", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(5 * time.Minute),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__rate_interval]})",
"format": "time_series",
"intervalFactor": 1,
"interval": "5m",
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [5m15s]})", res.Expr)
})
t.Run("parsing query model with $__rate_interval variable in expr and interval", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(5 * time.Minute),
}
q := queryContext(`{
"expr": "rate(ALERTS{job=\"test\" [$__rate_interval]})",
"format": "time_series",
"intervalFactor": 1,
"interval": "$__rate_interval",
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [1m0s]})", res.Expr)
require.Equal(t, 1*time.Minute, res.Step)
})
t.Run("parsing query model of range query", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A",
"range": true
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, true, res.RangeQuery)
})
t.Run("parsing query model of range and instant query", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A",
"range": true,
"instant": true
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, true, res.RangeQuery)
require.Equal(t, true, res.InstantQuery)
})
t.Run("parsing query model of with no query type", func(t *testing.T) {
timeRange := backend.TimeRange{
From: now,
To: now.Add(48 * time.Hour),
}
q := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`, timeRange)
res, err := models.Parse(q, "15s", intervalCalculator, false)
require.NoError(t, err)
require.Equal(t, true, res.RangeQuery)
})
}
func queryContext(json string, timeRange backend.TimeRange) backend.DataQuery {
return backend.DataQuery{
JSON: []byte(json),
TimeRange: timeRange,
RefID: "A",
}
}

View File

@ -15,23 +15,27 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/prometheus/buffered"
"github.com/grafana/grafana/pkg/tsdb/prometheus/querydata"
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
)
var plog = log.New("tsdb.prometheus")
type Service struct {
im instancemgmt.InstanceManager
im instancemgmt.InstanceManager
features featuremgmt.FeatureToggles
}
type instance struct {
Buffered *buffered.Buffered
buffered *buffered.Buffered
queryData *querydata.QueryData
}
func ProvideService(httpClientProvider httpclient.Provider, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer) *Service {
plog.Debug("initializing")
return &Service{
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider, cfg, features, tracer)),
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider, cfg, features, tracer)),
features: features,
}
}
@ -43,13 +47,19 @@ func newInstanceSettings(httpClientProvider httpclient.Provider, cfg *setting.Cf
return nil, fmt.Errorf("error reading settings: %w", err)
}
buf, err := buffered.New(httpClientProvider, cfg, features, tracer, settings, plog)
b, err := buffered.New(httpClientProvider, cfg, features, tracer, settings, plog)
if err != nil {
return nil, err
}
qd, err := querydata.New(httpClientProvider, cfg, features, tracer, settings, plog)
if err != nil {
return nil, err
}
return instance{
Buffered: buf,
buffered: b,
queryData: qd,
}, nil
}
}
@ -64,7 +74,11 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
return nil, err
}
return i.Buffered.ExecuteTimeSeriesQuery(ctx, req)
if s.features.IsEnabled(featuremgmt.FlagPrometheusStreamingJSONParser) {
return i.queryData.Execute(ctx, req)
}
return i.buffered.ExecuteTimeSeriesQuery(ctx, req)
}
func (s *Service) getInstance(pluginCtx backend.PluginContext) (*instance, error) {

View File

@ -0,0 +1,143 @@
package querydata_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
)
var update = false
func TestMatrixResponses(t *testing.T) {
tt := []struct {
name string
filepath string
}{
{name: "parse a simple matrix response", filepath: "range_simple"},
{name: "parse a simple matrix response with value missing steps", filepath: "range_missing"},
{name: "parse a response with Infinity", filepath: "range_infinity"},
{name: "parse a response with NaN", filepath: "range_nan"},
}
for _, test := range tt {
t.Run(test.name, func(t *testing.T) {
queryFileName := filepath.Join("../testdata", test.filepath+".query.json")
responseFileName := filepath.Join("../testdata", test.filepath+".result.json")
goldenFileName := filepath.Join("../testdata", test.filepath+".result.streaming.golden")
query, err := loadStoredQuery(queryFileName)
require.NoError(t, err)
responseBytes, err := os.ReadFile(responseFileName)
require.NoError(t, err)
result, err := runQuery(responseBytes, query)
require.NoError(t, err)
require.Len(t, result.Responses, 1)
dr, found := result.Responses["A"]
require.True(t, found)
actual, err := json.MarshalIndent(&dr, "", " ")
require.NoError(t, err)
// nolint:gosec
// We can ignore the gosec G304 because this is a test with static defined paths
expected, err := ioutil.ReadFile(goldenFileName + ".json")
if err != nil || update {
err = os.WriteFile(goldenFileName+".json", actual, 0600)
require.NoError(t, err)
}
require.JSONEq(t, string(expected), string(actual))
require.NoError(t, experimental.CheckGoldenDataResponse(goldenFileName+".txt", &dr, update))
})
}
}
// we store the prometheus query data in a json file, here is some minimal code
// to be able to read it back. unfortunately we cannot use the models.Query
// struct here, because it has `time.time` and `time.duration` fields that
// cannot be unmarshalled from JSON automatically.
type storedPrometheusQuery struct {
RefId string
RangeQuery bool
Start int64
End int64
Step int64
Expr string
}
func loadStoredQuery(fileName string) (*backend.QueryDataRequest, error) {
bytes, err := os.ReadFile(fileName)
if err != nil {
return nil, err
}
var sq storedPrometheusQuery
err = json.Unmarshal(bytes, &sq)
if err != nil {
return nil, err
}
qm := models.QueryModel{
RangeQuery: sq.RangeQuery,
Expr: sq.Expr,
Interval: fmt.Sprintf("%ds", sq.Step),
IntervalMS: sq.Step * 1000,
}
data, err := json.Marshal(&qm)
if err != nil {
return nil, err
}
return &backend.QueryDataRequest{
Queries: []backend.DataQuery{
{
TimeRange: backend.TimeRange{
From: time.Unix(sq.Start, 0),
To: time.Unix(sq.End, 0),
},
RefID: sq.RefId,
Interval: time.Second * time.Duration(sq.Step),
JSON: json.RawMessage(data),
},
},
}, nil
}
func runQuery(response []byte, q *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
tCtx := setup()
res := &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(response)),
}
tCtx.httpProvider.setResponse(res)
return tCtx.queryData.Execute(context.Background(), q)
}
type fakeLogger struct {
log.Logger
}
func (fl *fakeLogger) Debug(testMessage string, ctx ...interface{}) {}
func (fl *fakeLogger) Info(testMessage string, ctx ...interface{}) {}
func (fl *fakeLogger) Warn(testMessage string, ctx ...interface{}) {}
func (fl *fakeLogger) Error(testMessage string, ctx ...interface{}) {}

View File

@ -0,0 +1,100 @@
package querydata_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"strings"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
"github.com/stretchr/testify/require"
)
// when memory-profiling this benchmark, these commands are recommended:
// - go test -benchmem -run=^$ -benchtime 1x -memprofile memprofile.out -memprofilerate 1 -bench ^BenchmarkJson$ github.com/grafana/grafana/pkg/tsdb/prometheus
// - go tool pprof -http=localhost:6061 memprofile.out
func BenchmarkJson(b *testing.B) {
body, q := createJsonTestData(1642000000, 1, 300, 400)
tCtx := setup()
b.ResetTimer()
for n := 0; n < b.N; n++ {
res := http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(body)),
}
tCtx.httpProvider.setResponse(&res)
_, err := tCtx.queryData.Execute(context.Background(), q)
require.NoError(b, err)
}
}
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++ {
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{
RangeQuery: true,
Expr: "test",
}
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
}

View File

@ -0,0 +1,183 @@
package querydata
import (
"context"
"encoding/json"
"fmt"
"regexp"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
"github.com/grafana/grafana/pkg/tsdb/prometheus/client"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
"github.com/grafana/grafana/pkg/util/maputil"
"go.opentelemetry.io/otel/attribute"
)
const legendFormatAuto = "__auto"
var legendFormatRegexp = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
type clientGetter func(map[string]string) (*client.Client, error)
type ExemplarEvent struct {
Time time.Time
Value float64
Labels map[string]string
}
type QueryData struct {
intervalCalculator intervalv2.Calculator
tracer tracing.Tracer
getClient clientGetter
log log.Logger
ID int64
URL string
TimeInterval string
}
func New(
httpClientProvider httpclient.Provider,
cfg *setting.Cfg,
features featuremgmt.FeatureToggles,
tracer tracing.Tracer,
settings backend.DataSourceInstanceSettings,
plog log.Logger,
) (*QueryData, error) {
var jsonData map[string]interface{}
if err := json.Unmarshal(settings.JSONData, &jsonData); err != nil {
return nil, fmt.Errorf("error reading settings: %w", err)
}
timeInterval, err := maputil.GetStringOptional(jsonData, "timeInterval")
if err != nil {
return nil, err
}
p := client.NewProvider(settings, jsonData, httpClientProvider, cfg, features, plog)
pc, err := client.NewProviderCache(p)
if err != nil {
return nil, err
}
return &QueryData{
intervalCalculator: intervalv2.NewCalculator(),
tracer: tracer,
log: plog,
getClient: pc.GetClient,
TimeInterval: timeInterval,
ID: settings.ID,
URL: settings.URL,
}, nil
}
func (s *QueryData) Execute(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
fromAlert := req.Headers["FromAlert"] == "true"
result := backend.QueryDataResponse{
Responses: backend.Responses{},
}
client, err := s.getClient(req.Headers)
if err != nil {
return &result, err
}
for _, q := range req.Queries {
query, err := models.Parse(q, s.TimeInterval, s.intervalCalculator, fromAlert)
if err != nil {
return &result, err
}
r, err := s.fetch(ctx, client, query)
if err != nil {
return &result, err
}
if r == nil {
s.log.Debug("Received nilresponse from runQuery", "query", query.Expr)
continue
}
result.Responses[q.RefID] = *r
}
return &result, nil
}
func (s *QueryData) fetch(ctx context.Context, client *client.Client, q *models.Query) (*backend.DataResponse, error) {
s.log.Debug("Sending query", "start", q.Start, "end", q.End, "step", q.Step, "query", q.Expr)
traceCtx, span := s.trace(ctx, q)
defer span.End()
response := &backend.DataResponse{
Frames: data.Frames{},
Error: nil,
}
if q.RangeQuery {
res, err := s.rangeQuery(traceCtx, client, q)
if err != nil {
return nil, err
}
response.Frames = res.Frames
}
if q.InstantQuery {
res, err := s.instantQuery(traceCtx, client, q)
if err != nil {
return nil, err
}
response.Frames = append(response.Frames, res.Frames...)
}
if q.ExemplarQuery {
res, err := s.exemplarQuery(traceCtx, client, q)
if err != nil {
// If exemplar query returns error, we want to only log it and
// continue with other results processing
s.log.Error("Exemplar query failed", "query", q.Expr, "err", err)
}
if res != nil {
response.Frames = append(response.Frames, res.Frames...)
}
}
return response, nil
}
func (s *QueryData) rangeQuery(ctx context.Context, c *client.Client, q *models.Query) (*backend.DataResponse, error) {
res, err := c.QueryRange(ctx, q)
if err != nil {
return nil, err
}
return s.parseResponse(ctx, q, res)
}
func (s *QueryData) instantQuery(ctx context.Context, c *client.Client, q *models.Query) (*backend.DataResponse, error) {
res, err := c.QueryInstant(ctx, q)
if err != nil {
return nil, err
}
return s.parseResponse(ctx, q, res)
}
func (s *QueryData) exemplarQuery(ctx context.Context, c *client.Client, q *models.Query) (*backend.DataResponse, error) {
res, err := c.QueryExemplars(ctx, q)
if err != nil {
return nil, err
}
return s.parseResponse(ctx, q, res)
}
func (s *QueryData) trace(ctx context.Context, q *models.Query) (context.Context, tracing.Span) {
traceCtx, span := s.tracer.Start(ctx, "datasource.prometheus")
span.SetAttributes("expr", q.Expr, attribute.Key("expr").String(q.Expr))
span.SetAttributes("start_unixnano", q.Start, attribute.Key("start_unixnano").Int64(q.Start.UnixNano()))
span.SetAttributes("stop_unixnano", q.End, attribute.Key("stop_unixnano").Int64(q.End.UnixNano()))
return traceCtx, span
}

View File

@ -0,0 +1,464 @@
package querydata_test
import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"math"
"net/http"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
"github.com/grafana/grafana/pkg/tsdb/prometheus/querydata"
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
p "github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
t.Run("exemplars response should be sampled and parsed normally", func(t *testing.T) {
t.Skip()
exemplars := []apiv1.ExemplarQueryResult{
{
SeriesLabels: p.LabelSet{
"__name__": "tns_request_duration_seconds_bucket",
"instance": "app:80",
"job": "tns/app",
},
Exemplars: []apiv1.Exemplar{
{
Labels: p.LabelSet{"traceID": "test1"},
Value: 0.003535405,
Timestamp: p.TimeFromUnixNano(time.Now().Add(-2 * time.Minute).UnixNano()),
},
{
Labels: p.LabelSet{"traceID": "test2"},
Value: 0.005555605,
Timestamp: p.TimeFromUnixNano(time.Now().Add(-4 * time.Minute).UnixNano()),
},
{
Labels: p.LabelSet{"traceID": "test3"},
Value: 0.007545445,
Timestamp: p.TimeFromUnixNano(time.Now().Add(-6 * time.Minute).UnixNano()),
},
{
Labels: p.LabelSet{"traceID": "test4"},
Value: 0.009545445,
Timestamp: p.TimeFromUnixNano(time.Now().Add(-7 * time.Minute).UnixNano()),
},
},
},
}
tctx := setup()
qm := models.QueryModel{
LegendFormat: "legend {{app}}",
UtcOffsetSec: 0,
ExemplarQuery: true,
}
b, err := json.Marshal(&qm)
require.NoError(t, err)
query := backend.DataQuery{
RefID: "A",
JSON: b,
}
res, err := execute(tctx, query, exemplars)
require.NoError(t, err)
// Test fields
require.Len(t, res, 1)
// require.Equal(t, res[0].Name, "exemplar")
require.Equal(t, res[0].Fields[0].Name, "Time")
require.Equal(t, res[0].Fields[1].Name, "Value")
require.Len(t, res[0].Fields, 6)
// Test correct values (sampled to 2)
require.Equal(t, res[0].Fields[1].Len(), 2)
require.Equal(t, res[0].Fields[1].At(0), 0.009545445)
require.Equal(t, res[0].Fields[1].At(1), 0.003535405)
})
t.Run("matrix response should be parsed normally", func(t *testing.T) {
values := []p.SamplePair{
{Value: 1, Timestamp: 1000},
{Value: 2, Timestamp: 2000},
{Value: 3, Timestamp: 3000},
{Value: 4, Timestamp: 4000},
{Value: 5, Timestamp: 5000},
}
result := queryResult{
Type: p.ValMatrix,
Result: p.Matrix{
&p.SampleStream{
Metric: p.Metric{"app": "Application", "tag2": "tag2"},
Values: values,
},
},
}
qm := models.QueryModel{
LegendFormat: "legend {{app}}",
UtcOffsetSec: 0,
RangeQuery: true,
}
b, err := json.Marshal(&qm)
require.NoError(t, err)
query := backend.DataQuery{
TimeRange: backend.TimeRange{
From: time.Unix(1, 0).UTC(),
To: time.Unix(5, 0).UTC(),
},
JSON: b,
}
tctx := setup()
res, err := execute(tctx, query, result)
require.NoError(t, err)
require.Len(t, res, 1)
//require.Equal(t, "legend Application", res[0].Name)
require.Len(t, res[0].Fields, 2)
require.Len(t, res[0].Fields[0].Labels, 0)
require.Equal(t, "Time", res[0].Fields[0].Name)
require.Len(t, res[0].Fields[1].Labels, 2)
require.Equal(t, "app=Application, tag2=tag2", res[0].Fields[1].Labels.String())
require.Equal(t, "Value", res[0].Fields[1].Name)
require.Equal(t, "legend Application", res[0].Fields[1].Config.DisplayNameFromDS)
// Ensure the timestamps are UTC zoned
testValue := res[0].Fields[0].At(0)
require.Equal(t, "UTC", testValue.(time.Time).Location().String())
})
t.Run("matrix response with missed data points should be parsed correctly", func(t *testing.T) {
values := []p.SamplePair{
{Value: 1, Timestamp: 1000},
{Value: 4, Timestamp: 4000},
}
result := queryResult{
Type: p.ValMatrix,
Result: p.Matrix{
&p.SampleStream{
Metric: p.Metric{"app": "Application", "tag2": "tag2"},
Values: values,
},
},
}
qm := models.QueryModel{
LegendFormat: "",
UtcOffsetSec: 0,
RangeQuery: true,
}
b, err := json.Marshal(&qm)
require.NoError(t, err)
query := backend.DataQuery{
TimeRange: backend.TimeRange{
From: time.Unix(1, 0).UTC(),
To: time.Unix(4, 0).UTC(),
},
JSON: b,
}
tctx := setup()
res, err := execute(tctx, query, result)
require.NoError(t, err)
require.Len(t, res, 1)
require.Equal(t, res[0].Fields[0].Len(), 2)
require.Equal(t, time.Unix(1, 0).UTC(), res[0].Fields[0].At(0))
require.Equal(t, time.Unix(4, 0).UTC(), res[0].Fields[0].At(1))
require.Equal(t, res[0].Fields[1].Len(), 2)
require.Equal(t, float64(1), res[0].Fields[1].At(0).(float64))
require.Equal(t, float64(4), res[0].Fields[1].At(1).(float64))
})
t.Run("matrix response with from alerting missed data points should be parsed correctly", func(t *testing.T) {
values := []p.SamplePair{
{Value: 1, Timestamp: 1000},
{Value: 4, Timestamp: 4000},
}
result := queryResult{
Type: p.ValMatrix,
Result: p.Matrix{
&p.SampleStream{
Metric: p.Metric{"app": "Application", "tag2": "tag2"},
Values: values,
},
},
}
qm := models.QueryModel{
LegendFormat: "",
UtcOffsetSec: 0,
RangeQuery: true,
}
b, err := json.Marshal(&qm)
require.NoError(t, err)
query := backend.DataQuery{
TimeRange: backend.TimeRange{
From: time.Unix(1, 0).UTC(),
To: time.Unix(4, 0).UTC(),
},
JSON: b,
}
tctx := setup()
res, err := execute(tctx, query, result)
require.NoError(t, err)
require.Len(t, res, 1)
require.Equal(t, res[0].Name, "{app=\"Application\", tag2=\"tag2\"}")
require.Len(t, res[0].Fields, 2)
require.Len(t, res[0].Fields[0].Labels, 0)
require.Equal(t, res[0].Fields[0].Name, "Time")
require.Len(t, res[0].Fields[1].Labels, 2)
require.Equal(t, res[0].Fields[1].Labels.String(), "app=Application, tag2=tag2")
require.Equal(t, res[0].Fields[1].Name, "Value")
require.Equal(t, res[0].Fields[1].Config.DisplayNameFromDS, "{app=\"Application\", tag2=\"tag2\"}")
})
t.Run("matrix response with NaN value should be changed to null", func(t *testing.T) {
result := queryResult{
Type: p.ValMatrix,
Result: p.Matrix{
&p.SampleStream{
Metric: p.Metric{"app": "Application"},
Values: []p.SamplePair{
{Value: p.SampleValue(math.NaN()), Timestamp: 1000},
},
},
},
}
qm := models.QueryModel{
LegendFormat: "",
UtcOffsetSec: 0,
RangeQuery: true,
}
b, err := json.Marshal(&qm)
require.NoError(t, err)
query := backend.DataQuery{
TimeRange: backend.TimeRange{
From: time.Unix(1, 0).UTC(),
To: time.Unix(4, 0).UTC(),
},
JSON: b,
}
tctx := setup()
res, err := execute(tctx, query, result)
require.NoError(t, err)
require.Equal(t, res[0].Fields[1].Name, "Value")
require.True(t, math.IsNaN(res[0].Fields[1].At(0).(float64)))
})
t.Run("vector response should be parsed normally", func(t *testing.T) {
qr := queryResult{
Type: p.ValVector,
Result: p.Vector{
&p.Sample{
Metric: p.Metric{"app": "Application", "tag2": "tag2"},
Value: 1,
Timestamp: 123,
},
},
}
qm := models.QueryModel{
LegendFormat: "legend {{app}}",
UtcOffsetSec: 0,
InstantQuery: true,
}
b, err := json.Marshal(&qm)
require.NoError(t, err)
query := backend.DataQuery{
JSON: b,
}
tctx := setup()
res, err := execute(tctx, query, qr)
require.NoError(t, err)
require.Len(t, res, 1)
require.Equal(t, res[0].Name, "legend Application")
require.Len(t, res[0].Fields, 2)
require.Len(t, res[0].Fields[0].Labels, 0)
require.Equal(t, res[0].Fields[0].Name, "Time")
require.Equal(t, res[0].Fields[0].Name, "Time")
require.Len(t, res[0].Fields[1].Labels, 2)
require.Equal(t, res[0].Fields[1].Labels.String(), "app=Application, tag2=tag2")
require.Equal(t, res[0].Fields[1].Name, "Value")
require.Equal(t, res[0].Fields[1].Config.DisplayNameFromDS, "legend Application")
// Ensure the timestamps are UTC zoned
testValue := res[0].Fields[0].At(0)
require.Equal(t, "UTC", testValue.(time.Time).Location().String())
require.Equal(t, int64(123), testValue.(time.Time).UnixMilli())
})
t.Run("scalar response should be parsed normally", func(t *testing.T) {
t.Skip("TODO: implement scalar responses")
qr := queryResult{
Type: p.ValScalar,
Result: &p.Scalar{
Value: 1,
Timestamp: 123,
},
}
qm := models.QueryModel{
LegendFormat: "",
UtcOffsetSec: 0,
InstantQuery: true,
}
b, err := json.Marshal(&qm)
require.NoError(t, err)
query := backend.DataQuery{
JSON: b,
}
tctx := setup()
res, err := execute(tctx, query, qr)
require.NoError(t, err)
require.Len(t, res, 1)
require.Equal(t, res[0].Name, "1")
require.Len(t, res[0].Fields, 2)
require.Len(t, res[0].Fields[0].Labels, 0)
require.Equal(t, res[0].Fields[0].Name, "Time")
require.Equal(t, res[0].Fields[1].Name, "Value")
require.Equal(t, res[0].Fields[1].Config.DisplayNameFromDS, "1")
// Ensure the timestamps are UTC zoned
testValue := res[0].Fields[0].At(0)
require.Equal(t, "UTC", testValue.(time.Time).Location().String())
require.Equal(t, int64(123), testValue.(time.Time).UnixMilli())
})
}
type queryResult struct {
Type p.ValueType `json:"resultType"`
Result interface{} `json:"result"`
}
func execute(tctx *testContext, query backend.DataQuery, qr interface{}) (data.Frames, error) {
req := backend.QueryDataRequest{
Queries: []backend.DataQuery{query},
Headers: map[string]string{},
}
promRes, err := toAPIResponse(qr)
if err != nil {
return nil, err
}
tctx.httpProvider.setResponse(promRes)
res, err := tctx.queryData.Execute(context.Background(), &req)
if err != nil {
return nil, err
}
return res.Responses[req.Queries[0].RefID].Frames, nil
}
type apiResponse struct {
Status string `json:"status"`
Data json.RawMessage `json:"data"`
}
func toAPIResponse(d interface{}) (*http.Response, error) {
b, err := json.Marshal(d)
if err != nil {
return nil, err
}
res := apiResponse{
Status: "success",
Data: json.RawMessage(b),
}
raw, err := json.Marshal(&res)
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(raw)),
}, nil
}
type testContext struct {
httpProvider *fakeHttpClientProvider
queryData *querydata.QueryData
}
func setup() *testContext {
tracer, err := tracing.InitializeTracerForTest()
if err != nil {
panic(err)
}
httpProvider := &fakeHttpClientProvider{
opts: sdkhttpclient.Options{
Timeouts: &sdkhttpclient.DefaultTimeoutOptions,
},
res: &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
},
}
queryData, _ := querydata.New(
httpProvider,
setting.NewCfg(),
&fakeFeatureToggles{enabled: true},
tracer,
backend.DataSourceInstanceSettings{URL: "http://localhost:9090", JSONData: json.RawMessage(`{"timeInterval": "15s"}`)},
&fakeLogger{},
)
return &testContext{
httpProvider: httpProvider,
queryData: queryData,
}
}
type fakeFeatureToggles struct {
enabled bool
}
func (f *fakeFeatureToggles) IsEnabled(feature string) bool {
return f.enabled
}
type fakeHttpClientProvider struct {
httpclient.Provider
opts sdkhttpclient.Options
res *http.Response
}
func (p *fakeHttpClientProvider) New(opts ...sdkhttpclient.Options) (*http.Client, error) {
p.opts = opts[0]
c, err := sdkhttpclient.New(opts[0])
if err != nil {
return nil, err
}
c.Transport = p
return c, nil
}
func (p *fakeHttpClientProvider) GetTransport(opts ...sdkhttpclient.Options) (http.RoundTripper, error) {
p.opts = opts[0]
return http.DefaultTransport, nil
}
func (p *fakeHttpClientProvider) setResponse(res *http.Response) {
p.res = res
}
func (p *fakeHttpClientProvider) RoundTrip(req *http.Request) (*http.Response, error) {
return p.res, nil
}

View File

@ -0,0 +1,111 @@
package querydata
import (
"context"
"fmt"
"net/http"
"sort"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
"github.com/grafana/grafana/pkg/util/converter"
jsoniter "github.com/json-iterator/go"
)
func (s *QueryData) parseResponse(ctx context.Context, q *models.Query, res *http.Response) (*backend.DataResponse, error) {
defer func() {
if err := res.Body.Close(); err != nil {
s.log.Error("Failed to close response body", "err", err)
}
}()
iter := jsoniter.Parse(jsoniter.ConfigDefault, res.Body, 1024)
r := converter.ReadPrometheusStyleResult(iter)
if r == nil {
return nil, fmt.Errorf("received empty response from prometheus")
}
// The ExecutedQueryString can be viewed in QueryInspector in UI
for _, frame := range r.Frames {
addMetadataToFrame(q, frame)
}
return r, nil
}
func addMetadataToFrame(q *models.Query, frame *data.Frame) {
if frame.Meta == nil {
frame.Meta = &data.FrameMeta{}
}
frame.Meta.ExecutedQueryString = executedQueryString(q)
if len(frame.Fields) < 2 {
return
}
frame.Name = getName(q, frame)
frame.Fields[0].Config = &data.FieldConfig{Interval: float64(q.Step.Milliseconds())}
if frame.Name != "" {
frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: frame.Name}
}
}
// this is based on the logic from the String() function in github.com/prometheus/common/model.go
func metricNameFromLabels(f *data.Frame) string {
labels := f.Fields[1].Labels
metricName, hasName := labels["__name__"]
numLabels := len(labels) - 1
if !hasName {
numLabels = len(labels)
}
labelStrings := make([]string, 0, numLabels)
for label, value := range labels {
if label != "__name__" {
labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value))
}
}
switch numLabels {
case 0:
if hasName {
return metricName
}
return "{}"
default:
sort.Strings(labelStrings)
return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ", "))
}
}
func executedQueryString(q *models.Query) string {
return "Expr: " + q.Expr + "\n" + "Step: " + q.Step.String()
}
func getName(q *models.Query, frame *data.Frame) string {
labels := frame.Fields[1].Labels
legend := metricNameFromLabels(frame)
if q.LegendFormat == legendFormatAuto && len(labels) > 0 {
return ""
}
if q.LegendFormat != "" {
result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendFormat), func(in []byte) []byte {
labelName := strings.Replace(string(in), "{{", "", 1)
labelName = strings.Replace(labelName, "}}", "", 1)
labelName = strings.TrimSpace(labelName)
if val, exists := labels[labelName]; exists {
return []byte(val)
}
return []byte{}
})
legend = string(result)
}
// If legend is empty brackets, use query expression
if legend == "{}" {
return q.Expr
}
return legend
}

View File

@ -4,6 +4,7 @@
"schema": {
"name": "1 / 0",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},

View File

@ -1,6 +1,7 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
@ -20,4 +21,4 @@ Dimensions: 2 Fields by 3 Rows
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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
FRAME=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

View File

@ -0,0 +1,63 @@
{
"frames": [
{
"schema": {
"name": "1 / 0",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: 1 / 0\nStep: 1s"
},
"fields": [
{
"name": "Time",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"interval": 1000
}
},
{
"name": "Value",
"type": "number",
"typeInfo": {
"frame": "float64"
},
"labels": {},
"config": {
"displayNameFromDS": "1 / 0"
}
}
]
},
"data": {
"values": [
[
1641889530000,
1641889531000,
1641889532000
],
[
null,
null,
null
]
],
"entities": [
null,
{
"Inf": [
0,
1,
2
]
}
]
}
}
]
}

View File

@ -0,0 +1,24 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: 1 / 0\nStep: 1s"
}
Name: 1 / 0
Dimensions: 2 Fields by 3 Rows
+-------------------------------+-----------------+
| Name: Time | Name: Value |
| Labels: | Labels: |
| Type: []time.Time | Type: []float64 |
+-------------------------------+-----------------+
| 2022-01-11 08:25:30 +0000 UTC | +Inf |
| 2022-01-11 08:25:31 +0000 UTC | +Inf |
| 2022-01-11 08:25:32 +0000 UTC | +Inf |
+-------------------------------+-----------------+
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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

View File

@ -4,6 +4,7 @@
"schema": {
"name": "go_goroutines{job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},

View File

@ -1,6 +1,7 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
@ -20,4 +21,4 @@ Dimensions: 2 Fields by 3 Rows
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=QVJST1cxAAD/////6AIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAOAAAAADAAAAaAAAACgAAAAEAAAAtP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADU/f//CAAAACgAAAAfAAAAZ29fZ29yb3V0aW5lc3tqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAAAQ/v//CAAAAFwAAABQAAAAeyJjdXN0b20iOnsicmVzdWx0VHlwZSI6Im1hdHJpeCJ9LCJleGVjdXRlZFF1ZXJ5U3RyaW5nIjoiRXhwcjogdGVzdDFcblN0ZXA6IDFzIn0AAAAABAAAAG1ldGEAAAAAAgAAADQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAOgAAADoAAAAAAADAegAAAADAAAAfAAAACwAAAAEAAAAyP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAA7P7//wgAAAA4AAAALwAAAHsiX19uYW1lX18iOiJnb19nb3JvdXRpbmVzIiwiam9iIjoicHJvbWV0aGV1cyJ9AAYAAABsYWJlbHMAADj///8IAAAARAAAADkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoiZ29fZ29yb3V0aW5lc3tqb2I9XCJwcm9tZXRoZXVzXCJ9In0AAAAGAAAAY29uZmlnAAAAAAAAVv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAAB4AAAAgAAAAAAAAAqAAAAAAgAAADQAAAAEAAAA3P///wgAAAAQAAAABAAAAFRpbWUAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABwAAAARAAAAeyJpbnRlcnZhbCI6MTAwMH0AAAAGAAAAY29uZmlnAAAAAAAAAAAGAAgABgAGAAAAAAADAAQAAABUaW1lAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAMAAAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAWAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAIAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAACi5cbUKckWAGyAAtUpyRYAylC11SnJFgAAAAAAADVAAAAAAAAAQEAAAAAAAIBFQBAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAAEAAEAAAD4AgAAAAAAAMAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAOAAAAADAAAAaAAAACgAAAAEAAAAtP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADU/f//CAAAACgAAAAfAAAAZ29fZ29yb3V0aW5lc3tqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAAAQ/v//CAAAAFwAAABQAAAAeyJjdXN0b20iOnsicmVzdWx0VHlwZSI6Im1hdHJpeCJ9LCJleGVjdXRlZFF1ZXJ5U3RyaW5nIjoiRXhwcjogdGVzdDFcblN0ZXA6IDFzIn0AAAAABAAAAG1ldGEAAAAAAgAAADQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAOgAAADoAAAAAAADAegAAAADAAAAfAAAACwAAAAEAAAAyP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAA7P7//wgAAAA4AAAALwAAAHsiX19uYW1lX18iOiJnb19nb3JvdXRpbmVzIiwiam9iIjoicHJvbWV0aGV1cyJ9AAYAAABsYWJlbHMAADj///8IAAAARAAAADkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoiZ29fZ29yb3V0aW5lc3tqb2I9XCJwcm9tZXRoZXVzXCJ9In0AAAAGAAAAY29uZmlnAAAAAAAAVv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAAB4AAAAgAAAAAAAAAqAAAAAAgAAADQAAAAEAAAA3P///wgAAAAQAAAABAAAAFRpbWUAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABwAAAARAAAAeyJpbnRlcnZhbCI6MTAwMH0AAAAGAAAAY29uZmlnAAAAAAAAAAAGAAgABgAGAAAAAAADAAQAAABUaW1lAAAAABgDAABBUlJPVzE=
FRAME=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

View File

@ -0,0 +1,56 @@
{
"frames": [
{
"schema": {
"name": "go_goroutines{job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: test1\nStep: 1s"
},
"fields": [
{
"name": "Time",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"interval": 1000
}
},
{
"name": "Value",
"type": "number",
"typeInfo": {
"frame": "float64"
},
"labels": {
"__name__": "go_goroutines",
"job": "prometheus"
},
"config": {
"displayNameFromDS": "go_goroutines{job=\"prometheus\"}"
}
}
]
},
"data": {
"values": [
[
1641889533000,
1641889534000,
1641889537000
],
[
21,
32,
43
]
]
}
}
]
}

View File

@ -0,0 +1,24 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: test1\nStep: 1s"
}
Name: go_goroutines{job="prometheus"}
Dimensions: 2 Fields by 3 Rows
+-------------------------------+------------------------------------------------+
| Name: Time | Name: Value |
| Labels: | Labels: __name__=go_goroutines, job=prometheus |
| Type: []time.Time | Type: []float64 |
+-------------------------------+------------------------------------------------+
| 2022-01-11 08:25:33 +0000 UTC | 21 |
| 2022-01-11 08:25:34 +0000 UTC | 32 |
| 2022-01-11 08:25:37 +0000 UTC | 43 |
+-------------------------------+------------------------------------------------+
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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

View File

@ -4,6 +4,7 @@
"schema": {
"name": "{handler=\"/api/v1/query_range\", job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},

View File

@ -1,6 +1,7 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
@ -20,4 +21,4 @@ Dimensions: 2 Fields by 3 Rows
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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
FRAME=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

View File

@ -0,0 +1,66 @@
{
"frames": [
{
"schema": {
"name": "{handler=\"/api/v1/query_range\", job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: \nStep: 1s"
},
"fields": [
{
"name": "Time",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"interval": 1000
}
},
{
"name": "Value",
"type": "number",
"typeInfo": {
"frame": "float64"
},
"labels": {
"handler": "/api/v1/query_range",
"job": "prometheus"
},
"config": {
"displayNameFromDS": "{handler=\"/api/v1/query_range\", job=\"prometheus\"}"
}
}
]
},
"data": {
"values": [
[
1641889530000,
1641889531000,
1641889532000
],
[
null,
null,
null
]
],
"entities": [
null,
{
"NaN": [
0,
1,
2
]
}
]
}
}
]
}

View File

@ -0,0 +1,24 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: \nStep: 1s"
}
Name: {handler="/api/v1/query_range", job="prometheus"}
Dimensions: 2 Fields by 3 Rows
+-------------------------------+-----------------------------------------------------+
| Name: Time | Name: Value |
| Labels: | Labels: handler=/api/v1/query_range, job=prometheus |
| Type: []time.Time | Type: []float64 |
+-------------------------------+-----------------------------------------------------+
| 2022-01-11 08:25:30 +0000 UTC | NaN |
| 2022-01-11 08:25:31 +0000 UTC | NaN |
| 2022-01-11 08:25:32 +0000 UTC | NaN |
+-------------------------------+-----------------------------------------------------+
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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

View File

@ -4,6 +4,7 @@
"schema": {
"name": "prometheus_http_requests_total{code=\"200\", handler=\"/api/v1/query_range\", job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
@ -58,6 +59,7 @@
"schema": {
"name": "prometheus_http_requests_total{code=\"400\", handler=\"/api/v1/query_range\", job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},

View File

@ -1,6 +1,7 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
@ -21,6 +22,7 @@ Dimensions: 2 Fields by 3 Rows
Frame[1] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
@ -40,5 +42,5 @@ Dimensions: 2 Fields by 3 Rows
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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
FRAME=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
FRAME=QVJST1cxAAD/////uAMAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAADABAAADAAAApAAAACgAAAAEAAAA5Pz//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAAAE/f//CAAAAGQAAABbAAAAcHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9IjIwMCIsIGhhbmRsZXI9Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCBqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAAB8/f//CAAAAHAAAABkAAAAeyJ0eXBlIjoidGltZXNlcmllcy1tYW55IiwiY3VzdG9tIjp7InJlc3VsdFR5cGUiOiJtYXRyaXgifSwiZXhlY3V0ZWRRdWVyeVN0cmluZyI6IkV4cHI6IFxuU3RlcDogMXMifQAAAAAEAAAAbWV0YQAAAAACAAAAtAEAABgAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAaAEAAGgBAAAAAAMBaAEAAAMAAAC8AAAALAAAAAQAAABI/v//CAAAABAAAAAFAAAAVmFsdWUAAAAEAAAAbmFtZQAAAABs/v//CAAAAHgAAABtAAAAeyJfX25hbWVfXyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbCIsImNvZGUiOiIyMDAiLCJoYW5kbGVyIjoiL2FwaS92MS9xdWVyeV9yYW5nZSIsImpvYiI6InByb21ldGhldXMifQAAAAYAAABsYWJlbHMAAPj+//8IAAAAhAAAAHkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoicHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9XCIyMDBcIiwgaGFuZGxlcj1cIi9hcGkvdjEvcXVlcnlfcmFuZ2VcIiwgam9iPVwicHJvbWV0aGV1c1wifSJ9AAAABgAAAGNvbmZpZwAAAAAAAFb///8AAAIABQAAAFZhbHVlABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAeAAAAIAAAAAAAAAKgAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAcAAAAEQAAAHsiaW50ZXJ2YWwiOjEwMDB9AAAABgAAAGNvbmZpZwAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAAD/////uAAAABQAAAAAAAAADAAWABQAEwAMAAQADAAAADAAAAAAAAAAFAAAAAAAAAMEAAoAGAAMAAgABAAKAAAAFAAAAFgAAAADAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAYAAAAAAAAAAAAAAACAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAADAGGob1CnJFsDiBFfUKckWwKyfktQpyRYAAAAAAAA1QAAAAAAAAEBAAAAAAACARUAQAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAPAAAAAAABAABAAAAyAMAAAAAAADAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAwAAAAIAAQACgAAAAgAAAAwAQAAAwAAAKQAAAAoAAAABAAAAOT8//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAABP3//wgAAABkAAAAWwAAAHByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPSIyMDAiLCBoYW5kbGVyPSIvYXBpL3YxL3F1ZXJ5X3JhbmdlIiwgam9iPSJwcm9tZXRoZXVzIn0ABAAAAG5hbWUAAAAAfP3//wgAAABwAAAAZAAAAHsidHlwZSI6InRpbWVzZXJpZXMtbWFueSIsImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoibWF0cml4In0sImV4ZWN1dGVkUXVlcnlTdHJpbmciOiJFeHByOiBcblN0ZXA6IDFzIn0AAAAABAAAAG1ldGEAAAAAAgAAALQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAGgBAABoAQAAAAADAWgBAAADAAAAvAAAACwAAAAEAAAASP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAbP7//wgAAAB4AAAAbQAAAHsiX19uYW1lX18iOiJwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWwiLCJjb2RlIjoiMjAwIiwiaGFuZGxlciI6Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCJqb2IiOiJwcm9tZXRoZXVzIn0AAAAGAAAAbGFiZWxzAAD4/v//CAAAAIQAAAB5AAAAeyJkaXNwbGF5TmFtZUZyb21EUyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPVwiMjAwXCIsIGhhbmRsZXI9XCIvYXBpL3YxL3F1ZXJ5X3JhbmdlXCIsIGpvYj1cInByb21ldGhldXNcIn0ifQAAAAYAAABjb25maWcAAAAAAABW////AAACAAUAAABWYWx1ZQASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHgAAACAAAAAAAAACoAAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAIAAwACAAEAAgAAAAIAAAAHAAAABEAAAB7ImludGVydmFsIjoxMDAwfQAAAAYAAABjb25maWcAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAA6AMAAEFSUk9XMQ==
FRAME=QVJST1cxAAD/////uAMAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAADABAAADAAAApAAAACgAAAAEAAAA5Pz//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAAAE/f//CAAAAGQAAABbAAAAcHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9IjQwMCIsIGhhbmRsZXI9Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCBqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAAB8/f//CAAAAHAAAABkAAAAeyJ0eXBlIjoidGltZXNlcmllcy1tYW55IiwiY3VzdG9tIjp7InJlc3VsdFR5cGUiOiJtYXRyaXgifSwiZXhlY3V0ZWRRdWVyeVN0cmluZyI6IkV4cHI6IFxuU3RlcDogMXMifQAAAAAEAAAAbWV0YQAAAAACAAAAtAEAABgAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAaAEAAGgBAAAAAAMBaAEAAAMAAAC8AAAALAAAAAQAAABI/v//CAAAABAAAAAFAAAAVmFsdWUAAAAEAAAAbmFtZQAAAABs/v//CAAAAHgAAABtAAAAeyJfX25hbWVfXyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbCIsImNvZGUiOiI0MDAiLCJoYW5kbGVyIjoiL2FwaS92MS9xdWVyeV9yYW5nZSIsImpvYiI6InByb21ldGhldXMifQAAAAYAAABsYWJlbHMAAPj+//8IAAAAhAAAAHkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoicHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9XCI0MDBcIiwgaGFuZGxlcj1cIi9hcGkvdjEvcXVlcnlfcmFuZ2VcIiwgam9iPVwicHJvbWV0aGV1c1wifSJ9AAAABgAAAGNvbmZpZwAAAAAAAFb///8AAAIABQAAAFZhbHVlABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAeAAAAIAAAAAAAAAKgAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAcAAAAEQAAAHsiaW50ZXJ2YWwiOjEwMDB9AAAABgAAAGNvbmZpZwAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAAD/////uAAAABQAAAAAAAAADAAWABQAEwAMAAQADAAAADAAAAAAAAAAFAAAAAAAAAMEAAoAGAAMAAgABAAKAAAAFAAAAFgAAAADAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAYAAAAAAAAAAAAAAACAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAADAGGob1CnJFsDiBFfUKckWwKyfktQpyRYAAAAAAABLQAAAAAAAQFBAAAAAAAAAU0AQAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAPAAAAAAABAABAAAAyAMAAAAAAADAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAwAAAAIAAQACgAAAAgAAAAwAQAAAwAAAKQAAAAoAAAABAAAAOT8//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAABP3//wgAAABkAAAAWwAAAHByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPSI0MDAiLCBoYW5kbGVyPSIvYXBpL3YxL3F1ZXJ5X3JhbmdlIiwgam9iPSJwcm9tZXRoZXVzIn0ABAAAAG5hbWUAAAAAfP3//wgAAABwAAAAZAAAAHsidHlwZSI6InRpbWVzZXJpZXMtbWFueSIsImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoibWF0cml4In0sImV4ZWN1dGVkUXVlcnlTdHJpbmciOiJFeHByOiBcblN0ZXA6IDFzIn0AAAAABAAAAG1ldGEAAAAAAgAAALQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAGgBAABoAQAAAAADAWgBAAADAAAAvAAAACwAAAAEAAAASP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAbP7//wgAAAB4AAAAbQAAAHsiX19uYW1lX18iOiJwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWwiLCJjb2RlIjoiNDAwIiwiaGFuZGxlciI6Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCJqb2IiOiJwcm9tZXRoZXVzIn0AAAAGAAAAbGFiZWxzAAD4/v//CAAAAIQAAAB5AAAAeyJkaXNwbGF5TmFtZUZyb21EUyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPVwiNDAwXCIsIGhhbmRsZXI9XCIvYXBpL3YxL3F1ZXJ5X3JhbmdlXCIsIGpvYj1cInByb21ldGhldXNcIn0ifQAAAAYAAABjb25maWcAAAAAAABW////AAACAAUAAABWYWx1ZQASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHgAAACAAAAAAAAACoAAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAIAAwACAAEAAgAAAAIAAAAHAAAABEAAAB7ImludGVydmFsIjoxMDAwfQAAAAYAAABjb25maWcAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAA6AMAAEFSUk9XMQ==

View File

@ -0,0 +1,112 @@
{
"frames": [
{
"schema": {
"name": "prometheus_http_requests_total{code=\"200\", handler=\"/api/v1/query_range\", job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: \nStep: 1s"
},
"fields": [
{
"name": "Time",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"interval": 1000
}
},
{
"name": "Value",
"type": "number",
"typeInfo": {
"frame": "float64"
},
"labels": {
"__name__": "prometheus_http_requests_total",
"code": "200",
"handler": "/api/v1/query_range",
"job": "prometheus"
},
"config": {
"displayNameFromDS": "prometheus_http_requests_total{code=\"200\", handler=\"/api/v1/query_range\", job=\"prometheus\"}"
}
}
]
},
"data": {
"values": [
[
1641889530123,
1641889531123,
1641889532123
],
[
21,
32,
43
]
]
}
},
{
"schema": {
"name": "prometheus_http_requests_total{code=\"400\", handler=\"/api/v1/query_range\", job=\"prometheus\"}",
"meta": {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: \nStep: 1s"
},
"fields": [
{
"name": "Time",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"interval": 1000
}
},
{
"name": "Value",
"type": "number",
"typeInfo": {
"frame": "float64"
},
"labels": {
"__name__": "prometheus_http_requests_total",
"code": "400",
"handler": "/api/v1/query_range",
"job": "prometheus"
},
"config": {
"displayNameFromDS": "prometheus_http_requests_total{code=\"400\", handler=\"/api/v1/query_range\", job=\"prometheus\"}"
}
}
]
},
"data": {
"values": [
[
1641889530123,
1641889531123,
1641889532123
],
[
54,
65,
76
]
]
}
}
]
}

View File

@ -0,0 +1,46 @@
🌟 This was machine generated. Do not edit. 🌟
Frame[0] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: \nStep: 1s"
}
Name: prometheus_http_requests_total{code="200", handler="/api/v1/query_range", job="prometheus"}
Dimensions: 2 Fields by 3 Rows
+-----------------------------------+--------------------------------------------------------------------------------------------------------+
| Name: Time | Name: Value |
| Labels: | Labels: __name__=prometheus_http_requests_total, code=200, handler=/api/v1/query_range, job=prometheus |
| Type: []time.Time | Type: []float64 |
+-----------------------------------+--------------------------------------------------------------------------------------------------------+
| 2022-01-11 08:25:30.123 +0000 UTC | 21 |
| 2022-01-11 08:25:31.123 +0000 UTC | 32 |
| 2022-01-11 08:25:32.123 +0000 UTC | 43 |
+-----------------------------------+--------------------------------------------------------------------------------------------------------+
Frame[1] {
"type": "timeseries-many",
"custom": {
"resultType": "matrix"
},
"executedQueryString": "Expr: \nStep: 1s"
}
Name: prometheus_http_requests_total{code="400", handler="/api/v1/query_range", job="prometheus"}
Dimensions: 2 Fields by 3 Rows
+-----------------------------------+--------------------------------------------------------------------------------------------------------+
| Name: Time | Name: Value |
| Labels: | Labels: __name__=prometheus_http_requests_total, code=400, handler=/api/v1/query_range, job=prometheus |
| Type: []time.Time | Type: []float64 |
+-----------------------------------+--------------------------------------------------------------------------------------------------------+
| 2022-01-11 08:25:30.123 +0000 UTC | 54 |
| 2022-01-11 08:25:31.123 +0000 UTC | 65 |
| 2022-01-11 08:25:32.123 +0000 UTC | 76 |
+-----------------------------------+--------------------------------------------------------------------------------------------------------+
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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
FRAME=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

View File

@ -204,7 +204,7 @@ func readArrayData(iter *jsoniter.Iterator) *backend.DataResponse {
}
}
if rsp.Frames == nil || stringField.Len() > 0 {
if stringField.Len() > 0 {
rsp.Frames = append(rsp.Frames, data.NewFrame("", stringField))
}
@ -232,9 +232,9 @@ func readLabelsOrExemplars(iter *jsoniter.Iterator) (*data.Frame, [][2]string) {
case "exemplars":
lookup := make(map[string]*data.Field)
timeField := data.NewFieldFromFieldType(data.FieldTypeTime, 0)
timeField.Name = data.TimeSeriesTimeFieldName
valueField := data.NewFieldFromFieldType(data.FieldTypeFloat64, 0)
valueField.Name = labels["__name__"]
delete(labels, "__name__")
valueField.Name = data.TimeSeriesValueFieldName
valueField.Labels = labels
frame = data.NewFrame("", timeField, valueField)
frame.Meta = &data.FrameMeta{

View File

@ -9,18 +9,20 @@
},
"fields": [
{
"name": "Time",
"type": "time",
"typeInfo": {
"frame": "time.Time"
}
},
{
"name": "test_exemplar_metric_total",
"name": "Value",
"type": "number",
"typeInfo": {
"frame": "float64"
},
"labels": {
"__name__": "test_exemplar_metric_total",
"instance": "localhost:8090",
"job": "prometheus",
"service": "bar"
@ -68,18 +70,20 @@
},
"fields": [
{
"name": "Time",
"type": "time",
"typeInfo": {
"frame": "time.Time"
}
},
{
"name": "test_exemplar_metric_total",
"name": "Value",
"type": "number",
"typeInfo": {
"frame": "float64"
},
"labels": {
"__name__": "test_exemplar_metric_total",
"instance": "localhost:8090",
"job": "prometheus",
"service": "foo"

View File

@ -7,13 +7,13 @@ Frame[0] {
}
Name:
Dimensions: 4 Fields by 1 Rows
+-----------------------------------+--------------------------------------------------------------+------------------+----------------+
| Name: | Name: test_exemplar_metric_total | Name: traceID | Name: a |
| Labels: | Labels: instance=localhost:8090, job=prometheus, service=bar | Labels: | Labels: |
| Type: []time.Time | Type: []float64 | Type: []string | Type: []string |
+-----------------------------------+--------------------------------------------------------------+------------------+----------------+
| 2020-09-14 15:22:25.479 +0000 UTC | 6 | EpTxMJ40fUus7aGY | not in next |
+-----------------------------------+--------------------------------------------------------------+------------------+----------------+
+-----------------------------------+---------------------------------------------------------------------------------------------------+------------------+----------------+
| Name: Time | Name: Value | Name: traceID | Name: a |
| Labels: | Labels: __name__=test_exemplar_metric_total, instance=localhost:8090, job=prometheus, service=bar | Labels: | Labels: |
| Type: []time.Time | Type: []float64 | Type: []string | Type: []string |
+-----------------------------------+---------------------------------------------------------------------------------------------------+------------------+----------------+
| 2020-09-14 15:22:25.479 +0000 UTC | 6 | EpTxMJ40fUus7aGY | not in next |
+-----------------------------------+---------------------------------------------------------------------------------------------------+------------------+----------------+
@ -24,16 +24,16 @@ Frame[1] {
}
Name:
Dimensions: 3 Fields by 2 Rows
+-----------------------------------+--------------------------------------------------------------+------------------+
| Name: | Name: test_exemplar_metric_total | Name: traceID |
| Labels: | Labels: instance=localhost:8090, job=prometheus, service=foo | Labels: |
| Type: []time.Time | Type: []float64 | Type: []string |
+-----------------------------------+--------------------------------------------------------------+------------------+
| 2020-09-14 15:22:35.479 +0000 UTC | 19 | Olp9XHlq763ccsfa |
| 2020-09-14 15:22:45.489 +0000 UTC | 20 | hCtjygkIHwAN9vs4 |
+-----------------------------------+--------------------------------------------------------------+------------------+
+-----------------------------------+---------------------------------------------------------------------------------------------------+------------------+
| Name: Time | Name: Value | Name: traceID |
| Labels: | Labels: __name__=test_exemplar_metric_total, instance=localhost:8090, job=prometheus, service=foo | Labels: |
| Type: []time.Time | Type: []float64 | Type: []string |
+-----------------------------------+---------------------------------------------------------------------------------------------------+------------------+
| 2020-09-14 15:22:35.479 +0000 UTC | 19 | Olp9XHlq763ccsfa |
| 2020-09-14 15:22:45.489 +0000 UTC | 20 | hCtjygkIHwAN9vs4 |
+-----------------------------------+---------------------------------------------------------------------------------------------------+------------------+
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=QVJST1cxAAD/////6AIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAJgAAAADAAAATAAAACgAAAAEAAAAoP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADA/f//CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAOD9//8IAAAAMAAAACQAAAB7ImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoiZXhlbXBsYXIifX0AAAAABAAAAG1ldGEAAAAABAAAALgBAAC4AAAAWAAAAAQAAABq/v//FAAAADgAAAA4AAAAAAAABTQAAAABAAAABAAAAFj+//8IAAAADAAAAAEAAABhAAAABAAAAG5hbWUAAAAAAAAAAKz///8BAAAAYQAAALr+//8UAAAAPAAAAEAAAAAAAAAFPAAAAAEAAAAEAAAAqP7//wgAAAAQAAAABwAAAHRyYWNlSUQABAAAAG5hbWUAAAAAAAAAAAQABAAEAAAABwAAAHRyYWNlSUQAFv///xQAAAC0AAAAtAAAAAAAAAO0AAAAAgAAAEAAAAAEAAAACP///wgAAAAkAAAAGgAAAHRlc3RfZXhlbXBsYXJfbWV0cmljX3RvdGFsAAAEAAAAbmFtZQAAAABA////CAAAAEwAAABAAAAAeyJpbnN0YW5jZSI6ImxvY2FsaG9zdDo4MDkwIiwiam9iIjoicHJvbWV0aGV1cyIsInNlcnZpY2UiOiJiYXIifQAAAAAGAAAAbGFiZWxzAAAAAAAAdv///wAAAgAaAAAAdGVzdF9leGVtcGxhcl9tZXRyaWNfdG90YWwAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAApIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAAAAAAAAAAAP////84AQAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAQAAAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAuAAAAAEAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAYAAAAAAAAABAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAACAAAAAAAAAAwAAAAAAAAAAsAAAAAAAAAAAAAAAQAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAwJ92ubGvNBYAAAAAAAAYQAAAAAAQAAAARXBUeE1KNDBmVXVzN2FHWQAAAAALAAAAbm90IGluIG5leHQAAAAAABAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAAEAAEAAAD4AgAAAAAAAEABAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAJgAAAADAAAATAAAACgAAAAEAAAAoP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADA/f//CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAOD9//8IAAAAMAAAACQAAAB7ImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoiZXhlbXBsYXIifX0AAAAABAAAAG1ldGEAAAAABAAAALgBAAC4AAAAWAAAAAQAAABq/v//FAAAADgAAAA4AAAAAAAABTQAAAABAAAABAAAAFj+//8IAAAADAAAAAEAAABhAAAABAAAAG5hbWUAAAAAAAAAAKz///8BAAAAYQAAALr+//8UAAAAPAAAAEAAAAAAAAAFPAAAAAEAAAAEAAAAqP7//wgAAAAQAAAABwAAAHRyYWNlSUQABAAAAG5hbWUAAAAAAAAAAAQABAAEAAAABwAAAHRyYWNlSUQAFv///xQAAAC0AAAAtAAAAAAAAAO0AAAAAgAAAEAAAAAEAAAACP///wgAAAAkAAAAGgAAAHRlc3RfZXhlbXBsYXJfbWV0cmljX3RvdGFsAAAEAAAAbmFtZQAAAABA////CAAAAEwAAABAAAAAeyJpbnN0YW5jZSI6ImxvY2FsaG9zdDo4MDkwIiwiam9iIjoicHJvbWV0aGV1cyIsInNlcnZpY2UiOiJiYXIifQAAAAAGAAAAbGFiZWxzAAAAAAAAdv///wAAAgAaAAAAdGVzdF9leGVtcGxhcl9tZXRyaWNfdG90YWwAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAApIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAAAAAAAAAAABgDAABBUlJPVzE=
FRAME=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
FRAME=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
FRAME=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