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

Commit ea69b46

Browse files
committed
metrics: Metrics to monitoring/v3.MetricDescriptor
A converter from Metrics to a monitoring/v3.MetricDescriptor Updates #64.
1 parent c719bc1 commit ea69b46

3 files changed

Lines changed: 201 additions & 5 deletions

File tree

metrics.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,91 @@ directly to Stackdriver Metrics.
2020
*/
2121

2222
import (
23+
"errors"
2324
"fmt"
25+
"path"
2426

2527
"github.com/golang/protobuf/ptypes/timestamp"
2628

2729
distributionpb "google.golang.org/genproto/googleapis/api/distribution"
30+
labelpb "google.golang.org/genproto/googleapis/api/label"
31+
googlemetricpb "google.golang.org/genproto/googleapis/api/metric"
2832
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
2933

3034
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
3135
)
3236

37+
var errNilMetric = errors.New("expecting a non-nil metric")
38+
39+
func (se *statsExporter) protoToMonitoringMetricDescriptor(metric *metricspb.Metric) (*googlemetricpb.MetricDescriptor, error) {
40+
if metric == nil {
41+
return nil, errNilMetric
42+
}
43+
44+
metricName, description, unit, _ := metricProseFromProto(metric)
45+
metricType, _ := se.metricTypeFromProto(metricName)
46+
displayName := se.displayName(metricName)
47+
metricKind, valueType := protoMetricDescriptorTypeToMetricKind(metric)
48+
49+
sdm := &googlemetricpb.MetricDescriptor{
50+
Name: fmt.Sprintf("projects/%s/metricDescriptors/%s", se.o.ProjectID, metricType),
51+
DisplayName: displayName,
52+
Description: description,
53+
Unit: unit,
54+
Type: metricType,
55+
MetricKind: metricKind,
56+
ValueType: valueType,
57+
Labels: labelDescriptorsFromProto(se.defaultLabels, metric.GetMetricDescriptor().GetLabelKeys()),
58+
}
59+
60+
return sdm, nil
61+
}
62+
63+
func labelDescriptorsFromProto(defaults map[string]labelValue, protoLabelKeys []*metricspb.LabelKey) []*labelpb.LabelDescriptor {
64+
labelDescriptors := make([]*labelpb.LabelDescriptor, 0, len(defaults)+len(protoLabelKeys))
65+
66+
// Fill in the defaults first.
67+
for key, lbl := range defaults {
68+
labelDescriptors = append(labelDescriptors, &labelpb.LabelDescriptor{
69+
Key: sanitize(key),
70+
Description: lbl.desc,
71+
ValueType: labelpb.LabelDescriptor_STRING,
72+
})
73+
}
74+
75+
// Now fill in those from the metric.
76+
for _, protoKey := range protoLabelKeys {
77+
labelDescriptors = append(labelDescriptors, &labelpb.LabelDescriptor{
78+
Key: sanitize(protoKey.GetKey()),
79+
Description: protoKey.GetDescription(),
80+
ValueType: labelpb.LabelDescriptor_STRING, // We only use string tags
81+
})
82+
}
83+
return labelDescriptors
84+
}
85+
86+
func metricProseFromProto(metric *metricspb.Metric) (name, description, unit string, ok bool) {
87+
mname := metric.GetName()
88+
if mname != "" {
89+
name = mname
90+
return
91+
}
92+
93+
md := metric.GetMetricDescriptor()
94+
95+
name = md.GetName()
96+
unit = md.GetUnit()
97+
description = md.GetDescription()
98+
99+
return
100+
}
101+
102+
func (se *statsExporter) metricTypeFromProto(name string) (string, bool) {
103+
// TODO: (@odeke-em) support non-"custom.googleapis.com" metrics names.
104+
name = path.Join("custom.googleapis.com", "opencensus", name)
105+
return name, true
106+
}
107+
33108
func fromProtoPoint(startTime *timestamp.Timestamp, pt *metricspb.Point) (*monitoringpb.Point, error) {
34109
if pt == nil {
35110
return nil, nil
@@ -133,3 +208,33 @@ func bucketCounts(buckets []*metricspb.DistributionValue_Bucket) []int64 {
133208
}
134209
return bucketCounts
135210
}
211+
212+
func protoMetricDescriptorTypeToMetricKind(m *metricspb.Metric) (googlemetricpb.MetricDescriptor_MetricKind, googlemetricpb.MetricDescriptor_ValueType) {
213+
dt := m.GetMetricDescriptor()
214+
if dt == nil {
215+
return googlemetricpb.MetricDescriptor_METRIC_KIND_UNSPECIFIED, googlemetricpb.MetricDescriptor_VALUE_TYPE_UNSPECIFIED
216+
}
217+
218+
switch dt.Type {
219+
case metricspb.MetricDescriptor_CUMULATIVE_INT64:
220+
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_INT64
221+
222+
case metricspb.MetricDescriptor_CUMULATIVE_DOUBLE:
223+
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_DOUBLE
224+
225+
case metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION:
226+
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_DISTRIBUTION
227+
228+
case metricspb.MetricDescriptor_GAUGE_DOUBLE:
229+
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_DOUBLE
230+
231+
case metricspb.MetricDescriptor_GAUGE_INT64:
232+
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_INT64
233+
234+
case metricspb.MetricDescriptor_GAUGE_DISTRIBUTION:
235+
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_DISTRIBUTION
236+
237+
default:
238+
return googlemetricpb.MetricDescriptor_METRIC_KIND_UNSPECIFIED, googlemetricpb.MetricDescriptor_VALUE_TYPE_UNSPECIFIED
239+
}
240+
}

metrics_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,102 @@ package stackdriver
1717
import (
1818
"encoding/json"
1919
"reflect"
20+
"strings"
2021
"testing"
2122

2223
"github.com/golang/protobuf/ptypes/timestamp"
2324
distributionpb "google.golang.org/genproto/googleapis/api/distribution"
25+
googlemetricpb "google.golang.org/genproto/googleapis/api/metric"
2426
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
2527

2628
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
2729
)
2830

31+
func TestProtoToMonitoringMetricDescriptor(t *testing.T) {
32+
tests := []struct {
33+
in *metricspb.Metric
34+
want *googlemetricpb.MetricDescriptor
35+
wantErr string
36+
37+
statsExporter *statsExporter
38+
}{
39+
{in: nil, wantErr: "non-nil metric"},
40+
{
41+
in: &metricspb.Metric{},
42+
statsExporter: &statsExporter{
43+
o: Options{ProjectID: "test"},
44+
},
45+
want: &googlemetricpb.MetricDescriptor{
46+
Name: "projects/test/metricDescriptors/custom.googleapis.com/opencensus",
47+
Type: "custom.googleapis.com/opencensus",
48+
DisplayName: "OpenCensus",
49+
},
50+
},
51+
{
52+
in: &metricspb.Metric{
53+
Descriptor_: &metricspb.Metric_Name{Name: "with_name"},
54+
},
55+
statsExporter: &statsExporter{
56+
o: Options{ProjectID: "test"},
57+
},
58+
want: &googlemetricpb.MetricDescriptor{
59+
Name: "projects/test/metricDescriptors/custom.googleapis.com/opencensus/with_name",
60+
Type: "custom.googleapis.com/opencensus/with_name",
61+
DisplayName: "OpenCensus/with_name",
62+
},
63+
},
64+
{
65+
in: &metricspb.Metric{
66+
Descriptor_: &metricspb.Metric_MetricDescriptor{
67+
MetricDescriptor: &metricspb.MetricDescriptor{
68+
Name: "with_metric_descriptor",
69+
Description: "This is with metric descriptor",
70+
Unit: "By",
71+
},
72+
},
73+
},
74+
statsExporter: &statsExporter{
75+
o: Options{ProjectID: "test"},
76+
},
77+
want: &googlemetricpb.MetricDescriptor{
78+
Name: "projects/test/metricDescriptors/custom.googleapis.com/opencensus/with_metric_descriptor",
79+
Type: "custom.googleapis.com/opencensus/with_metric_descriptor",
80+
DisplayName: "OpenCensus/with_metric_descriptor",
81+
Description: "This is with metric descriptor",
82+
Unit: "By",
83+
},
84+
},
85+
}
86+
87+
for i, tt := range tests {
88+
se := tt.statsExporter
89+
if se == nil {
90+
se = new(statsExporter)
91+
}
92+
got, err := se.protoToMonitoringMetricDescriptor(tt.in)
93+
if tt.wantErr != "" {
94+
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
95+
t.Errorf("#%d: \nGot %v\nWanted error substring %q", i, err, tt.wantErr)
96+
}
97+
continue
98+
}
99+
100+
if err != nil {
101+
t.Errorf("#%d: Unexpected error: %v", i, err)
102+
continue
103+
}
104+
105+
if !reflect.DeepEqual(got, tt.want) {
106+
// Our saving grace is serialization equality since some
107+
// unexported fields could be present in the various values.
108+
gj, wj := serializeAsJSON(got), serializeAsJSON(tt.want)
109+
if gj != wj {
110+
t.Errorf("#%d: Unmatched JSON\nGot:\n\t%s\nWant:\n\t%s", i, gj, wj)
111+
}
112+
}
113+
}
114+
}
115+
29116
func TestProtoMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
30117
startTimestamp := &timestamp.Timestamp{
31118
Seconds: 1543160298,

stats.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,7 @@ func (e *statsExporter) createMeasure(ctx context.Context, v *view.View) error {
278278

279279
var displayName string
280280
if e.o.GetMetricDisplayName == nil {
281-
displayNamePrefix := defaultDisplayNamePrefix
282-
if e.o.MetricPrefix != "" {
283-
displayNamePrefix = e.o.MetricPrefix
284-
}
285-
displayName = path.Join(displayNamePrefix, viewName)
281+
displayName = e.displayName(viewName)
286282
} else {
287283
displayName = e.o.GetMetricDisplayName(v)
288284
}
@@ -308,6 +304,14 @@ func (e *statsExporter) createMeasure(ctx context.Context, v *view.View) error {
308304
return nil
309305
}
310306

307+
func (e *statsExporter) displayName(suffix string) string {
308+
displayNamePrefix := defaultDisplayNamePrefix
309+
if e.o.MetricPrefix != "" {
310+
displayNamePrefix = e.o.MetricPrefix
311+
}
312+
return path.Join(displayNamePrefix, suffix)
313+
}
314+
311315
func newPoint(v *view.View, row *view.Row, start, end time.Time) *monitoringpb.Point {
312316
switch v.Aggregation.Type {
313317
case view.AggTypeLastValue:

0 commit comments

Comments
 (0)