SSE: Refactor to simplify Series type (#35063)

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Kyle Brandt 2021-06-02 12:29:19 -04:00 committed by GitHub
parent 134dba5101
commit 4093fae99a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 517 additions and 849 deletions

View File

@ -38,7 +38,7 @@ func (cr classicReducer) Reduce(series mathexp.Series) mathexp.Number {
value := float64(0)
allNull := true
vF := series.Frame.Fields[series.ValueIdx]
vF := series.Frame.Fields[1]
ff := mathexp.Float64Field(*vF)
switch cr {

View File

@ -399,9 +399,9 @@ func TestPercentDiffAbsReducer(t *testing.T) {
}
func valBasedSeries(vals ...*float64) mathexp.Series {
newSeries := mathexp.NewSeries("", nil, 0, false, 1, true, len(vals))
newSeries := mathexp.NewSeries("", nil, len(vals))
for idx, f := range vals {
err := newSeries.SetPoint(idx, unixTimePointer(int64(idx)), f)
err := newSeries.SetPoint(idx, time.Unix(int64(idx), 0), f)
if err != nil {
panic(err)
}
@ -410,9 +410,9 @@ func valBasedSeries(vals ...*float64) mathexp.Series {
}
func valBasedSeriesWithLabels(l data.Labels, vals ...*float64) mathexp.Series {
newSeries := mathexp.NewSeries("", l, 0, false, 1, true, len(vals))
newSeries := mathexp.NewSeries("", l, len(vals))
for idx, f := range vals {
err := newSeries.SetPoint(idx, unixTimePointer(int64(idx)), f)
err := newSeries.SetPoint(idx, time.Unix(int64(idx), 0), f)
if err != nil {
panic(err)
}
@ -420,11 +420,6 @@ func valBasedSeriesWithLabels(l data.Labels, vals ...*float64) mathexp.Series {
return newSeries
}
func unixTimePointer(sec int64) *time.Time {
t := time.Unix(sec, 0)
return &t
}
func valBasedNumber(f *float64) mathexp.Number {
newNumber := mathexp.NewNumber("", nil)
newNumber.SetValue(f)

View File

@ -129,7 +129,7 @@ func (e *State) walkUnary(node *parse.UnaryNode) (Results, error) {
}
func (e *State) unarySeries(s Series, op string) (Series, error) {
newSeries := NewSeries(e.RefID, s.GetLabels(), s.TimeIdx, s.TimeIsNullable, s.ValueIdx, s.ValueIsNullable, s.Len())
newSeries := NewSeries(e.RefID, s.GetLabels(), s.Len())
for i := 0; i < s.Len(); i++ {
t, f := s.GetPoint(i)
if f == nil {
@ -431,7 +431,7 @@ func (e *State) biScalarNumber(labels data.Labels, op string, number Number, sca
}
func (e *State) biSeriesNumber(labels data.Labels, op string, s Series, scalarVal *float64, seriesFirst bool) (Series, error) {
newSeries := NewSeries(e.RefID, labels, s.TimeIdx, s.TimeIsNullable, s.ValueIdx, s.ValueIsNullable, s.Len())
newSeries := NewSeries(e.RefID, labels, s.Len())
var err error
for i := 0; i < s.Len(); i++ {
nF := math.NaN()
@ -464,15 +464,10 @@ func (e *State) biSeriesSeries(labels data.Labels, op string, aSeries, bSeries S
bPoints := make(map[string]*float64)
for i := 0; i < bSeries.Len(); i++ {
t, f := bSeries.GetPoint(i)
if t != nil {
bPoints[t.UTC().String()] = f
}
bPoints[t.UTC().String()] = f
}
newSeries := NewSeries(
e.RefID, labels, aSeries.TimeIdx, aSeries.TimeIsNullable || bSeries.TimeIsNullable, aSeries.ValueIdx,
aSeries.ValueIsNullable || bSeries.ValueIsNullable, 0,
)
newSeries := NewSeries(e.RefID, labels, 0)
for aIdx := 0; aIdx < aSeries.Len(); aIdx++ {
aTime, aF := aSeries.GetPoint(aIdx)
bF, ok := bPoints[aTime.UTC().String()]

View File

@ -3,6 +3,7 @@ package mathexp
import (
"math"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana-plugin-sdk-go/data"
@ -29,7 +30,7 @@ func TestNaN(t *testing.T) {
},
{
name: "unary -: Op Number(NaN) is NaN",
expr: "! $A",
expr: "-$A",
vars: Vars{"A": Results{[]Value{makeNumber("", nil, NaN)}}},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
@ -57,10 +58,10 @@ func TestNaN(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), NaN,
makeSeries("temp", nil, tp{
time.Unix(5, 0), float64Pointer(2),
}, tp{
time.Unix(10, 0), NaN,
}),
},
},
@ -69,10 +70,10 @@ func TestNaN(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(-1),
}, nullTimeTP{
unixTimePointer(10, 0), NaN,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(-1),
}, tp{
time.Unix(10, 0), NaN,
}),
},
},
@ -83,10 +84,10 @@ func TestNaN(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), NaN,
makeSeries("temp", nil, tp{
time.Unix(5, 0), float64Pointer(2),
}, tp{
time.Unix(10, 0), NaN,
}),
},
},
@ -96,10 +97,10 @@ func TestNaN(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(0),
}, nullTimeTP{
unixTimePointer(10, 0), NaN,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(0),
}, tp{
time.Unix(10, 0), NaN,
}),
},
},
@ -110,10 +111,10 @@ func TestNaN(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), NaN,
makeSeries("temp", nil, tp{
time.Unix(5, 0), float64Pointer(2),
}, tp{
time.Unix(10, 0), NaN,
}),
},
},
@ -123,10 +124,10 @@ func TestNaN(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), NaN,
}, nullTimeTP{
unixTimePointer(10, 0), NaN,
makeSeries("", nil, tp{
time.Unix(5, 0), NaN,
}, tp{
time.Unix(10, 0), NaN,
}),
},
},
@ -197,10 +198,10 @@ func TestNullValues(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -209,10 +210,10 @@ func TestNullValues(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(-1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(-1),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -223,10 +224,10 @@ func TestNullValues(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -235,10 +236,10 @@ func TestNullValues(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(0),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(0),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -249,10 +250,10 @@ func TestNullValues(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -261,10 +262,10 @@ func TestNullValues(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(0),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(0),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -339,10 +340,10 @@ func TestNullValues(t *testing.T) {
},
"B": Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -351,10 +352,10 @@ func TestNullValues(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -370,10 +371,10 @@ func TestNullValues(t *testing.T) {
},
"B": Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), nil,
}),
},
},
@ -382,10 +383,10 @@ func TestNullValues(t *testing.T) {
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), nil,
}, nullTimeTP{
unixTimePointer(10, 0), nil,
makeSeries("", nil, tp{
time.Unix(5, 0), nil,
}, tp{
time.Unix(10, 0), nil,
}),
},
},

View File

@ -21,185 +21,6 @@ func TestSeriesExpr(t *testing.T) {
{
name: "unary series",
expr: "! ! $A",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
}),
},
},
},
{
name: "binary scalar Op series",
expr: "98 + $A",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(100),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(99),
}),
},
},
},
{
name: "binary series Op scalar",
expr: "$A + 98",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(100),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(99),
}),
},
},
},
{
name: "series Op series",
expr: "$A + $A",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(4),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(2),
}),
},
},
},
{
name: "series Op number",
expr: "$A + $B",
vars: aSeriesbNumber,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", data.Labels{"id": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(9),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(8),
}),
},
},
},
{
name: "number Op series",
expr: "$B + $A",
vars: aSeriesbNumber,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", data.Labels{"id": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(9),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(8),
}),
},
},
},
{
name: "series Op series with label union",
expr: "$A * $B",
vars: twoSeriesSets,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", data.Labels{"sensor": "a", "turbine": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(6 * .5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(8 * .2),
}),
makeSeriesNullableTime("", data.Labels{"sensor": "b", "turbine": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(10 * .5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(16 * .2),
}),
},
},
},
// Length of resulting series is A when A + B. However, only points where the time matches
// for A and B are added to the result
{
name: "series Op series with sparse time join",
expr: "$A + $B",
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(2),
}),
},
},
"B": Results{
[]Value{
makeSeriesNullableTime("efficiency", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(9, 0), float64Pointer(4),
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preserving names...
unixTimePointer(5, 0), float64Pointer(4),
}),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e, err := New(tt.expr)
tt.newErrIs(t, err)
if e != nil {
res, err := e.Execute("", tt.vars)
tt.execErrIs(t, err)
if diff := cmp.Diff(tt.results, res, data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
}
})
}
}
func TestSeriesAlternateFormsExpr(t *testing.T) {
var tests = []struct {
name string
expr string
vars Vars
newErrIs assert.ErrorAssertionFunc
execErrIs assert.ErrorAssertionFunc
results Results
}{
{
name: "unary series: non-nullable time",
expr: "! ! $A",
vars: aSeries,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
@ -214,159 +35,144 @@ func TestSeriesAlternateFormsExpr(t *testing.T) {
},
},
{
name: "unary series: non-nullable time, time second",
expr: "! ! $A",
vars: aSeriesTimeSecond,
name: "binary scalar Op series",
expr: "98 + $A",
vars: aSeries,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesTimeSecond("", nil, timeSecondTP{ // Not sure about preservering names...
float64Pointer(1), time.Unix(5, 0),
}, timeSecondTP{
float64Pointer(1), time.Unix(10, 0),
makeSeries("", nil, tp{ // Not sure about preservering names...
time.Unix(5, 0), float64Pointer(100),
}, tp{
time.Unix(10, 0), float64Pointer(99),
}),
},
},
},
{
name: "unary series: non-nullable value",
expr: "! ! $A",
vars: aSeriesNoNull,
name: "binary series Op scalar",
expr: "$A + 98",
vars: aSeries,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeNoNullSeries("", nil, noNullTP{ // Not sure about preservering names...
time.Unix(5, 0), 1,
}, noNullTP{
time.Unix(10, 0), 1,
makeSeries("", nil, tp{ // Not sure about preservering names...
time.Unix(5, 0), float64Pointer(100),
}, tp{
time.Unix(10, 0), float64Pointer(99),
}),
},
},
},
{
name: "series Op series: nullable and non-nullable time",
expr: "$A + $B",
vars: Vars{
"A": Results{
[]Value{
makeSeries("temp", data.Labels{}, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), float64Pointer(2),
}),
},
},
"B": Results{
[]Value{
makeSeriesNullableTime("efficiency", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(4),
}),
},
},
},
name: "series Op series",
expr: "$A + $A",
vars: aSeries,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(4),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(6),
}),
},
},
},
{
name: "series Op series: nullable (time second) and non-nullable time (time first)",
expr: "$B + $A", // takes order from first operator
vars: Vars{
"A": Results{
[]Value{
makeSeriesTimeSecond("temp", data.Labels{}, timeSecondTP{
float64Pointer(1), time.Unix(5, 0),
}, timeSecondTP{
float64Pointer(2), time.Unix(10, 0),
}),
},
},
"B": Results{
[]Value{
makeSeriesNullableTime("efficiency", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(4),
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(4),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(6),
}),
},
},
},
{
name: "series Op series: nullable and non-nullable values",
expr: "$A + $B",
vars: Vars{
"A": Results{
[]Value{
makeSeries("temp", data.Labels{}, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), float64Pointer(2),
}),
},
},
"B": Results{
[]Value{
makeNoNullSeries("efficiency", data.Labels{}, noNullTP{
time.Unix(5, 0), 3,
}, noNullTP{
time.Unix(10, 0), 4,
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeries("", nil, tp{
makeSeries("", nil, tp{ // Not sure about preservering names...
time.Unix(5, 0), float64Pointer(4),
}, tp{
time.Unix(10, 0), float64Pointer(6),
time.Unix(10, 0), float64Pointer(2),
}),
},
},
},
{
name: "binary scalar Op series: non-nullable time second",
expr: "98 + $A",
vars: aSeriesTimeSecond,
name: "series Op number",
expr: "$A + $B",
vars: aSeriesbNumber,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesTimeSecond("", nil, timeSecondTP{ // Not sure about preservering names...
float64Pointer(100), time.Unix(5, 0),
}, timeSecondTP{
float64Pointer(99), time.Unix(10, 0),
makeSeries("", data.Labels{"id": "1"}, tp{
time.Unix(5, 0), float64Pointer(9),
}, tp{
time.Unix(10, 0), float64Pointer(8),
}),
},
},
},
{
name: "number Op series",
expr: "$B + $A",
vars: aSeriesbNumber,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeries("", data.Labels{"id": "1"}, tp{
time.Unix(5, 0), float64Pointer(9),
}, tp{
time.Unix(10, 0), float64Pointer(8),
}),
},
},
},
{
name: "series Op series with label union",
expr: "$A * $B",
vars: twoSeriesSets,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeries("", data.Labels{"sensor": "a", "turbine": "1"}, tp{
time.Unix(5, 0), float64Pointer(6 * .5),
}, tp{
time.Unix(10, 0), float64Pointer(8 * .2),
}),
makeSeries("", data.Labels{"sensor": "b", "turbine": "1"}, tp{
time.Unix(5, 0), float64Pointer(10 * .5),
}, tp{
time.Unix(10, 0), float64Pointer(16 * .2),
}),
},
},
},
// Length of resulting series is A when A + B. However, only points where the time matches
// for A and B are added to the result
{
name: "series Op series with sparse time join",
expr: "$A + $B",
vars: Vars{
"A": Results{
[]Value{
makeSeries("temp", data.Labels{}, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), float64Pointer(2),
}),
},
},
"B": Results{
[]Value{
makeSeries("efficiency", data.Labels{}, tp{
time.Unix(5, 0), float64Pointer(3),
}, tp{
time.Unix(9, 0), float64Pointer(4),
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeries("", nil, tp{ // Not sure about preserving names...
time.Unix(5, 0), float64Pointer(4),
}),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e, err := New(tt.expr)

View File

@ -8,60 +8,15 @@ import (
)
// Common Test Constructor Utils and Types
type nullTimeTP struct {
t *time.Time
f *float64
}
type tp struct {
t time.Time
f *float64
}
type timeSecondTP struct {
f *float64
t time.Time
}
type noNullTP struct {
t time.Time
f float64
}
func makeSeriesNullableTime(name string, labels data.Labels, points ...nullTimeTP) Series {
newSeries := NewSeries(name, labels, 0, true, 1, true, len(points))
for idx, p := range points {
_ = newSeries.SetPoint(idx, p.t, p.f)
}
return newSeries
}
func makeSeries(name string, labels data.Labels, points ...tp) Series {
newSeries := NewSeries(name, labels, 0, false, 1, true, len(points))
newSeries := NewSeries(name, labels, len(points))
for idx, p := range points {
err := newSeries.SetPoint(idx, &p.t, p.f)
if err != nil {
panic(err)
}
}
return newSeries
}
func makeNoNullSeries(name string, labels data.Labels, points ...noNullTP) Series {
newSeries := NewSeries(name, labels, 0, false, 1, false, len(points))
for idx, p := range points {
err := newSeries.SetPoint(idx, &p.t, &p.f)
if err != nil {
panic(err)
}
}
return newSeries
}
func makeSeriesTimeSecond(name string, labels data.Labels, points ...timeSecondTP) Series {
newSeries := NewSeries(name, labels, 1, false, 0, true, len(points))
for idx, p := range points {
err := newSeries.SetPoint(idx, &p.t, p.f)
err := newSeries.SetPoint(idx, p.t, p.f)
if err != nil {
panic(err)
}
@ -75,27 +30,10 @@ func makeNumber(name string, labels data.Labels, f *float64) Number {
return newNumber
}
func unixTimePointer(sec, nsec int64) *time.Time {
t := time.Unix(sec, nsec)
return &t
}
func float64Pointer(f float64) *float64 {
return &f
}
var aSeriesNullableTime = Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
}),
},
},
}
var aSeries = Vars{
"A": Results{
[]Value{
@ -108,37 +46,13 @@ var aSeries = Vars{
},
}
var aSeriesTimeSecond = Vars{
"A": Results{
[]Value{
makeSeriesTimeSecond("temp", nil, timeSecondTP{
float64Pointer(2), time.Unix(5, 0),
}, timeSecondTP{
float64Pointer(1), time.Unix(10, 0),
}),
},
},
}
var aSeriesNoNull = Vars{
"A": Results{
[]Value{
makeNoNullSeries("temp", nil, noNullTP{
time.Unix(5, 0), 2,
}, noNullTP{
time.Unix(10, 0), 1,
}),
},
},
}
var aSeriesbNumber = Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
makeSeries("temp", nil, tp{
time.Unix(5, 0), float64Pointer(2),
}, tp{
time.Unix(10, 0), float64Pointer(1),
}),
},
},
@ -152,24 +66,24 @@ var aSeriesbNumber = Vars{
var twoSeriesSets = Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", data.Labels{"sensor": "a", "turbine": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(6),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(8),
makeSeries("temp", data.Labels{"sensor": "a", "turbine": "1"}, tp{
time.Unix(5, 0), float64Pointer(6),
}, tp{
time.Unix(10, 0), float64Pointer(8),
}),
makeSeriesNullableTime("temp", data.Labels{"sensor": "b", "turbine": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(10),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(16),
makeSeries("temp", data.Labels{"sensor": "b", "turbine": "1"}, tp{
time.Unix(5, 0), float64Pointer(10),
}, tp{
time.Unix(10, 0), float64Pointer(16),
}),
},
},
"B": Results{
[]Value{
makeSeriesNullableTime("efficiency", data.Labels{"turbine": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(.5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(.2),
makeSeries("efficiency", data.Labels{"turbine": "1"}, tp{
time.Unix(5, 0), float64Pointer(.5),
}, tp{
time.Unix(10, 0), float64Pointer(.2),
}),
},
},

View File

@ -95,10 +95,7 @@ func perFloat(e *State, val Value, floatF func(x float64) float64) (Value, error
newVal = NewScalar(e.RefID, &nF)
case parse.TypeSeriesSet:
resSeries := val.(Series)
newSeries := NewSeries(
e.RefID, resSeries.GetLabels(), resSeries.TimeIdx, resSeries.TimeIsNullable, resSeries.ValueIdx,
resSeries.ValueIsNullable, resSeries.Len(),
)
newSeries := NewSeries(e.RefID, resSeries.GetLabels(), resSeries.Len())
for i := 0; i < resSeries.Len(); i++ {
t, f := resSeries.GetPoint(i)
nF := math.NaN()

View File

@ -2,6 +2,7 @@ package mathexp
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
@ -46,10 +47,10 @@ func TestFunc(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(-2),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(-1),
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(-2),
}, tp{
time.Unix(10, 0), float64Pointer(-1),
}),
},
},
@ -59,10 +60,10 @@ func TestFunc(t *testing.T) {
resultIs: assert.Equal,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(2),
}, tp{
time.Unix(10, 0), float64Pointer(1),
}),
},
},

View File

@ -77,7 +77,7 @@ func (s Series) Reduce(refID, rFunc string) (Number, error) {
}
number := NewNumber(refID, l)
var f *float64
fVec := s.Frame.Fields[s.ValueIdx]
fVec := s.Frame.Fields[seriesTypeValIdx]
floatField := Float64Field(*fVec)
switch rFunc {
case "sum":

View File

@ -44,7 +44,7 @@ func TestSeriesReduce(t *testing.T) {
name: "foo reduction will error",
red: "foo",
varToReduce: "A",
vars: aSeriesNullableTime,
vars: aSeries,
errIs: require.Error,
resultsIs: require.Equal,
},
@ -52,7 +52,7 @@ func TestSeriesReduce(t *testing.T) {
name: "sum series",
red: "sum",
varToReduce: "A",
vars: aSeriesNullableTime,
vars: aSeries,
errIs: require.NoError,
resultsIs: require.Equal,
results: Results{
@ -169,20 +169,7 @@ func TestSeriesReduce(t *testing.T) {
name: "mean series",
red: "mean",
varToReduce: "A",
vars: aSeriesNullableTime,
errIs: require.NoError,
resultsIs: require.Equal,
results: Results{
[]Value{
makeNumber("", nil, float64Pointer(1.5)),
},
},
},
{
name: "mean series (non-null-value)",
red: "mean",
varToReduce: "A",
vars: aSeriesNoNull,
vars: aSeries,
errIs: require.NoError,
resultsIs: require.Equal,
results: Results{
@ -211,10 +198,10 @@ func TestSeriesReduce(t *testing.T) {
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", data.Labels{"host": "a"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
makeSeries("temp", data.Labels{"host": "a"}, tp{
time.Unix(5, 0), float64Pointer(2),
}, tp{
time.Unix(10, 0), float64Pointer(1),
}),
},
},

View File

@ -13,7 +13,7 @@ func (s Series) Resample(refID string, interval time.Duration, downsampler strin
if newSeriesLength <= 0 {
return s, fmt.Errorf("the series cannot be sampled further; the time range is shorter than the interval")
}
resampled := NewSeries(refID, s.GetLabels(), s.TimeIdx, true, s.ValueIdx, true, newSeriesLength+1)
resampled := NewSeries(refID, s.GetLabels(), newSeriesLength+1)
bookmark := 0
var lastSeen *float64
idx := 0
@ -72,8 +72,7 @@ func (s Series) Resample(refID string, interval time.Duration, downsampler strin
}
value = tmp
}
tv := t // his is required otherwise all points keep the latest timestamp; anything better?
if err := resampled.SetPoint(idx, &tv, value); err != nil {
if err := resampled.SetPoint(idx, t, value); err != nil {
return resampled, err
}
t = t.Add(interval)

View File

@ -28,10 +28,10 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(4, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}),
},
{
@ -43,10 +43,10 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(11, 0),
To: time.Unix(0, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}),
},
{
@ -58,51 +58,23 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(16, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(4, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(9, 0), float64Pointer(2),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(4, 0), float64Pointer(3),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}, tp{
time.Unix(9, 0), float64Pointer(2),
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), nil,
}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2.5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1.5),
}, nullTimeTP{
unixTimePointer(15, 0), float64Pointer(2),
}),
},
{
name: "resample series: downsampling (mean / pad) (no-nullable)",
interval: time.Second * 5,
downsampler: "mean",
upsampler: "pad",
timeRange: backend.TimeRange{
From: time.Unix(0, 0),
To: time.Unix(16, 0),
},
seriesToResample: makeNoNullSeries("", nil, noNullTP{
time.Unix(2, 0), 2,
}, noNullTP{
time.Unix(4, 0), 3,
}, noNullTP{
time.Unix(7, 0), 1,
}, noNullTP{
time.Unix(9, 0), 2,
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), nil,
}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2.5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1.5),
}, nullTimeTP{
unixTimePointer(15, 0), float64Pointer(2),
series: makeSeries("", nil, tp{
time.Unix(0, 0), nil,
}, tp{
time.Unix(5, 0), float64Pointer(2.5),
}, tp{
time.Unix(10, 0), float64Pointer(1.5),
}, tp{
time.Unix(15, 0), float64Pointer(2),
}),
},
{
@ -114,23 +86,23 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(16, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(4, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(9, 0), float64Pointer(2),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(4, 0), float64Pointer(3),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}, tp{
time.Unix(9, 0), float64Pointer(2),
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), nil,
}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(15, 0), nil,
series: makeSeries("", nil, tp{
time.Unix(0, 0), nil,
}, tp{
time.Unix(5, 0), float64Pointer(3),
}, tp{
time.Unix(10, 0), float64Pointer(2),
}, tp{
time.Unix(15, 0), nil,
}),
},
{
@ -142,23 +114,23 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(16, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(4, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(9, 0), float64Pointer(2),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(4, 0), float64Pointer(3),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}, tp{
time.Unix(9, 0), float64Pointer(2),
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), nil,
}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(15, 0), nil,
series: makeSeries("", nil, tp{
time.Unix(0, 0), nil,
}, tp{
time.Unix(5, 0), float64Pointer(2),
}, tp{
time.Unix(10, 0), float64Pointer(1),
}, tp{
time.Unix(15, 0), nil,
}),
},
{
@ -170,23 +142,23 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(16, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(4, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(9, 0), float64Pointer(2),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(4, 0), float64Pointer(3),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}, tp{
time.Unix(9, 0), float64Pointer(2),
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), nil,
}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(15, 0), nil,
series: makeSeries("", nil, tp{
time.Unix(0, 0), nil,
}, tp{
time.Unix(5, 0), float64Pointer(5),
}, tp{
time.Unix(10, 0), float64Pointer(3),
}, tp{
time.Unix(15, 0), nil,
}),
},
{
@ -198,23 +170,23 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(16, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(4, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(9, 0), float64Pointer(2),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(4, 0), float64Pointer(3),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}, tp{
time.Unix(9, 0), float64Pointer(2),
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), nil,
}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(2.5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1.5),
}, nullTimeTP{
unixTimePointer(15, 0), nil,
series: makeSeries("", nil, tp{
time.Unix(0, 0), nil,
}, tp{
time.Unix(5, 0), float64Pointer(2.5),
}, tp{
time.Unix(10, 0), float64Pointer(1.5),
}, tp{
time.Unix(15, 0), nil,
}),
},
{
@ -226,23 +198,23 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(11, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), nil,
}, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(4, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(6, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(8, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
series: makeSeries("", nil, tp{
time.Unix(0, 0), nil,
}, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(4, 0), float64Pointer(2),
}, tp{
time.Unix(6, 0), float64Pointer(2),
}, tp{
time.Unix(8, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), float64Pointer(1),
}),
},
{
@ -254,23 +226,23 @@ func TestResampleSeries(t *testing.T) {
From: time.Unix(0, 0),
To: time.Unix(11, 0),
},
seriesToResample: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(7, 0), float64Pointer(1),
seriesToResample: makeSeries("", nil, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(7, 0), float64Pointer(1),
}),
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(0, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(4, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(6, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(8, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), nil,
series: makeSeries("", nil, tp{
time.Unix(0, 0), float64Pointer(2),
}, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(4, 0), float64Pointer(1),
}, tp{
time.Unix(6, 0), float64Pointer(1),
}, tp{
time.Unix(8, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), nil,
}),
},
}

View File

@ -9,79 +9,108 @@ import (
"github.com/grafana/grafana/pkg/expr/mathexp/parse"
)
// Series has time.Time and ...? *float64 fields.
// seriesTypeTimeIdx is the data frame field index for the Series type's Time column.
const seriesTypeTimeIdx = 0
// seriesTypeValIdx is the data frame field index for the Series type's Value column.
const seriesTypeValIdx = 1
// Series has a time.Time and a *float64 fields.
type Series struct {
Frame *data.Frame
TimeIsNullable bool
TimeIdx int
ValueIsNullable bool
ValueIdx int
Frame *data.Frame
// TODO:
// - Multiple Value Fields
// - Value can be different number types
}
// SeriesFromFrame validates that the dataframe can be considered a Series type
// and populate meta information on Series about the frame.
// and mutates the frame to be in the format that additional SSE operations expect.
func SeriesFromFrame(frame *data.Frame) (s Series, err error) {
if len(frame.Fields) != 2 {
return s, fmt.Errorf("frame must have exactly two fields to be a series, has %v", len(frame.Fields))
}
foundTime := false
foundValue := false
valueIdx := -1
timeIdx := -1
timeNullable := false
valueNullable := false
FIELDS:
for i, field := range frame.Fields {
switch field.Type() {
case data.FieldTypeTime:
s.TimeIdx = i
foundTime = true
timeIdx = i
case data.FieldTypeNullableTime:
s.TimeIsNullable = true
foundTime = true
s.TimeIdx = i
timeNullable = true
timeIdx = i
case data.FieldTypeFloat64:
foundValue = true
s.ValueIdx = i
valueIdx = i
case data.FieldTypeNullableFloat64:
s.ValueIsNullable = true
foundValue = true
s.ValueIdx = i
valueNullable = true
valueIdx = i
default:
// Handle default case
if valueIdx != -1 && timeIdx != -1 {
break FIELDS
}
}
}
if !foundTime {
if timeIdx == -1 {
return s, fmt.Errorf("no time column found in frame %v", frame.Name)
}
if !foundValue {
if valueIdx == -1 {
return s, fmt.Errorf("no float64 value column found in frame %v", frame.Name)
}
if timeNullable { // make time not nullable if it is in the input
timeSlice := make([]time.Time, 0, frame.Fields[timeIdx].Len())
for rowIdx := 0; rowIdx < frame.Fields[timeIdx].Len(); rowIdx++ {
val, ok := frame.At(0, rowIdx).(*time.Time)
if !ok {
return s, fmt.Errorf("unexpected time type, expected *time.Time but got %T", val)
}
if val == nil {
return s, fmt.Errorf("time series with null time stamps are not supported")
}
timeSlice = append(timeSlice, *val)
}
nF := data.NewField(frame.Fields[timeIdx].Name, nil, timeSlice) // (labels are not used on time field)
nF.Config = frame.Fields[timeIdx].Config
frame.Fields[timeIdx] = nF
}
if !valueNullable { // make value nullable if it is not in the input
floatSlice := make([]*float64, 0, frame.Fields[valueIdx].Len())
for rowIdx := 0; rowIdx < frame.Fields[valueIdx].Len(); rowIdx++ {
val, ok := frame.At(valueIdx, rowIdx).(float64)
if !ok {
return s, fmt.Errorf("unexpected time type, expected float64 but got %T", val)
}
floatSlice = append(floatSlice, &val)
}
nF := data.NewField(frame.Fields[valueIdx].Name, frame.Fields[valueIdx].Labels, floatSlice)
nF.Config = frame.Fields[valueIdx].Config
frame.Fields[valueIdx] = nF
}
fields := make([]*data.Field, 2)
fields[seriesTypeTimeIdx] = frame.Fields[timeIdx]
fields[seriesTypeValIdx] = frame.Fields[valueIdx]
frame.Fields = fields
s.Frame = frame
return s, nil
}
// NewSeries returns a dataframe of type Series.
func NewSeries(refID string, labels data.Labels, timeIdx int, nullableTime bool, valueIdx int, nullableValue bool, size int) Series {
func NewSeries(refID string, labels data.Labels, size int) Series {
fields := make([]*data.Field, 2)
if nullableValue {
fields[valueIdx] = data.NewField(refID, labels, make([]*float64, size))
} else {
fields[valueIdx] = data.NewField(refID, labels, make([]float64, size))
}
if nullableTime {
fields[timeIdx] = data.NewField("Time", nil, make([]*time.Time, size))
} else {
fields[timeIdx] = data.NewField("Time", nil, make([]time.Time, size))
}
fields[seriesTypeTimeIdx] = data.NewField("Time", nil, make([]time.Time, size))
fields[seriesTypeValIdx] = data.NewField(refID, labels, make([]*float64, size))
return Series{
Frame: data.NewFrame("", fields...),
TimeIsNullable: nullableTime,
TimeIdx: timeIdx,
ValueIsNullable: nullableValue,
ValueIdx: valueIdx,
Frame: data.NewFrame("", fields...),
}
}
@ -91,9 +120,9 @@ func (s Series) Type() parse.ReturnType { return parse.TypeSeriesSet }
// Value returns the actual value allows it to fulfill the Value interface.
func (s Series) Value() interface{} { return &s }
func (s Series) GetLabels() data.Labels { return s.Frame.Fields[s.ValueIdx].Labels }
func (s Series) GetLabels() data.Labels { return s.Frame.Fields[seriesTypeValIdx].Labels }
func (s Series) SetLabels(ls data.Labels) { s.Frame.Fields[s.ValueIdx].Labels = ls }
func (s Series) SetLabels(ls data.Labels) { s.Frame.Fields[seriesTypeValIdx].Labels = ls }
func (s Series) GetName() string { return s.Frame.Name }
@ -109,73 +138,37 @@ func (s Series) SetMeta(v interface{}) {
func (s Series) AsDataFrame() *data.Frame { return s.Frame }
// GetPoint returns the time and value at the specified index.
func (s Series) GetPoint(pointIdx int) (*time.Time, *float64) {
func (s Series) GetPoint(pointIdx int) (time.Time, *float64) {
return s.GetTime(pointIdx), s.GetValue(pointIdx)
}
// SetPoint sets the time and value on the corresponding vectors at the specified index.
func (s Series) SetPoint(pointIdx int, t *time.Time, f *float64) (err error) {
if s.TimeIsNullable {
s.Frame.Fields[s.TimeIdx].Set(pointIdx, t)
} else {
if t == nil {
return fmt.Errorf("can not set null time value on non-nullable time field for series name %v", s.Frame.Name)
}
s.Frame.Fields[s.TimeIdx].Set(pointIdx, *t)
}
if s.ValueIsNullable {
s.Frame.Fields[s.ValueIdx].Set(pointIdx, f)
} else {
if f == nil {
return fmt.Errorf("can not set null float value on non-nullable float field for series name %v", s.Frame.Name)
}
s.Frame.Fields[s.ValueIdx].Set(pointIdx, *f)
}
func (s Series) SetPoint(pointIdx int, t time.Time, f *float64) (err error) {
s.Frame.Fields[seriesTypeTimeIdx].Set(pointIdx, t)
s.Frame.Fields[seriesTypeValIdx].Set(pointIdx, f)
return
}
// AppendPoint appends a point (time/value).
func (s Series) AppendPoint(pointIdx int, t *time.Time, f *float64) (err error) {
if s.TimeIsNullable {
s.Frame.Fields[s.TimeIdx].Append(t)
} else {
if t == nil {
return fmt.Errorf("can not append null time value on non-nullable time field for series name %v", s.Frame.Name)
}
s.Frame.Fields[s.TimeIdx].Append(*t)
}
if s.ValueIsNullable {
s.Frame.Fields[s.ValueIdx].Append(f)
} else {
if f == nil {
return fmt.Errorf("can not append null float value on non-nullable float field for series name %v", s.Frame.Name)
}
s.Frame.Fields[s.ValueIdx].Append(*f)
}
func (s Series) AppendPoint(pointIdx int, t time.Time, f *float64) (err error) {
s.Frame.Fields[seriesTypeTimeIdx].Append(t)
s.Frame.Fields[seriesTypeValIdx].Append(f)
return
}
// Len returns the length of the series.
func (s Series) Len() int {
return s.Frame.Fields[0].Len()
return s.Frame.Fields[seriesTypeTimeIdx].Len()
}
// GetTime returns the time at the specified index.
func (s Series) GetTime(pointIdx int) *time.Time {
if s.TimeIsNullable {
return s.Frame.Fields[s.TimeIdx].At(pointIdx).(*time.Time)
}
t := s.Frame.Fields[s.TimeIdx].At(pointIdx).(time.Time)
return &t
func (s Series) GetTime(pointIdx int) time.Time {
return s.Frame.Fields[seriesTypeTimeIdx].At(pointIdx).(time.Time)
}
// GetValue returns the float value at the specified index.
func (s Series) GetValue(pointIdx int) *float64 {
if s.ValueIsNullable {
return s.Frame.Fields[s.ValueIdx].At(pointIdx).(*float64)
}
f := s.Frame.Fields[s.ValueIdx].At(pointIdx).(float64)
return &f
return s.Frame.Fields[seriesTypeValIdx].At(pointIdx).(*float64)
}
// SortByTime sorts the series by the time from oldest to newest.
@ -205,5 +198,5 @@ func (ss SortSeriesByTime) Swap(i, j int) {
func (ss SortSeriesByTime) Less(i, j int) bool {
iTimeVal := Series(ss).GetTime(i)
jTimeVal := Series(ss).GetTime(j)
return iTimeVal.Before(*jTimeVal)
return iTimeVal.Before(jTimeVal)
}

View File

@ -20,39 +20,39 @@ func TestSeriesSort(t *testing.T) {
{
name: "unordered series should sort by time ascending",
descending: false,
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(3, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(1, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
series: makeSeries("", nil, tp{
time.Unix(3, 0), float64Pointer(3),
}, tp{
time.Unix(1, 0), float64Pointer(1),
}, tp{
time.Unix(2, 0), float64Pointer(2),
}),
sortedSeriesIs: assert.Equal,
sortedSeries: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(1, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(3, 0), float64Pointer(3),
sortedSeries: makeSeries("", nil, tp{
time.Unix(1, 0), float64Pointer(1),
}, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(3, 0), float64Pointer(3),
}),
},
{
name: "unordered series should sort by time descending",
descending: true,
series: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(3, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(1, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
series: makeSeries("", nil, tp{
time.Unix(3, 0), float64Pointer(3),
}, tp{
time.Unix(1, 0), float64Pointer(1),
}, tp{
time.Unix(2, 0), float64Pointer(2),
}),
sortedSeriesIs: assert.Equal,
sortedSeries: makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(3, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(2, 0), float64Pointer(2),
}, nullTimeTP{
unixTimePointer(1, 0), float64Pointer(1),
sortedSeries: makeSeries("", nil, tp{
time.Unix(3, 0), float64Pointer(3),
}, tp{
time.Unix(2, 0), float64Pointer(2),
}, tp{
time.Unix(1, 0), float64Pointer(1),
}),
},
}
@ -89,22 +89,18 @@ func TestSeriesFromFrame(t *testing.T) {
Name: "test",
Fields: []*data.Field{
data.NewField("time", nil, []time.Time{}),
data.NewField("value", nil, []float64{}),
data.NewField("value", nil, []*float64{}),
},
},
TimeIdx: 0,
TimeIsNullable: false,
ValueIdx: 1,
ValueIsNullable: false,
},
},
{
name: "[]*float, []*time frame should convert",
name: "[]time, []*float frame should convert",
frame: &data.Frame{
Name: "test",
Fields: []*data.Field{
data.NewField("time", nil, []time.Time{time.Unix(5, 0)}),
data.NewField("value", nil, []*float64{float64Pointer(5)}),
data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}),
},
},
errIs: assert.NoError,
@ -113,14 +109,31 @@ func TestSeriesFromFrame(t *testing.T) {
Frame: &data.Frame{
Name: "test",
Fields: []*data.Field{
data.NewField("time", nil, []time.Time{time.Unix(5, 0)}),
data.NewField("value", nil, []*float64{float64Pointer(5)}),
},
},
},
},
{
name: "[]*float, []time frame should convert",
frame: &data.Frame{
Name: "test",
Fields: []*data.Field{
data.NewField("value", nil, []*float64{float64Pointer(5)}),
data.NewField("time", nil, []time.Time{time.Unix(5, 0)}),
},
},
errIs: assert.NoError,
Is: assert.Equal,
Series: Series{
Frame: &data.Frame{
Name: "test",
Fields: []*data.Field{
data.NewField("time", nil, []time.Time{time.Unix(5, 0)}),
data.NewField("value", nil, []*float64{float64Pointer(5)}),
data.NewField("time", nil, []*time.Time{unixTimePointer(5, 0)}),
},
},
TimeIdx: 1,
TimeIsNullable: true,
ValueIdx: 0,
ValueIsNullable: true,
},
},
{

View File

@ -19,20 +19,20 @@ func Test_union(t *testing.T) {
name: "equal tags single union",
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1"}),
makeSeries("a", data.Labels{"id": "1"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "1"}),
makeSeries("b", data.Labels{"id": "1"}),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{
{
Labels: data.Labels{"id": "1"},
A: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "1"}),
A: makeSeries("a", data.Labels{"id": "1"}),
B: makeSeries("b", data.Labels{"id": "1"}),
},
},
},
@ -40,19 +40,19 @@ func Test_union(t *testing.T) {
name: "equal tags keys with no matching values will result in a union when len(A) == 1 && len(B) == 1",
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1"}),
makeSeries("a", data.Labels{"id": "1"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "2"}),
makeSeries("b", data.Labels{"id": "2"}),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{
{
A: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "2"}),
A: makeSeries("a", data.Labels{"id": "1"}),
B: makeSeries("b", data.Labels{"id": "2"}),
},
},
},
@ -60,13 +60,13 @@ func Test_union(t *testing.T) {
name: "equal tags keys with no matching values will result in no unions when len(A) != 1 && len(B) != 1",
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1"}),
makeSeriesNullableTime("q", data.Labels{"id": "3"}),
makeSeries("a", data.Labels{"id": "1"}),
makeSeries("q", data.Labels{"id": "3"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "2"}),
makeSeries("b", data.Labels{"id": "2"}),
},
},
unionsAre: assert.EqualValues,
@ -83,13 +83,13 @@ func Test_union(t *testing.T) {
name: "incompatible tags of different length with will result in no unions when len(A) != 1 && len(B) != 1",
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"ID": "1"}),
makeSeriesNullableTime("q", data.Labels{"ID": "3"}),
makeSeries("a", data.Labels{"ID": "1"}),
makeSeries("q", data.Labels{"ID": "3"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "red snapper"}),
makeSeries("b", data.Labels{"id": "1", "fish": "red snapper"}),
},
},
unionsAre: assert.EqualValues,
@ -99,20 +99,20 @@ func Test_union(t *testing.T) {
name: "A is subset of B results in single union with Labels of B",
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1"}),
makeSeries("a", data.Labels{"id": "1"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{
{
Labels: data.Labels{"id": "1", "fish": "herring"}, // Union gets the labels that is not the subset
A: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
A: makeSeries("a", data.Labels{"id": "1"}),
B: makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
},
},
},
@ -120,20 +120,20 @@ func Test_union(t *testing.T) {
name: "B is subset of A results in single union with Labels of A",
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1", "fish": "herring"}),
makeSeries("a", data.Labels{"id": "1", "fish": "herring"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "1"}),
makeSeries("b", data.Labels{"id": "1"}),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{
{
Labels: data.Labels{"id": "1", "fish": "herring"}, // Union gets the labels that is not the subset
A: makeSeriesNullableTime("a", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "1"}),
A: makeSeries("a", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeries("b", data.Labels{"id": "1"}),
},
},
},
@ -141,26 +141,26 @@ func Test_union(t *testing.T) {
name: "single valued A is subset of many valued B, results in many union with Labels of B",
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1"}),
makeSeries("a", data.Labels{"id": "1"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "red snapper"}),
makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
makeSeries("b", data.Labels{"id": "1", "fish": "red snapper"}),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{
{
Labels: data.Labels{"id": "1", "fish": "herring"},
A: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
A: makeSeries("a", data.Labels{"id": "1"}),
B: makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
},
{
Labels: data.Labels{"id": "1", "fish": "red snapper"},
A: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "red snapper"}),
A: makeSeries("a", data.Labels{"id": "1"}),
B: makeSeries("b", data.Labels{"id": "1", "fish": "red snapper"}),
},
},
},
@ -170,32 +170,32 @@ func Test_union(t *testing.T) {
// be uniquely identifiable.
aResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1"}),
makeSeriesNullableTime("aa", data.Labels{"id": "1", "fish": "herring"}),
makeSeries("a", data.Labels{"id": "1"}),
makeSeries("aa", data.Labels{"id": "1", "fish": "herring"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
makeSeriesNullableTime("bb", data.Labels{"id": "1", "fish": "red snapper"}),
makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
makeSeries("bb", data.Labels{"id": "1", "fish": "red snapper"}),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{
{
Labels: data.Labels{"id": "1", "fish": "herring"},
A: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
A: makeSeries("a", data.Labels{"id": "1"}),
B: makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
},
{
Labels: data.Labels{"id": "1", "fish": "red snapper"},
A: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
B: makeSeriesNullableTime("bb", data.Labels{"id": "1", "fish": "red snapper"}),
A: makeSeries("a", data.Labels{"id": "1"}),
B: makeSeries("bb", data.Labels{"id": "1", "fish": "red snapper"}),
},
{
Labels: data.Labels{"id": "1", "fish": "herring"},
A: makeSeriesNullableTime("aa", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
A: makeSeries("aa", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
},
},
},
@ -205,32 +205,32 @@ func Test_union(t *testing.T) {
// be uniquely identifiable.
aResults: Results{
Values: Values{
makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
makeSeriesNullableTime("bb", data.Labels{"id": "1", "fish": "red snapper"}),
makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
makeSeries("bb", data.Labels{"id": "1", "fish": "red snapper"}),
},
},
bResults: Results{
Values: Values{
makeSeriesNullableTime("a", data.Labels{"id": "1"}),
makeSeriesNullableTime("aa", data.Labels{"id": "1", "fish": "herring"}),
makeSeries("a", data.Labels{"id": "1"}),
makeSeries("aa", data.Labels{"id": "1", "fish": "herring"}),
},
},
unionsAre: assert.EqualValues,
unions: []*Union{
{
Labels: data.Labels{"id": "1", "fish": "herring"},
A: makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
A: makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeries("a", data.Labels{"id": "1"}),
},
{
Labels: data.Labels{"id": "1", "fish": "herring"},
A: makeSeriesNullableTime("b", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeriesNullableTime("aa", data.Labels{"id": "1", "fish": "herring"}),
A: makeSeries("b", data.Labels{"id": "1", "fish": "herring"}),
B: makeSeries("aa", data.Labels{"id": "1", "fish": "herring"}),
},
{
Labels: data.Labels{"id": "1", "fish": "red snapper"},
A: makeSeriesNullableTime("bb", data.Labels{"id": "1", "fish": "red snapper"}),
B: makeSeriesNullableTime("a", data.Labels{"id": "1"}),
A: makeSeries("bb", data.Labels{"id": "1", "fish": "red snapper"}),
B: makeSeries("a", data.Labels{"id": "1"}),
},
},
},

View File

@ -22,7 +22,7 @@ import (
// nolint:staticcheck // plugins.DataPlugin deprecated
func TestService(t *testing.T) {
dsDF := data.NewFrame("test",
data.NewField("time", nil, []*time.Time{utp(1)}),
data.NewField("time", nil, []time.Time{time.Unix(1, 0)}),
data.NewField("value", nil, []*float64{fp(2)}))
dataSvc := tsdb.NewService()
@ -61,7 +61,7 @@ func TestService(t *testing.T) {
require.NoError(t, err)
bDF := data.NewFrame("",
data.NewField("Time", nil, []*time.Time{utp(1)}),
data.NewField("Time", nil, []time.Time{time.Unix(1, 0)}),
data.NewField("B", nil, []*float64{fp(4)}))
bDF.RefID = "B"
@ -90,11 +90,6 @@ func TestService(t *testing.T) {
}
}
func utp(sec int64) *time.Time {
t := time.Unix(sec, 0)
return &t
}
func fp(f float64) *float64 {
return &f
}