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

Commit

Permalink
metrics: Metrics to monitoring/v3.MetricDescriptor
Browse files Browse the repository at this point in the history
A converter from Metrics to a monitoring/v3.MetricDescriptor

Updates #64.
  • Loading branch information
odeke-em committed Dec 11, 2018
1 parent c719bc1 commit ea69b46
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 5 deletions.
105 changes: 105 additions & 0 deletions metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,91 @@ directly to Stackdriver Metrics.
*/

import (
"errors"
"fmt"
"path"

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

distributionpb "google.golang.org/genproto/googleapis/api/distribution"
labelpb "google.golang.org/genproto/googleapis/api/label"
googlemetricpb "google.golang.org/genproto/googleapis/api/metric"
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"

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

var errNilMetric = errors.New("expecting a non-nil metric")

func (se *statsExporter) protoToMonitoringMetricDescriptor(metric *metricspb.Metric) (*googlemetricpb.MetricDescriptor, error) {
if metric == nil {
return nil, errNilMetric
}

metricName, description, unit, _ := metricProseFromProto(metric)
metricType, _ := se.metricTypeFromProto(metricName)
displayName := se.displayName(metricName)
metricKind, valueType := protoMetricDescriptorTypeToMetricKind(metric)

sdm := &googlemetricpb.MetricDescriptor{
Name: fmt.Sprintf("projects/%s/metricDescriptors/%s", se.o.ProjectID, metricType),
DisplayName: displayName,
Description: description,
Unit: unit,
Type: metricType,
MetricKind: metricKind,
ValueType: valueType,
Labels: labelDescriptorsFromProto(se.defaultLabels, metric.GetMetricDescriptor().GetLabelKeys()),
}

return sdm, nil
}

func labelDescriptorsFromProto(defaults map[string]labelValue, protoLabelKeys []*metricspb.LabelKey) []*labelpb.LabelDescriptor {
labelDescriptors := make([]*labelpb.LabelDescriptor, 0, len(defaults)+len(protoLabelKeys))

// Fill in the defaults first.
for key, lbl := range defaults {
labelDescriptors = append(labelDescriptors, &labelpb.LabelDescriptor{
Key: sanitize(key),
Description: lbl.desc,
ValueType: labelpb.LabelDescriptor_STRING,
})
}

// Now fill in those from the metric.
for _, protoKey := range protoLabelKeys {
labelDescriptors = append(labelDescriptors, &labelpb.LabelDescriptor{
Key: sanitize(protoKey.GetKey()),
Description: protoKey.GetDescription(),
ValueType: labelpb.LabelDescriptor_STRING, // We only use string tags
})
}
return labelDescriptors
}

func metricProseFromProto(metric *metricspb.Metric) (name, description, unit string, ok bool) {
mname := metric.GetName()
if mname != "" {
name = mname
return
}

md := metric.GetMetricDescriptor()

name = md.GetName()
unit = md.GetUnit()
description = md.GetDescription()

return
}

func (se *statsExporter) metricTypeFromProto(name string) (string, bool) {
// TODO: (@odeke-em) support non-"custom.googleapis.com" metrics names.
name = path.Join("custom.googleapis.com", "opencensus", name)
return name, true
}

func fromProtoPoint(startTime *timestamp.Timestamp, pt *metricspb.Point) (*monitoringpb.Point, error) {
if pt == nil {
return nil, nil
Expand Down Expand Up @@ -133,3 +208,33 @@ func bucketCounts(buckets []*metricspb.DistributionValue_Bucket) []int64 {
}
return bucketCounts
}

func protoMetricDescriptorTypeToMetricKind(m *metricspb.Metric) (googlemetricpb.MetricDescriptor_MetricKind, googlemetricpb.MetricDescriptor_ValueType) {
dt := m.GetMetricDescriptor()
if dt == nil {
return googlemetricpb.MetricDescriptor_METRIC_KIND_UNSPECIFIED, googlemetricpb.MetricDescriptor_VALUE_TYPE_UNSPECIFIED
}

switch dt.Type {
case metricspb.MetricDescriptor_CUMULATIVE_INT64:
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_INT64

case metricspb.MetricDescriptor_CUMULATIVE_DOUBLE:
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_DOUBLE

case metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION:
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_DISTRIBUTION

case metricspb.MetricDescriptor_GAUGE_DOUBLE:
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_DOUBLE

case metricspb.MetricDescriptor_GAUGE_INT64:
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_INT64

case metricspb.MetricDescriptor_GAUGE_DISTRIBUTION:
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_DISTRIBUTION

default:
return googlemetricpb.MetricDescriptor_METRIC_KIND_UNSPECIFIED, googlemetricpb.MetricDescriptor_VALUE_TYPE_UNSPECIFIED
}
}
87 changes: 87 additions & 0 deletions metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,102 @@ package stackdriver
import (
"encoding/json"
"reflect"
"strings"
"testing"

"github.com/golang/protobuf/ptypes/timestamp"
distributionpb "google.golang.org/genproto/googleapis/api/distribution"
googlemetricpb "google.golang.org/genproto/googleapis/api/metric"
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"

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

func TestProtoToMonitoringMetricDescriptor(t *testing.T) {
tests := []struct {
in *metricspb.Metric
want *googlemetricpb.MetricDescriptor
wantErr string

statsExporter *statsExporter
}{
{in: nil, wantErr: "non-nil metric"},
{
in: &metricspb.Metric{},
statsExporter: &statsExporter{
o: Options{ProjectID: "test"},
},
want: &googlemetricpb.MetricDescriptor{
Name: "projects/test/metricDescriptors/custom.googleapis.com/opencensus",
Type: "custom.googleapis.com/opencensus",
DisplayName: "OpenCensus",
},
},
{
in: &metricspb.Metric{
Descriptor_: &metricspb.Metric_Name{Name: "with_name"},
},
statsExporter: &statsExporter{
o: Options{ProjectID: "test"},
},
want: &googlemetricpb.MetricDescriptor{
Name: "projects/test/metricDescriptors/custom.googleapis.com/opencensus/with_name",
Type: "custom.googleapis.com/opencensus/with_name",
DisplayName: "OpenCensus/with_name",
},
},
{
in: &metricspb.Metric{
Descriptor_: &metricspb.Metric_MetricDescriptor{
MetricDescriptor: &metricspb.MetricDescriptor{
Name: "with_metric_descriptor",
Description: "This is with metric descriptor",
Unit: "By",
},
},
},
statsExporter: &statsExporter{
o: Options{ProjectID: "test"},
},
want: &googlemetricpb.MetricDescriptor{
Name: "projects/test/metricDescriptors/custom.googleapis.com/opencensus/with_metric_descriptor",
Type: "custom.googleapis.com/opencensus/with_metric_descriptor",
DisplayName: "OpenCensus/with_metric_descriptor",
Description: "This is with metric descriptor",
Unit: "By",
},
},
}

for i, tt := range tests {
se := tt.statsExporter
if se == nil {
se = new(statsExporter)
}
got, err := se.protoToMonitoringMetricDescriptor(tt.in)
if tt.wantErr != "" {
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("#%d: \nGot %v\nWanted error substring %q", i, err, tt.wantErr)
}
continue
}

if err != nil {
t.Errorf("#%d: Unexpected error: %v", i, err)
continue
}

if !reflect.DeepEqual(got, tt.want) {
// Our saving grace is serialization equality since some
// unexported fields could be present in the various values.
gj, wj := serializeAsJSON(got), serializeAsJSON(tt.want)
if gj != wj {
t.Errorf("#%d: Unmatched JSON\nGot:\n\t%s\nWant:\n\t%s", i, gj, wj)
}
}
}
}

func TestProtoMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
startTimestamp := &timestamp.Timestamp{
Seconds: 1543160298,
Expand Down
14 changes: 9 additions & 5 deletions stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,7 @@ func (e *statsExporter) createMeasure(ctx context.Context, v *view.View) error {

var displayName string
if e.o.GetMetricDisplayName == nil {
displayNamePrefix := defaultDisplayNamePrefix
if e.o.MetricPrefix != "" {
displayNamePrefix = e.o.MetricPrefix
}
displayName = path.Join(displayNamePrefix, viewName)
displayName = e.displayName(viewName)
} else {
displayName = e.o.GetMetricDisplayName(v)
}
Expand All @@ -308,6 +304,14 @@ func (e *statsExporter) createMeasure(ctx context.Context, v *view.View) error {
return nil
}

func (e *statsExporter) displayName(suffix string) string {
displayNamePrefix := defaultDisplayNamePrefix
if e.o.MetricPrefix != "" {
displayNamePrefix = e.o.MetricPrefix
}
return path.Join(displayNamePrefix, suffix)
}

func newPoint(v *view.View, row *view.Row, start, end time.Time) *monitoringpb.Point {
switch v.Aggregation.Type {
case view.AggTypeLastValue:
Expand Down

0 comments on commit ea69b46

Please sign in to comment.