datasource: testdata - add predictable pulse scenario (#18142)
Adds pulse waveform. Is predictable in the sense that the start of the waveform is aligned to epoch time (instead of the start of the query time). This makes a useful signal for manual testing of alerting in the devenv.
This commit is contained in:
Vendored
+106
@@ -9,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/null"
|
"github.com/grafana/grafana/pkg/components/null"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
@@ -102,6 +104,15 @@ func init() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
registerScenario(&Scenario{
|
||||||
|
Id: "predictable_pulse",
|
||||||
|
Name: "Predictable Pulse",
|
||||||
|
Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
|
||||||
|
return getPredictablePulse(query, context)
|
||||||
|
},
|
||||||
|
Description: PredictablePulseDesc,
|
||||||
|
})
|
||||||
|
|
||||||
registerScenario(&Scenario{
|
registerScenario(&Scenario{
|
||||||
Id: "random_walk_table",
|
Id: "random_walk_table",
|
||||||
Name: "Random Walk Table",
|
Name: "Random Walk Table",
|
||||||
@@ -342,6 +353,101 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PredictablePulseDesc is the description for the Predictable Pulse scenerio.
|
||||||
|
const PredictablePulseDesc = `Predictable Pulse returns a pulse wave where there is a datapoint every timeStepSeconds.
|
||||||
|
The wave cycles at timeStepSeconds*(onCount+offCount).
|
||||||
|
The cycle of the wave is based off of absolute time (from the epoch) which makes it predictable.
|
||||||
|
Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means times will all end in :00 seconds).`
|
||||||
|
|
||||||
|
func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
|
||||||
|
queryRes := tsdb.NewQueryResult()
|
||||||
|
|
||||||
|
// Process Input
|
||||||
|
var timeStep int64
|
||||||
|
var onCount int64
|
||||||
|
var offCount int64
|
||||||
|
var onValue null.Float
|
||||||
|
var offValue null.Float
|
||||||
|
|
||||||
|
options := query.Model.Get("pulseWave")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if timeStep, err = options.Get("timeStep").Int64(); err != nil {
|
||||||
|
queryRes.Error = fmt.Errorf("failed to parse timeStep value '%v' into integer: %v", options.Get("timeStep"), err)
|
||||||
|
return queryRes
|
||||||
|
}
|
||||||
|
if onCount, err = options.Get("onCount").Int64(); err != nil {
|
||||||
|
queryRes.Error = fmt.Errorf("failed to parse onCount value '%v' into integer: %v", options.Get("onCount"), err)
|
||||||
|
return queryRes
|
||||||
|
}
|
||||||
|
if offCount, err = options.Get("offCount").Int64(); err != nil {
|
||||||
|
queryRes.Error = fmt.Errorf("failed to parse offCount value '%v' into integer: %v", options.Get("offCount"), err)
|
||||||
|
return queryRes
|
||||||
|
}
|
||||||
|
|
||||||
|
fromStringOrNumber := func(val *simplejson.Json) (null.Float, error) {
|
||||||
|
switch v := val.Interface().(type) {
|
||||||
|
case json.Number:
|
||||||
|
fV, err := v.Float64()
|
||||||
|
if err != nil {
|
||||||
|
return null.Float{}, err
|
||||||
|
}
|
||||||
|
return null.FloatFrom(fV), nil
|
||||||
|
case string:
|
||||||
|
if v == "null" {
|
||||||
|
return null.FloatFromPtr(nil), nil
|
||||||
|
}
|
||||||
|
fV, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err != nil {
|
||||||
|
return null.Float{}, err
|
||||||
|
}
|
||||||
|
return null.FloatFrom(fV), nil
|
||||||
|
default:
|
||||||
|
return null.Float{}, fmt.Errorf("failed to extract value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onValue, err = fromStringOrNumber(options.Get("onValue"))
|
||||||
|
if err != nil {
|
||||||
|
queryRes.Error = fmt.Errorf("failed to parse onValue value '%v' into float: %v", options.Get("onValue"), err)
|
||||||
|
return queryRes
|
||||||
|
}
|
||||||
|
offValue, err = fromStringOrNumber(options.Get("offValue"))
|
||||||
|
if err != nil {
|
||||||
|
queryRes.Error = fmt.Errorf("failed to parse offValue value '%v' into float: %v", options.Get("offValue"), err)
|
||||||
|
return queryRes
|
||||||
|
}
|
||||||
|
|
||||||
|
from := context.TimeRange.GetFromAsMsEpoch()
|
||||||
|
to := context.TimeRange.GetToAsMsEpoch()
|
||||||
|
|
||||||
|
series := newSeriesForQuery(query)
|
||||||
|
points := make(tsdb.TimeSeriesPoints, 0)
|
||||||
|
|
||||||
|
timeStep = timeStep * 1000 // Seconds to Milliseconds
|
||||||
|
timeCursor := from - (from % timeStep) // Truncate Start
|
||||||
|
wavePeriod := timeStep * (onCount + offCount)
|
||||||
|
maxPoints := 10000 // Don't return too many points
|
||||||
|
|
||||||
|
onFor := func(mod int64) null.Float { // How many items in the cycle should get the on value
|
||||||
|
var i int64
|
||||||
|
for i = 0; i < onCount; i++ {
|
||||||
|
if mod == i*timeStep {
|
||||||
|
return onValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offValue
|
||||||
|
}
|
||||||
|
for i := 0; i < maxPoints && timeCursor < to; i++ {
|
||||||
|
point := tsdb.NewTimePoint(onFor(timeCursor%wavePeriod), float64(timeCursor))
|
||||||
|
points = append(points, point)
|
||||||
|
timeCursor += timeStep
|
||||||
|
}
|
||||||
|
|
||||||
|
series.Points = points
|
||||||
|
queryRes.Series = append(queryRes.Series, series)
|
||||||
|
return queryRes
|
||||||
|
}
|
||||||
|
|
||||||
func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
|
func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
|
||||||
timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
|
timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
|
||||||
to := tsdbQuery.TimeRange.GetToAsMsEpoch()
|
to := tsdbQuery.TimeRange.GetToAsMsEpoch()
|
||||||
|
|||||||
@@ -116,4 +116,68 @@
|
|||||||
<gf-form-switch class="gf-form" label="Level" label-class="query-keyword width-5" checked="ctrl.target.levelColumn" switch-class="max-width-6" on-change="ctrl.refresh()"></gf-form-switch>
|
<gf-form-switch class="gf-form" label="Level" label-class="query-keyword width-5" checked="ctrl.target.levelColumn" switch-class="max-width-6" on-change="ctrl.refresh()"></gf-form-switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Predictable Pulse Scenario Options Form -->
|
||||||
|
<div class="gf-form-inline" ng-if="ctrl.scenario.id === 'predictable_pulse'">
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword width-7">
|
||||||
|
Step
|
||||||
|
<info-popover mode="right-normal">The number of seconds between datapoints.</info-popover>
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
class="gf-form-input width-5"
|
||||||
|
placeholder="60"
|
||||||
|
ng-model="ctrl.target.pulseWave.timeStep"
|
||||||
|
ng-change="ctrl.refresh()"
|
||||||
|
ng-model-onblur />
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword width-7">
|
||||||
|
On Count
|
||||||
|
<info-popover mode="right-normal">The number of values within a cycle, at the start of the cycle, that should have the onValue.</info-popover>
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
class="gf-form-input width-3"
|
||||||
|
placeholder="3"
|
||||||
|
ng-model="ctrl.target.pulseWave.onCount"
|
||||||
|
ng-change="ctrl.refresh()"
|
||||||
|
ng-model-onblur />
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword width-7">
|
||||||
|
Off Count
|
||||||
|
<info-popover mode="right-normal">The number of offValues within the cycle.</info-popover>
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
class="gf-form-input width-3"
|
||||||
|
placeholder="6"
|
||||||
|
ng-model="ctrl.target.pulseWave.offCount"
|
||||||
|
ng-change="ctrl.refresh()"
|
||||||
|
ng-model-onblur />
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword width-7">
|
||||||
|
On Value
|
||||||
|
<info-popover mode="right-normal">The value for "on values", may be a int, float, or null.</info-popover>
|
||||||
|
</label>
|
||||||
|
<input type="string"
|
||||||
|
class="gf-form-input width-5"
|
||||||
|
placeholder="1"
|
||||||
|
ng-model="ctrl.target.pulseWave.onValue"
|
||||||
|
ng-change="ctrl.refresh()"
|
||||||
|
ng-model-onblur />
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<label class="gf-form-label query-keyword width-7">
|
||||||
|
Off Value
|
||||||
|
<info-popover mode="right-normal">The value for "off values", may be a int, float, or null.</info-popover>
|
||||||
|
</label>
|
||||||
|
<input type="string"
|
||||||
|
class="gf-form-input width-5"
|
||||||
|
placeholder="1"
|
||||||
|
ng-model="ctrl.target.pulseWave.offValue"
|
||||||
|
ng-change="ctrl.refresh()"
|
||||||
|
ng-model-onblur />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</query-editor-row>
|
</query-editor-row>
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ import { defaultQuery } from './StreamHandler';
|
|||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
|
|
||||||
|
export const defaultPulse: any = {
|
||||||
|
timeStep: 60,
|
||||||
|
onCount: 3,
|
||||||
|
onValue: 2,
|
||||||
|
offCount: 3,
|
||||||
|
offValue: 1,
|
||||||
|
};
|
||||||
|
|
||||||
export class TestDataQueryCtrl extends QueryCtrl {
|
export class TestDataQueryCtrl extends QueryCtrl {
|
||||||
static templateUrl = 'partials/query.editor.html';
|
static templateUrl = 'partials/query.editor.html';
|
||||||
|
|
||||||
@@ -75,6 +83,12 @@ export class TestDataQueryCtrl extends QueryCtrl {
|
|||||||
delete this.target.stream;
|
delete this.target.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.target.scenarioId === 'predictable_pulse') {
|
||||||
|
this.target.pulseWave = _.defaults(this.target.pulseWave || {}, defaultPulse);
|
||||||
|
} else {
|
||||||
|
delete this.target.pulseWave;
|
||||||
|
}
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user