Commit ed099d5c by Kyle Brandt Committed by GitHub

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.
 
parent f38187d6
......@@ -9,6 +9,8 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb"
......@@ -103,6 +105,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{
Id: "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 {
timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
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>
</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>
......@@ -5,6 +5,14 @@ import { defaultQuery } from './StreamHandler';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { dateTime } from '@grafana/data';
export const defaultPulse: any = {
timeStep: 60,
onCount: 3,
onValue: 2,
offCount: 3,
offValue: 1,
};
export class TestDataQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
......@@ -75,6 +83,12 @@ export class TestDataQueryCtrl extends QueryCtrl {
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();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment