Skip to content

Commit eb6b09e

Browse files
authored
Add option to send underlying metric timestamps on prometheus output (#11)
1 parent 7197eac commit eb6b09e

2 files changed

Lines changed: 117 additions & 13 deletions

File tree

prometheus.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import (
2323
"net/http"
2424
"sort"
2525
"sync"
26+
"time"
2627

2728
"go.opencensus.io/trace"
2829

30+
"github.com/golang/protobuf/ptypes"
2931
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
3032
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
3133
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
@@ -44,10 +46,11 @@ type Exporter struct {
4446

4547
// Options customizes a created Prometheus Exporter.
4648
type Options struct {
47-
Namespace string
48-
OnError func(err error)
49-
ConstLabels prometheus.Labels // ConstLabels will be set as labels on all views.
50-
Registry *prometheus.Registry
49+
Namespace string
50+
OnError func(err error)
51+
ConstLabels prometheus.Labels // ConstLabels will be set as labels on all views.
52+
Registry *prometheus.Registry
53+
SendTimestamps bool
5154
}
5255

5356
// New is the constructor to make an Exporter with the defined Options.
@@ -237,7 +240,7 @@ func (c *collector) protoTimeSeriesToPrometheusMetrics(ctx context.Context, metr
237240

238241
pmetrics := make([]prometheus.Metric, 0, len(ts.Points))
239242
for _, point := range ts.Points {
240-
pmet, err := protoMetricToPrometheusMetric(ctx, point, desc, derivedPrometheusValueType, labelValues)
243+
pmet, err := protoMetricToPrometheusMetric(ctx, point, desc, derivedPrometheusValueType, labelValues, c.opts.SendTimestamps)
241244
if err == nil {
242245
pmetrics = append(pmetrics, pmet)
243246
} else {
@@ -277,7 +280,12 @@ func protoLabelKeysToLabels(protoLabelKeys []*metricspb.LabelKey) []string {
277280
return labelKeys
278281
}
279282

280-
func protoMetricToPrometheusMetric(ctx context.Context, point *metricspb.Point, desc *prometheus.Desc, derivedPrometheusType prometheus.ValueType, labelValues []string) (prometheus.Metric, error) {
283+
func protoMetricToPrometheusMetric(ctx context.Context, point *metricspb.Point, desc *prometheus.Desc, derivedPrometheusType prometheus.ValueType, labelValues []string, sendTimestamps bool) (prometheus.Metric, error) {
284+
timestamp, err := ptypes.Timestamp(point.Timestamp)
285+
if err != nil {
286+
timestamp = time.Now()
287+
}
288+
281289
switch value := point.Value.(type) {
282290
case *metricspb.Point_DistributionValue:
283291
dValue := value.DistributionValue
@@ -308,14 +316,26 @@ func protoMetricToPrometheusMetric(ctx context.Context, point *metricspb.Point,
308316
cumCount += countPerBucket
309317
points[bucket] = cumCount
310318
}
311-
return prometheus.NewConstHistogram(desc, uint64(dValue.Count), dValue.Sum, points, labelValues...)
319+
metric, err := prometheus.NewConstHistogram(desc, uint64(dValue.Count), dValue.Sum, points, labelValues...)
320+
if err != nil || !sendTimestamps {
321+
return metric, err
322+
}
323+
return prometheus.NewMetricWithTimestamp(timestamp, metric), nil
312324

313325
case *metricspb.Point_Int64Value:
314326
// Derive the Prometheus
315-
return prometheus.NewConstMetric(desc, derivedPrometheusType, float64(value.Int64Value), labelValues...)
327+
metric, err := prometheus.NewConstMetric(desc, derivedPrometheusType, float64(value.Int64Value), labelValues...)
328+
if err != nil || !sendTimestamps {
329+
return metric, err
330+
}
331+
return prometheus.NewMetricWithTimestamp(timestamp, metric), nil
316332

317333
case *metricspb.Point_DoubleValue:
318-
return prometheus.NewConstMetric(desc, derivedPrometheusType, value.DoubleValue, labelValues...)
334+
metric, err := prometheus.NewConstMetric(desc, derivedPrometheusType, value.DoubleValue, labelValues...)
335+
if err != nil || !sendTimestamps {
336+
return metric, err
337+
}
338+
return prometheus.NewMetricWithTimestamp(timestamp, metric), nil
319339

320340
default:
321341
return nil, fmt.Errorf("Unhandled type: %T", point.Value)

prometheus_test.go

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ var (
3939
Seconds: 1543160298,
4040
Nanos: 100000997,
4141
}
42+
// before is a scrape that happened 5s earlier
43+
startTimestampBefore = &timestamp.Timestamp{
44+
Seconds: 1543160293,
45+
Nanos: 100000090,
46+
}
47+
endTimestampBefore = &timestamp.Timestamp{
48+
Seconds: 1543160293,
49+
Nanos: 100000997,
50+
}
4251
)
4352

4453
func TestOnlyCumulativeWindowSupported(t *testing.T) {
@@ -303,31 +312,31 @@ func makeMetrics() []*metricspb.Metric {
303312
},
304313
Timeseries: []*metricspb.TimeSeries{
305314
{
306-
StartTimestamp: startTimestamp,
315+
StartTimestamp: startTimestampBefore,
307316
LabelValues: []*metricspb.LabelValue{
308317
{Value: "windows"},
309318
{Value: "x86"},
310319
{Value: "Storage"},
311320
},
312321
Points: []*metricspb.Point{
313322
{
314-
Timestamp: endTimestamp,
323+
Timestamp: endTimestampBefore,
315324
Value: &metricspb.Point_Int64Value{
316325
Int64Value: 99,
317326
},
318327
},
319328
},
320329
},
321330
{
322-
StartTimestamp: startTimestamp,
331+
StartTimestamp: startTimestampBefore,
323332
LabelValues: []*metricspb.LabelValue{
324333
{Value: "darwin"},
325334
{Value: "386"},
326335
{Value: "Ops"},
327336
},
328337
Points: []*metricspb.Point{
329338
{
330-
Timestamp: endTimestamp,
339+
Timestamp: endTimestampBefore,
331340
Value: &metricspb.Point_DoubleValue{
332341
DoubleValue: 49.5,
333342
},
@@ -443,3 +452,78 @@ with_metric_descriptor_count 2
443452
t.Errorf("Mismatched output\nGot:\n%s\nWant:\n%s", g, w)
444453
}
445454
}
455+
456+
func TestMetricsEndpointWithTimestampOutput(t *testing.T) {
457+
exp, err := New(Options{
458+
SendTimestamps: true,
459+
})
460+
if err != nil {
461+
t.Fatalf("Failed to create Prometheus exporter: %v", err)
462+
}
463+
464+
srv := httptest.NewServer(exp)
465+
defer srv.Close()
466+
467+
// Now record some metrics.
468+
metrics := makeMetrics()
469+
for _, metric := range metrics {
470+
exp.ExportMetric(context.Background(), nil, nil, metric)
471+
}
472+
473+
var i int
474+
var output string
475+
for {
476+
time.Sleep(10 * time.Millisecond)
477+
if i == 1000 {
478+
t.Fatal("no output at / (10s wait)")
479+
}
480+
i++
481+
482+
resp, err := http.Get(srv.URL)
483+
if err != nil {
484+
t.Fatalf("Failed to get metrics on / error: %v", err)
485+
}
486+
487+
slurp, err := ioutil.ReadAll(resp.Body)
488+
_ = resp.Body.Close()
489+
if err != nil {
490+
t.Fatalf("Failed to read body: %v", err)
491+
}
492+
493+
output = string(slurp)
494+
if output != "" {
495+
break
496+
}
497+
}
498+
499+
if strings.Contains(output, "collected before with the same name and label values") {
500+
t.Fatalf("metric name and labels were duplicated but must be unique. Got\n\t%q", output)
501+
}
502+
503+
if strings.Contains(output, "error(s) occurred") {
504+
t.Fatalf("error reported by Prometheus registry:\n\t%s", output)
505+
}
506+
507+
want := `# HELP a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_ Unlimited metric key lengths
508+
# TYPE a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_ counter
509+
a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_{arch="x86",keykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykey="",my_org_department="Storage",os="windows"} 99 1543160298100
510+
# HELP this_one_there_where_ Extra ones
511+
# TYPE this_one_there_where_ gauge
512+
this_one_there_where_{arch="386",my_org_department="Ops",os="darwin"} 49.5 1543160293100
513+
this_one_there_where_{arch="x86",my_org_department="Storage",os="windows"} 99 1543160293100
514+
# HELP with_metric_descriptor This is a test
515+
# TYPE with_metric_descriptor histogram
516+
with_metric_descriptor_bucket{le="0"} 0 1543160298100
517+
with_metric_descriptor_bucket{le="10"} 1 1543160298100
518+
with_metric_descriptor_bucket{le="20"} 1 1543160298100
519+
with_metric_descriptor_bucket{le="30"} 1 1543160298100
520+
with_metric_descriptor_bucket{le="40"} 6 1543160298100
521+
with_metric_descriptor_bucket{le="+Inf"} 2 1543160298100
522+
with_metric_descriptor_sum 61.9 1543160298100
523+
with_metric_descriptor_count 2 1543160298100
524+
`
525+
526+
if g, w := output, want; g != w {
527+
t.Errorf("Mismatched output\nGot:\n%s\nWant:\n%s", g, w)
528+
}
529+
}

0 commit comments

Comments
 (0)