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

Commit 52cb972

Browse files
authored
Support exporting Exemplar. (#124)
* Support exporting Exemplar. * Update a switch statement and add TODOs.
1 parent 9f1fc3b commit 52cb972

2 files changed

Lines changed: 119 additions & 10 deletions

File tree

metrics.go

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"context"
2424
"errors"
2525
"fmt"
26+
"github.com/golang/protobuf/proto"
27+
"github.com/golang/protobuf/ptypes/any"
2628
"github.com/golang/protobuf/ptypes/timestamp"
2729
"go.opencensus.io/trace"
2830

@@ -41,6 +43,14 @@ var (
4143
errUnspecifiedMetricKind = errors.New("metric kind is unpsecified")
4244
)
4345

46+
const (
47+
exemplarAttachmentTypeString = "type.googleapis.com/google.protobuf.StringValue"
48+
exemplarAttachmentTypeSpanCtx = "type.googleapis.com/google.monitoring.v3.SpanContext"
49+
50+
// TODO(songy23): add support for this.
51+
// exemplarAttachmentTypeDroppedLabels = "type.googleapis.com/google.monitoring.v3.DroppedLabels"
52+
)
53+
4454
// ExportMetrics exports OpenCensus Metrics to Stackdriver Monitoring.
4555
func (se *statsExporter) ExportMetrics(ctx context.Context, metrics []*metricdata.Metric) error {
4656
if len(metrics) == 0 {
@@ -340,7 +350,7 @@ func (se *statsExporter) metricTsToMpbPoint(ts *metricdata.TimeSeries, metricKin
340350
startTime = nil
341351
}
342352

343-
spt, err := metricPointToMpbPoint(startTime, &pt)
353+
spt, err := metricPointToMpbPoint(startTime, &pt, se.o.ProjectID)
344354
if err != nil {
345355
return nil, err
346356
}
@@ -349,12 +359,12 @@ func (se *statsExporter) metricTsToMpbPoint(ts *metricdata.TimeSeries, metricKin
349359
return sptl, nil
350360
}
351361

352-
func metricPointToMpbPoint(startTime *timestamp.Timestamp, pt *metricdata.Point) (*monitoringpb.Point, error) {
362+
func metricPointToMpbPoint(startTime *timestamp.Timestamp, pt *metricdata.Point, projectID string) (*monitoringpb.Point, error) {
353363
if pt == nil {
354364
return nil, nil
355365
}
356366

357-
mptv, err := metricPointToMpbValue(pt)
367+
mptv, err := metricPointToMpbValue(pt, projectID)
358368
if err != nil {
359369
return nil, err
360370
}
@@ -369,7 +379,7 @@ func metricPointToMpbPoint(startTime *timestamp.Timestamp, pt *metricdata.Point)
369379
return mpt, nil
370380
}
371381

372-
func metricPointToMpbValue(pt *metricdata.Point) (*monitoringpb.TypedValue, error) {
382+
func metricPointToMpbValue(pt *metricdata.Point, projectID string) (*monitoringpb.TypedValue, error) {
373383
if pt == nil {
374384
return nil, nil
375385
}
@@ -423,18 +433,67 @@ func metricPointToMpbValue(pt *metricdata.Point) (*monitoringpb.TypedValue, erro
423433
},
424434
}
425435
}
426-
mv.DistributionValue.BucketCounts = addZeroBucketCountOnCondition(insertZeroBound, metricBucketToBucketCounts(dv.Buckets)...)
436+
bucketCounts, exemplars := metricBucketToBucketCountsAndExemplars(dv.Buckets, projectID)
437+
mv.DistributionValue.BucketCounts = addZeroBucketCountOnCondition(insertZeroBound, bucketCounts...)
438+
mv.DistributionValue.Exemplars = exemplars
427439

428440
tval = &monitoringpb.TypedValue{Value: mv}
429441
}
430442

431443
return tval, err
432444
}
433445

434-
func metricBucketToBucketCounts(buckets []metricdata.Bucket) []int64 {
446+
func metricBucketToBucketCountsAndExemplars(buckets []metricdata.Bucket, projectID string) ([]int64, []*distributionpb.Distribution_Exemplar) {
435447
bucketCounts := make([]int64, len(buckets))
448+
var exemplars []*distributionpb.Distribution_Exemplar
436449
for i, bucket := range buckets {
437450
bucketCounts[i] = bucket.Count
451+
if bucket.Exemplar != nil {
452+
exemplars = append(exemplars, metricExemplarToPbExemplar(bucket.Exemplar, projectID))
453+
}
454+
}
455+
return bucketCounts, exemplars
456+
}
457+
458+
func metricExemplarToPbExemplar(exemplar *metricdata.Exemplar, projectID string) *distributionpb.Distribution_Exemplar {
459+
return &distributionpb.Distribution_Exemplar{
460+
Value: exemplar.Value,
461+
Timestamp: timestampProto(exemplar.Timestamp),
462+
Attachments: attachmentsToPbAttachments(exemplar.Attachments, projectID),
463+
}
464+
}
465+
466+
func attachmentsToPbAttachments(attachments metricdata.Attachments, projectID string) []*any.Any {
467+
var pbAttachments []*any.Any
468+
for _, v := range attachments {
469+
switch v.(type) {
470+
case trace.SpanContext:
471+
spanCtx, _ := v.(trace.SpanContext)
472+
pbAttachments = append(pbAttachments, toPbSpanCtxAttachment(spanCtx, projectID))
473+
default:
474+
// Treat everything else as plain string for now.
475+
// TODO(songy23): add support for dropped label attachments.
476+
pbAttachments = append(pbAttachments, toPbStringAttachment(v))
477+
}
478+
}
479+
return pbAttachments
480+
}
481+
482+
func toPbStringAttachment(v interface{}) *any.Any {
483+
s := fmt.Sprintf("%v", v)
484+
return &any.Any{
485+
TypeUrl: exemplarAttachmentTypeString,
486+
Value: []byte(s),
487+
}
488+
}
489+
490+
func toPbSpanCtxAttachment(spanCtx trace.SpanContext, projectID string) *any.Any {
491+
pbSpanCtx := monitoringpb.SpanContext{
492+
SpanName: fmt.Sprintf("projects/%s/traces/%s/spans/%s", projectID, spanCtx.TraceID.String(), spanCtx.SpanID.String()),
493+
}
494+
bytes, _ := proto.Marshal(&pbSpanCtx)
495+
return &any.Any{
496+
TypeUrl: exemplarAttachmentTypeSpanCtx,
497+
Value: bytes,
438498
}
439-
return bucketCounts
440499
}

metrics_test.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ package stackdriver
1616

1717
import (
1818
"context"
19+
"fmt"
1920
"strings"
2021
"testing"
22+
"time"
2123

24+
"github.com/golang/protobuf/proto"
25+
"github.com/golang/protobuf/ptypes/any"
2226
"github.com/golang/protobuf/ptypes/timestamp"
2327

2428
distributionpb "google.golang.org/genproto/googleapis/api/distribution"
@@ -29,6 +33,7 @@ import (
2933

3034
"go.opencensus.io/metric/metricdata"
3135
"go.opencensus.io/resource"
36+
"go.opencensus.io/trace"
3237
)
3338

3439
var se = &statsExporter{
@@ -85,6 +90,7 @@ func TestMetricToCreateTimeSeriesRequest(t *testing.T) {
8590
Seconds: 1543160298,
8691
Nanos: 100000090,
8792
}
93+
startTime := time.Unix(1543160298, 100000090)
8894
endTimestamp := &timestamp.Timestamp{
8995
Seconds: 1543160298,
9096
Nanos: 100000997,
@@ -116,7 +122,11 @@ func TestMetricToCreateTimeSeriesRequest(t *testing.T) {
116122
Sum: 11.9,
117123
SumOfSquaredDeviation: 0,
118124
Buckets: []metricdata.Bucket{
119-
{Count: 1}, {}, {}, {},
125+
{
126+
Count: 1,
127+
Exemplar: &metricdata.Exemplar{Value: 11.9, Timestamp: startTime, Attachments: map[string]interface{}{"key": "value"}},
128+
},
129+
{}, {}, {},
120130
},
121131
BucketOptions: &metricdata.BucketOptions{
122132
Bounds: []float64{10, 20, 30, 40},
@@ -158,6 +168,18 @@ func TestMetricToCreateTimeSeriesRequest(t *testing.T) {
158168
},
159169
},
160170
},
171+
Exemplars: []*distributionpb.Distribution_Exemplar{
172+
{
173+
Value: 11.9,
174+
Timestamp: startTimestamp,
175+
Attachments: []*any.Any{
176+
{
177+
TypeUrl: exemplarAttachmentTypeString,
178+
Value: []byte("value"),
179+
},
180+
},
181+
},
182+
},
161183
},
162184
},
163185
},
@@ -408,11 +430,21 @@ func TestMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
408430
Seconds: 1543160298,
409431
Nanos: 100000090,
410432
}
433+
startTime := time.Unix(1543160298, 100000090)
411434
endTimestamp := &timestamp.Timestamp{
412435
Seconds: 1543160298,
413436
Nanos: 100000997,
414437
}
415438

439+
traceID := trace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 4, 8, 16, 32, 64, 128}
440+
spanID := trace.SpanID{1, 2, 4, 8, 16, 32, 64, 128}
441+
spanCtx := trace.SpanContext{
442+
TraceID: traceID,
443+
SpanID: spanID,
444+
TraceOptions: 1,
445+
}
446+
wantSpanCtxBytes, _ := proto.Marshal(&monitoringpb.SpanContext{SpanName: fmt.Sprintf("projects/foo/traces/%s/spans/%s", traceID.String(), spanID.String())})
447+
416448
tests := []struct {
417449
in *metricdata.Point
418450
want *monitoringpb.Point
@@ -426,7 +458,13 @@ func TestMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
426458
Sum: 11.9,
427459
SumOfSquaredDeviation: 0,
428460
Buckets: []metricdata.Bucket{
429-
{}, {Count: 1}, {}, {}, {},
461+
{},
462+
{
463+
Count: 1,
464+
Exemplar: &metricdata.Exemplar{Value: 11.9, Timestamp: startTime, Attachments: map[string]interface{}{"SpanContext": spanCtx}}},
465+
{},
466+
{},
467+
{},
430468
},
431469
BucketOptions: &metricdata.BucketOptions{
432470
Bounds: []float64{0, 10, 20, 30, 40},
@@ -452,6 +490,18 @@ func TestMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
452490
},
453491
},
454492
},
493+
Exemplars: []*distributionpb.Distribution_Exemplar{
494+
{
495+
Value: 11.9,
496+
Timestamp: startTimestamp,
497+
Attachments: []*any.Any{
498+
{
499+
TypeUrl: exemplarAttachmentTypeSpanCtx,
500+
Value: wantSpanCtxBytes,
501+
},
502+
},
503+
},
504+
},
455505
},
456506
},
457507
},
@@ -490,7 +540,7 @@ func TestMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
490540
}
491541

492542
for i, tt := range tests {
493-
mpt, err := metricPointToMpbPoint(startTimestamp, tt.in)
543+
mpt, err := metricPointToMpbPoint(startTimestamp, tt.in, "foo")
494544
if tt.wantErr != "" {
495545
continue
496546
}

0 commit comments

Comments
 (0)