Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit 31e90cc

Browse files
authored
Add constant line function (#1734)
* Remove check for reqs.count == 0 * Add constantLine function * Format string differently * Add initial simple test for constantLine * Clean up simple test * Make constant line test more modular * Flesh out constantLIne tests * constantLine to stable, update docs * Fix test naming * Use time package in constantLine testing * go format files * Handle edge cases for constantLine * Change constantLine from/to -> first/last. Add comment explaning constantLine timestamps. * Gofmt
1 parent ec88cf4 commit 31e90cc

File tree

5 files changed

+322
-4
lines changed

5 files changed

+322
-4
lines changed

api/graphite.go

-3
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,6 @@ func (s *Server) executePlan(ctx context.Context, orgId uint32, plan expr.Plan)
757757
}
758758

759759
reqRenderSeriesCount.ValueUint32(reqs.cnt)
760-
if reqs.cnt == 0 {
761-
return nil, meta, nil
762-
}
763760

764761
meta.RenderStats.SeriesFetch = reqs.cnt
765762

docs/graphite.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ See also:
5959
| changed | | No |
6060
| color | | No |
6161
| consolidateBy(seriesList, func) seriesList | | Stable |
62-
| constantLine | | No |
62+
| constantLine | | Stable |
6363
| countSeries(seriesLists) series | | Stable |
6464
| cumulative | | Stable |
6565
| currentAbove | | Stable |

expr/func_constantline.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package expr
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/grafana/metrictank/api/models"
7+
"github.com/grafana/metrictank/schema"
8+
)
9+
10+
type FuncConstantLine struct {
11+
value float64
12+
first uint32
13+
last uint32
14+
}
15+
16+
func NewConstantLine() GraphiteFunc {
17+
return &FuncConstantLine{}
18+
}
19+
20+
func (s *FuncConstantLine) Signature() ([]Arg, []Arg) {
21+
return []Arg{
22+
ArgFloat{key: "value", val: &s.value},
23+
}, []Arg{
24+
ArgSeriesList{},
25+
}
26+
}
27+
28+
func (s *FuncConstantLine) Context(context Context) Context {
29+
// Graphite implements constantLine in a non-standard way with repspect to time.
30+
// Normally, graphite is from exclusive, to inclusive, but for constantLine,
31+
// is from inclusive, to inclusive. This means the first datapoint has a ts 1s
32+
// earlier than what it would normally be. We do the same here.
33+
s.first = context.from - 1
34+
s.last = context.to - 1
35+
return context
36+
}
37+
38+
func (s *FuncConstantLine) Exec(dataMap DataMap) ([]models.Series, error) {
39+
out := pointSlicePool.Get().([]schema.Point)
40+
41+
out = append(out, schema.Point{Val: s.value, Ts: s.first})
42+
diff := s.last - s.first
43+
44+
// edge cases
45+
// if first = last - 1, return one datapoint to user, so don't add more points
46+
// if first = last - 2, return two datapoints where timestamps are first, first +1
47+
if diff > 2 {
48+
out = append(out,
49+
schema.Point{Val: s.value, Ts: s.first + uint32(diff/2.0)},
50+
schema.Point{Val: s.value, Ts: s.last},
51+
)
52+
} else if diff == 2 {
53+
out = append(out, schema.Point{Val: s.value, Ts: s.first + 1})
54+
}
55+
56+
strValue := fmt.Sprintf("%g", s.value)
57+
58+
outputs := make([]models.Series, 1)
59+
outputs[0] = models.Series{
60+
Target: strValue,
61+
QueryPatt: strValue,
62+
Datapoints: out,
63+
}
64+
outputs[0].SetTags()
65+
66+
dataMap.Add(Req{}, outputs...)
67+
return outputs, nil
68+
}

expr/func_constantline_test.go

+252
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package expr
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"testing"
7+
"time"
8+
9+
"github.com/grafana/metrictank/api/models"
10+
"github.com/grafana/metrictank/schema"
11+
"github.com/grafana/metrictank/test"
12+
)
13+
14+
type ConstantLineTestCase struct {
15+
name string
16+
value float64
17+
}
18+
19+
func TestConstantLineSmallInt(t *testing.T) {
20+
cases := []ConstantLineTestCase{
21+
{
22+
name: "constantLine(1)",
23+
value: 1,
24+
},
25+
{
26+
name: "constantLine(100)",
27+
value: 100,
28+
},
29+
{
30+
name: "constantLine(10000)",
31+
value: 10000,
32+
},
33+
}
34+
35+
testConstantLineWrapper(cases, t)
36+
}
37+
38+
func TestConstantLineSmallFloatLowPrec(t *testing.T) {
39+
cases := []ConstantLineTestCase{
40+
{
41+
name: "constantLine(1.234)",
42+
value: 1.234,
43+
},
44+
{
45+
name: "constantLine(100.234)",
46+
value: 100.234,
47+
},
48+
{
49+
name: "constantLine(10000.234)",
50+
value: 10000.234,
51+
},
52+
}
53+
54+
testConstantLineWrapper(cases, t)
55+
}
56+
57+
func TestConstantLineSmallFloatHighPrec(t *testing.T) {
58+
cases := []ConstantLineTestCase{
59+
{
60+
name: "constantLine(1.2345678912345)",
61+
value: 1.2345678912345,
62+
},
63+
{
64+
name: "constantLine(100.2345678912345)",
65+
value: 100.2345678912345,
66+
},
67+
{
68+
name: "constantLine(10000.2345678912345)",
69+
value: 10000.2345678912345,
70+
},
71+
}
72+
73+
testConstantLineWrapper(cases, t)
74+
}
75+
76+
func TestConstantLineLargeInt(t *testing.T) {
77+
cases := []ConstantLineTestCase{
78+
{
79+
name: "constantLine(1000000000)",
80+
value: 1000000000,
81+
},
82+
{
83+
name: "constantLine(1000000000000)",
84+
value: 1000000000000,
85+
},
86+
}
87+
88+
testConstantLineWrapper(cases, t)
89+
}
90+
91+
func TestConstantLineLargeFloatLowPrec(t *testing.T) {
92+
cases := []ConstantLineTestCase{
93+
{
94+
name: "constantLine(1000000000.234)",
95+
value: 1000000000.234,
96+
},
97+
{
98+
name: "constantLine(1000000000000.234)",
99+
value: 1000000000000.234,
100+
},
101+
}
102+
103+
testConstantLineWrapper(cases, t)
104+
}
105+
106+
func TestConstantLineLargeFloatHighPrec(t *testing.T) {
107+
cases := []ConstantLineTestCase{
108+
{
109+
name: "constantLine(1000000000.2345678912345)",
110+
value: 1000000000.2345678912345,
111+
},
112+
{
113+
name: "constantLine(1000000000000.2345678912345)",
114+
value: 1000000000000.2345678912345,
115+
},
116+
}
117+
118+
testConstantLineWrapper(cases, t)
119+
}
120+
121+
func TestConstantLineFloatTooManyDecimals(t *testing.T) {
122+
cases := []ConstantLineTestCase{
123+
{
124+
name: "constantLine(1.23456789123456789123456789)",
125+
value: 1.23456789123456789123456789,
126+
},
127+
}
128+
129+
testConstantLineWrapper(cases, t)
130+
}
131+
132+
func testConstantLineWrapper(cases []ConstantLineTestCase, t *testing.T) {
133+
// array of time ranges
134+
// 1s, 1m, 1hr, 1day, 30days, 400days
135+
day := time.Hour * 24
136+
timeRanges := []time.Duration{
137+
time.Second,
138+
time.Second * 2,
139+
time.Minute,
140+
time.Hour,
141+
day,
142+
day * 30,
143+
day * 400,
144+
}
145+
146+
for _, c := range cases {
147+
for _, to := range timeRanges {
148+
toInt := uint32(to.Seconds())
149+
testConstantLine(c.name, c.value, 0, toInt, makeConstantLineSeries(c.value, 0, toInt), t)
150+
}
151+
}
152+
}
153+
154+
func makeConstantLineSeries(value float64, first uint32, last uint32) []models.Series {
155+
datapoints := []schema.Point{
156+
{Val: value, Ts: first},
157+
}
158+
diff := last - first
159+
if diff > 2 {
160+
datapoints = append(datapoints,
161+
schema.Point{Val: value, Ts: first + uint32(diff/2.0)},
162+
schema.Point{Val: value, Ts: last},
163+
)
164+
} else if diff == 2 {
165+
datapoints = append(datapoints, schema.Point{Val: value, Ts: first + 1})
166+
}
167+
series := []models.Series{
168+
{
169+
Target: fmt.Sprintf("%g", value),
170+
QueryPatt: fmt.Sprintf("%g", value),
171+
Datapoints: datapoints,
172+
},
173+
}
174+
175+
return series
176+
}
177+
178+
func testConstantLine(name string, value float64, from uint32, to uint32, out []models.Series, t *testing.T) {
179+
f := NewConstantLine()
180+
f.(*FuncConstantLine).value = value
181+
f.(*FuncConstantLine).first = from
182+
f.(*FuncConstantLine).last = to
183+
got, err := f.Exec(make(map[Req][]models.Series))
184+
185+
if err := equalOutput(out, got, nil, err); err != nil {
186+
t.Fatalf("Failed test %q: , got %q", name, err)
187+
}
188+
}
189+
190+
func BenchmarkConstantLine10k_1NoNulls(b *testing.B) {
191+
benchmarkConstantLine(b, 1, test.RandFloats10k, test.RandFloats10k)
192+
}
193+
func BenchmarkConstantLine10k_10NoNulls(b *testing.B) {
194+
benchmarkConstantLine(b, 10, test.RandFloats10k, test.RandFloats10k)
195+
}
196+
func BenchmarkConstantLine10k_100NoNulls(b *testing.B) {
197+
benchmarkConstantLine(b, 100, test.RandFloats10k, test.RandFloats10k)
198+
}
199+
func BenchmarkConstantLine10k_1000NoNulls(b *testing.B) {
200+
benchmarkConstantLine(b, 1000, test.RandFloats10k, test.RandFloats10k)
201+
}
202+
func BenchmarkConstantLine10k_1SomeSeriesHalfNulls(b *testing.B) {
203+
benchmarkConstantLine(b, 1, test.RandFloats10k, test.RandFloatsWithNulls10k)
204+
}
205+
func BenchmarkConstantLine10k_10SomeSeriesHalfNulls(b *testing.B) {
206+
benchmarkConstantLine(b, 10, test.RandFloats10k, test.RandFloatsWithNulls10k)
207+
}
208+
func BenchmarkConstantLine10k_100SomeSeriesHalfNulls(b *testing.B) {
209+
benchmarkConstantLine(b, 100, test.RandFloats10k, test.RandFloatsWithNulls10k)
210+
}
211+
func BenchmarkConstantLine10k_1000SomeSeriesHalfNulls(b *testing.B) {
212+
benchmarkConstantLine(b, 1000, test.RandFloats10k, test.RandFloatsWithNulls10k)
213+
}
214+
func BenchmarkConstantLine10k_1AllSeriesHalfNulls(b *testing.B) {
215+
benchmarkConstantLine(b, 1, test.RandFloatsWithNulls10k, test.RandFloatsWithNulls10k)
216+
}
217+
func BenchmarkConstantLine10k_10AllSeriesHalfNulls(b *testing.B) {
218+
benchmarkConstantLine(b, 10, test.RandFloatsWithNulls10k, test.RandFloatsWithNulls10k)
219+
}
220+
func BenchmarkConstantLine10k_100AllSeriesHalfNulls(b *testing.B) {
221+
benchmarkConstantLine(b, 100, test.RandFloatsWithNulls10k, test.RandFloatsWithNulls10k)
222+
}
223+
func BenchmarkConstantLine10k_1000AllSeriesHalfNulls(b *testing.B) {
224+
benchmarkConstantLine(b, 1000, test.RandFloatsWithNulls10k, test.RandFloatsWithNulls10k)
225+
}
226+
227+
func benchmarkConstantLine(b *testing.B, numSeries int, fn0, fn1 func() []schema.Point) {
228+
var input []models.Series
229+
for i := 0; i < numSeries; i++ {
230+
series := models.Series{
231+
QueryPatt: strconv.Itoa(i),
232+
}
233+
if i%2 == 0 {
234+
series.Datapoints = fn0()
235+
} else {
236+
series.Datapoints = fn1()
237+
}
238+
input = append(input, series)
239+
}
240+
b.ResetTimer()
241+
for i := 0; i < b.N; i++ {
242+
f := NewConstantLine()
243+
f.(*FuncConstantLine).value = 1.0
244+
f.(*FuncConstantLine).first = 1584849600
245+
f.(*FuncConstantLine).last = 1584849660
246+
got, err := f.Exec(make(map[Req][]models.Series))
247+
if err != nil {
248+
b.Fatalf("%s", err)
249+
}
250+
results = got
251+
}
252+
}

expr/funcs.go

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func init() {
6363
"averageBelow": {NewFilterSeriesConstructor("average", "<="), true},
6464
"averageSeries": {NewAggregateConstructor("average", crossSeriesAvg), true},
6565
"consolidateBy": {NewConsolidateBy, true},
66+
"constantLine": {NewConstantLine, true},
6667
"countSeries": {NewCountSeries, true},
6768
"cumulative": {NewConsolidateByConstructor("sum"), true},
6869
"currentAbove": {NewFilterSeriesConstructor("last", ">"), true},

0 commit comments

Comments
 (0)