Commit 7cac393d by Kyle Brandt Committed by GitHub

datasource: testdata - add predicatable csv wave scenario (#18183)

parent e47546d5
...@@ -42,6 +42,20 @@ func FloatFromPtr(f *float64) Float { ...@@ -42,6 +42,20 @@ func FloatFromPtr(f *float64) Float {
return NewFloat(*f, true) 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. // UnmarshalJSON implements json.Unmarshaler.
// It supports number and null input. // It supports number and null input.
// 0 will not be considered a null Float. // 0 will not be considered a null Float.
......
...@@ -114,6 +114,14 @@ func init() { ...@@ -114,6 +114,14 @@ func init() {
}) })
registerScenario(&Scenario{ 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", Id: "random_walk_table",
Name: "Random Walk Table", Name: "Random Walk Table",
...@@ -385,27 +393,6 @@ func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.Query ...@@ -385,27 +393,6 @@ func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.Query
return queryRes 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")) onValue, err = fromStringOrNumber(options.Get("onValue"))
if err != nil { if err != nil {
queryRes.Error = fmt.Errorf("failed to parse onValue value '%v' into float: %v", options.Get("onValue"), err) queryRes.Error = fmt.Errorf("failed to parse onValue value '%v' into float: %v", options.Get("onValue"), err)
...@@ -417,37 +404,99 @@ func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.Query ...@@ -417,37 +404,99 @@ func getPredictablePulse(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.Query
return queryRes return queryRes
} }
from := context.TimeRange.GetFromAsMsEpoch() timeStep = timeStep * 1000 // Seconds to Milliseconds
to := context.TimeRange.GetToAsMsEpoch() 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, nil
}
}
return offValue, nil
}
points, err := predictableSeries(context.TimeRange, timeStep, onCount+offCount, onFor)
if err != nil {
queryRes.Error = err
return queryRes
}
series := newSeriesForQuery(query) series := newSeriesForQuery(query)
points := make(tsdb.TimeSeriesPoints, 0) series.Points = *points
queryRes.Series = append(queryRes.Series, series)
return queryRes
}
timeStep = timeStep * 1000 // Seconds to Milliseconds func getPredictableCSVWave(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
timeCursor := from - (from % timeStep) // Truncate Start queryRes := tsdb.NewQueryResult()
wavePeriod := timeStep * (onCount + offCount)
maxPoints := 10000 // Don't return too many points // 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
}
onFor := func(mod int64) null.Float { // How many items in the cycle should get the on value timeStep = timeStep * 1000 // Seconds to Milliseconds
valuesLen := int64(len(values))
getValue := func(mod int64) (null.Float, error) {
var i int64 var i int64
for i = 0; i < onCount; i++ { for i = 0; i < valuesLen; i++ {
if mod == i*timeStep { if mod == i*timeStep {
return onValue return values[i], nil
} }
} }
return offValue return null.Float{}, fmt.Errorf("did not get value at point in waveform - should not be here")
} }
for i := 0; i < maxPoints && timeCursor < to; i++ { points, err := predictableSeries(context.TimeRange, timeStep, valuesLen, getValue)
point := tsdb.NewTimePoint(onFor(timeCursor%wavePeriod), float64(timeCursor)) if err != nil {
points = append(points, point) queryRes.Error = err
timeCursor += timeStep return queryRes
} }
series.Points = points series := newSeriesForQuery(query)
series.Points = *points
queryRes.Series = append(queryRes.Series, series) queryRes.Series = append(queryRes.Series, series)
return queryRes 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++ {
val, err := getValue(timeCursor % wavePeriod)
if err != nil {
return &points, err
}
point := tsdb.NewTimePoint(val, float64(timeCursor))
points = append(points, point)
timeCursor += timeStep
}
return &points, nil
}
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()
...@@ -541,3 +590,18 @@ func newSeriesForQuery(query *tsdb.Query) *tsdb.TimeSeries { ...@@ -541,3 +590,18 @@ func newSeriesForQuery(query *tsdb.Query) *tsdb.TimeSeries {
return &tsdb.TimeSeries{Name: alias} 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 @@ ...@@ -180,4 +180,32 @@
ng-model-onblur /> ng-model-onblur />
</div> </div>
</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> </query-editor-row>
...@@ -13,6 +13,11 @@ export const defaultPulse: any = { ...@@ -13,6 +13,11 @@ export const defaultPulse: any = {
offValue: 1, offValue: 1,
}; };
export const defaultCSVWave: any = {
timeStep: 60,
valuesCSV: '0,0,2,2,1,1',
};
export class TestDataQueryCtrl extends QueryCtrl { export class TestDataQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html'; static templateUrl = 'partials/query.editor.html';
...@@ -89,6 +94,12 @@ export class TestDataQueryCtrl extends QueryCtrl { ...@@ -89,6 +94,12 @@ export class TestDataQueryCtrl extends QueryCtrl {
delete this.target.pulseWave; 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(); 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