Skip to content
This repository was archived by the owner on Mar 18, 2025. It is now read-only.

Commit 6d82624

Browse files
authored
Merge pull request #34 from pablochacin/send-trend-sem-metric
Send trend sum as metric
2 parents b0581fe + d896b3e commit 6d82624

File tree

2 files changed

+129
-1
lines changed

2 files changed

+129
-1
lines changed

pkg/remotewrite/prometheus.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,14 @@ func (pm *PrometheusMapping) MapTrend(ms *metricsStorage, sample metrics.Sample,
7676

7777
// Prometheus metric system does not support Trend so this mapping will
7878
// store a counter for the number of reported values and gauges to keep
79-
// track of aggregated values.
79+
// track of aggregated values. Also store a sum of the values to allow
80+
// the calculation of moving averages.
8081
// TODO: when Prometheus implements support for sparse histograms, re-visit this implementation
8182

8283
s := metric.Sink.(*metrics.TrendSink)
8384
aggr := map[string]float64{
8485
"count": float64(s.Count),
86+
"sum": s.Sum,
8587
"min": s.Min,
8688
"max": s.Max,
8789
"avg": s.Avg,
@@ -103,6 +105,18 @@ func (pm *PrometheusMapping) MapTrend(ms *metricsStorage, sample metrics.Sample,
103105
},
104106
},
105107
},
108+
{
109+
Labels: append(labels, prompb.Label{
110+
Name: "__name__",
111+
Value: fmt.Sprintf("%s%s_sum", defaultMetricPrefix, sample.Metric.Name),
112+
}),
113+
Samples: []prompb.Sample{
114+
{
115+
Value: aggr["sum"],
116+
Timestamp: timestamp.FromTime(sample.Time),
117+
},
118+
},
119+
},
106120
{
107121
Labels: append(labels, prompb.Label{
108122
Name: "__name__",

pkg/remotewrite/prometheus_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package remotewrite
22

33
import (
44
"math/rand"
5+
"sort"
56
"testing"
67
"time"
78

9+
"github.com/prometheus/prometheus/prompb"
810
"github.com/stretchr/testify/assert"
911
"go.k6.io/k6/metrics"
1012
)
@@ -108,3 +110,115 @@ func BenchmarkTrendAdd(b *testing.B) {
108110
benchF[1](b, start)
109111
})
110112
}
113+
114+
// buildTimeSeries creates a TimSeries with the given name, value and timestamp
115+
func buildTimeSeries(name string, value float64, timestamp time.Time) prompb.TimeSeries {
116+
return prompb.TimeSeries{
117+
Labels: []prompb.Label{
118+
{
119+
Name: "__name__",
120+
Value: name,
121+
},
122+
},
123+
Samples: []prompb.Sample{
124+
{
125+
Value: value,
126+
Timestamp: timestamp.Unix(),
127+
},
128+
},
129+
}
130+
}
131+
132+
// getTimeSeriesName returs the name of the timeseries defined in the '__name__' label
133+
func getTimeSeriesName(ts prompb.TimeSeries) string {
134+
for _, l := range ts.Labels {
135+
if l.Name == "__name__" {
136+
return l.Value
137+
}
138+
}
139+
return ""
140+
}
141+
142+
// assertTimeSeriesEqual compares if two TimeSeries has the same name and value.
143+
// Assumes only one sample per TimeSeries
144+
func assertTimeSeriesEqual(t *testing.T, expected prompb.TimeSeries, actual prompb.TimeSeries) {
145+
expectedName := getTimeSeriesName(expected)
146+
actualName := getTimeSeriesName(actual)
147+
if expectedName != actualName {
148+
t.Errorf("names do not match expected: %s actual: %s", expectedName, actualName)
149+
}
150+
151+
expectedValue := expected.Samples[0].Value
152+
actualValue := actual.Samples[0].Value
153+
if expectedValue != actualValue {
154+
t.Errorf("values do not match expected: %f actual: %f", expectedValue, actualValue)
155+
}
156+
}
157+
158+
// sortTimeSeries sorts an array of TimeSeries by name
159+
func sortTimeSeries(ts []prompb.TimeSeries) []prompb.TimeSeries {
160+
sorted := make([]prompb.TimeSeries, len(ts))
161+
copy(sorted, ts)
162+
sort.Slice(sorted, func(i int, j int) bool {
163+
return getTimeSeriesName(sorted[i]) < getTimeSeriesName(sorted[j])
164+
})
165+
166+
return sorted
167+
}
168+
169+
// assertTimeSeriesMatch asserts if the elements of two arrays of TimeSeries match not considering order
170+
func assertTimeSeriesMatch(t *testing.T, expected []prompb.TimeSeries, actual []prompb.TimeSeries) {
171+
if len(expected) != len(actual) {
172+
t.Errorf("timeseries length does not match. expected %d actual: %d", len(expected), len(actual))
173+
}
174+
175+
//sort arrays
176+
se := sortTimeSeries(expected)
177+
sa := sortTimeSeries(actual)
178+
179+
//return false if any element does not match
180+
for i := 0; i < len(se); i++ {
181+
assertTimeSeriesEqual(t, se[i], sa[i])
182+
}
183+
184+
}
185+
186+
func TestMapTrend(t *testing.T) {
187+
t.Parallel()
188+
189+
now := time.Now()
190+
testCases := []struct {
191+
storage *metricsStorage
192+
sample metrics.Sample
193+
labels []prompb.Label
194+
expected []prompb.TimeSeries
195+
}{
196+
{
197+
storage: newMetricsStorage(),
198+
sample: metrics.Sample{
199+
Metric: &metrics.Metric{
200+
Name: "test",
201+
Type: metrics.Trend,
202+
},
203+
Value: 1.0,
204+
Time: now,
205+
},
206+
expected: []prompb.TimeSeries{
207+
buildTimeSeries("k6_test_count", 1.0, now),
208+
buildTimeSeries("k6_test_sum", 1.0, now),
209+
buildTimeSeries("k6_test_min", 1.0, now),
210+
buildTimeSeries("k6_test_max", 1.0, now),
211+
buildTimeSeries("k6_test_avg", 1.0, now),
212+
buildTimeSeries("k6_test_med", 1.0, now),
213+
buildTimeSeries("k6_test_p90", 1.0, now),
214+
buildTimeSeries("k6_test_p95", 1.0, now),
215+
},
216+
},
217+
}
218+
219+
for _, tc := range testCases {
220+
m := &PrometheusMapping{}
221+
ts := m.MapTrend(tc.storage, tc.sample, tc.labels)
222+
assertTimeSeriesMatch(t, tc.expected, ts)
223+
}
224+
}

0 commit comments

Comments
 (0)