|
|
@@ -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"
|
|
|
@@ -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{
|
|
|
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()
|