mirror of
https://github.com/grafana/grafana.git
synced 2025-01-04 13:17:16 -06:00
datasource: testdata - add predicatable csv wave scenario (#18183)
This commit is contained in:
parent
e47546d529
commit
7cac393ddc
@ -42,6 +42,20 @@ func FloatFromPtr(f *float64) Float {
|
||||
return NewFloat(*f, true)
|
||||
}
|
||||
|
||||
// FloatFromString creates a new Float from string f.
|
||||
// If the string is equal to the value of nullString then the Float will be null.
|
||||
// An empty string f will return an error.
|
||||
func FloatFromString(f string, nullString string) (Float, error) {
|
||||
if f == nullString {
|
||||
return FloatFromPtr(nil), nil
|
||||
}
|
||||
fV, err := strconv.ParseFloat(f, 64)
|
||||
if err != nil {
|
||||
return Float{}, err
|
||||
}
|
||||
return FloatFrom(fV), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
// It supports number and null input.
|
||||
// 0 will not be considered a null Float.
|
||||
|
144
pkg/tsdb/testdata/scenarios.go
vendored
144
pkg/tsdb/testdata/scenarios.go
vendored
@ -113,6 +113,14 @@ func init() {
|
||||
Description: PredictablePulseDesc,
|
||||
})
|
||||
|
||||
registerScenario(&Scenario{
|
||||
Id: "predictable_csv_wave",
|
||||
Name: "Predictable CSV Wave",
|
||||
Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
|
||||
return getPredictableCSVWave(query, context)
|
||||
},
|
||||
})
|
||||
|
||||
registerScenario(&Scenario{
|
||||
Id: "random_walk_table",
|
||||
Name: "Random Walk Table",
|
||||
@ -385,27 +393,6 @@ func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.Query
|
||||
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)
|
||||
@ -417,35 +404,97 @@ func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.Query
|
||||
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
|
||||
timeStep = timeStep * 1000 // Seconds to Milliseconds
|
||||
onFor := func(mod int64) (null.Float, error) { // 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 onValue, nil
|
||||
}
|
||||
}
|
||||
return offValue
|
||||
return offValue, nil
|
||||
}
|
||||
points, err := predictableSeries(context.TimeRange, timeStep, onCount+offCount, onFor)
|
||||
if err != nil {
|
||||
queryRes.Error = err
|
||||
return queryRes
|
||||
}
|
||||
|
||||
series := newSeriesForQuery(query)
|
||||
series.Points = *points
|
||||
queryRes.Series = append(queryRes.Series, series)
|
||||
return queryRes
|
||||
}
|
||||
|
||||
func getPredictableCSVWave(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
|
||||
queryRes := tsdb.NewQueryResult()
|
||||
|
||||
// Process Input
|
||||
var timeStep int64
|
||||
|
||||
options := query.Model.Get("csvWave")
|
||||
|
||||
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
|
||||
}
|
||||
rawValues := options.Get("valuesCSV").MustString()
|
||||
rawValues = strings.TrimRight(strings.TrimSpace(rawValues), ",") // Strip Trailing Comma
|
||||
rawValesCSV := strings.Split(rawValues, ",")
|
||||
values := make([]null.Float, len(rawValesCSV))
|
||||
for i, rawValue := range rawValesCSV {
|
||||
val, err := null.FloatFromString(strings.TrimSpace(rawValue), "null")
|
||||
if err != nil {
|
||||
queryRes.Error = fmt.Errorf("failed to parse value '%v' into nullable float: err", rawValue, err)
|
||||
return queryRes
|
||||
}
|
||||
values[i] = val
|
||||
}
|
||||
|
||||
timeStep = timeStep * 1000 // Seconds to Milliseconds
|
||||
valuesLen := int64(len(values))
|
||||
getValue := func(mod int64) (null.Float, error) {
|
||||
var i int64
|
||||
for i = 0; i < valuesLen; i++ {
|
||||
if mod == i*timeStep {
|
||||
return values[i], nil
|
||||
}
|
||||
}
|
||||
return null.Float{}, fmt.Errorf("did not get value at point in waveform - should not be here")
|
||||
}
|
||||
points, err := predictableSeries(context.TimeRange, timeStep, valuesLen, getValue)
|
||||
if err != nil {
|
||||
queryRes.Error = err
|
||||
return queryRes
|
||||
}
|
||||
|
||||
series := newSeriesForQuery(query)
|
||||
series.Points = *points
|
||||
queryRes.Series = append(queryRes.Series, series)
|
||||
return queryRes
|
||||
}
|
||||
|
||||
func predictableSeries(timeRange *tsdb.TimeRange, timeStep, length int64, getValue func(mod int64) (null.Float, error)) (*tsdb.TimeSeriesPoints, error) {
|
||||
points := make(tsdb.TimeSeriesPoints, 0)
|
||||
|
||||
from := timeRange.GetFromAsMsEpoch()
|
||||
to := timeRange.GetToAsMsEpoch()
|
||||
|
||||
timeCursor := from - (from % timeStep) // Truncate Start
|
||||
wavePeriod := timeStep * length
|
||||
maxPoints := 10000 // Don't return too many points
|
||||
|
||||
for i := 0; i < maxPoints && timeCursor < to; i++ {
|
||||
point := tsdb.NewTimePoint(onFor(timeCursor%wavePeriod), float64(timeCursor))
|
||||
val, err := getValue(timeCursor % wavePeriod)
|
||||
if err != nil {
|
||||
return &points, err
|
||||
}
|
||||
point := tsdb.NewTimePoint(val, float64(timeCursor))
|
||||
points = append(points, point)
|
||||
timeCursor += timeStep
|
||||
}
|
||||
|
||||
series.Points = points
|
||||
queryRes.Series = append(queryRes.Series, series)
|
||||
return queryRes
|
||||
return &points, nil
|
||||
}
|
||||
|
||||
func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
|
||||
@ -541,3 +590,18 @@ func newSeriesForQuery(query *tsdb.Query) *tsdb.TimeSeries {
|
||||
|
||||
return &tsdb.TimeSeries{Name: alias}
|
||||
}
|
||||
|
||||
func fromStringOrNumber(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:
|
||||
return null.FloatFromString(v, "null")
|
||||
default:
|
||||
return null.Float{}, fmt.Errorf("failed to extract value")
|
||||
}
|
||||
}
|
||||
|
@ -180,4 +180,32 @@
|
||||
ng-model-onblur />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Predictable CSV Wave Scenario Options Form -->
|
||||
<div class="gf-form-inline" ng-if="ctrl.scenario.id === 'predictable_csv_wave'">
|
||||
<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.csvWave.timeStep"
|
||||
ng-change="ctrl.refresh()"
|
||||
ng-model-onblur />
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<label class="gf-form-label query-keyword width-10">
|
||||
CSV Values
|
||||
<info-popover mode="right-normal">Comma separated values. Each value may be an int, float, or null and must not be empty. Whitespace and trailing commas are removed.</info-popover>
|
||||
</label>
|
||||
<input type="string"
|
||||
class="gf-form-input gf-form-label--grow"
|
||||
placeholder="1,2,3,2"
|
||||
ng-model="ctrl.target.csvWave.valuesCSV"
|
||||
ng-change="ctrl.refresh()"
|
||||
ng-model-onblur />
|
||||
</div>
|
||||
</div>
|
||||
</query-editor-row>
|
||||
|
@ -13,6 +13,11 @@ export const defaultPulse: any = {
|
||||
offValue: 1,
|
||||
};
|
||||
|
||||
export const defaultCSVWave: any = {
|
||||
timeStep: 60,
|
||||
valuesCSV: '0,0,2,2,1,1',
|
||||
};
|
||||
|
||||
export class TestDataQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'partials/query.editor.html';
|
||||
|
||||
@ -89,6 +94,12 @@ export class TestDataQueryCtrl extends QueryCtrl {
|
||||
delete this.target.pulseWave;
|
||||
}
|
||||
|
||||
if (this.target.scenarioId === 'predictable_csv_wave') {
|
||||
this.target.csvWave = _.defaults(this.target.csvWave || {}, defaultCSVWave);
|
||||
} else {
|
||||
delete this.target.csvWave;
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user