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

Commit

Permalink
OpenCensusProto Point to Stackdriver Point
Browse files Browse the repository at this point in the history
An adapter to directly convert from OpenCensus Proto
Point to Stackdriver's monitoring/v3 Point which are
both protobuf based code backed representations.

This the first of a series of changes to allow direct
conversion of metrics so that we can use this exporter
in the OpenCensus Agent/Service exporters, but also so that
applications such as OpenCensus-PHP's daemon can begin to it too.

Also added a TODO for SummaryValue conversion:
A reminder that we should support the conversion
from metricspb.SummaryValue to monitoring/v3 proto
whenever #66 is solved.

Updates #64.
  • Loading branch information
odeke-em committed Dec 10, 2018
1 parent 899e456 commit c719bc1
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 0 deletions.
135 changes: 135 additions & 0 deletions metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2018, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package stackdriver

/*
The code in this file is responsible for converting OpenCensus Proto metrics
directly to Stackdriver Metrics.
*/

import (
"fmt"

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

distributionpb "google.golang.org/genproto/googleapis/api/distribution"
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"

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

func fromProtoPoint(startTime *timestamp.Timestamp, pt *metricspb.Point) (*monitoringpb.Point, error) {
if pt == nil {
return nil, nil
}

mptv, err := protoToMetricPoint(pt.Value)
if err != nil {
return nil, err
}

mpt := &monitoringpb.Point{
Value: mptv,
Interval: &monitoringpb.TimeInterval{
StartTime: startTime,
EndTime: pt.Timestamp,
},
}
return mpt, nil
}

func protoToMetricPoint(value interface{}) (*monitoringpb.TypedValue, error) {
if value == nil {
return nil, nil
}

var err error
var tval *monitoringpb.TypedValue
switch v := value.(type) {
default:
// All the other types are not yet handled.
// TODO: (@odeke-em, @songy23) talk to the Stackdriver team to determine
// the use cases for:
//
// *TypedValue_BoolValue
// *TypedValue_StringValue
//
// and then file feature requests on OpenCensus-Specs and then OpenCensus-Proto,
// lest we shall error here.
//
// TODO: Add conversion from SummaryValue when
// https://github.com/census-ecosystem/opencensus-go-exporter-stackdriver/issues/66
// has been figured out.
err = fmt.Errorf("protoToMetricPoint: unknown Data type: %T", value)

case *metricspb.Point_Int64Value:
tval = &monitoringpb.TypedValue{
Value: &monitoringpb.TypedValue_Int64Value{
Int64Value: v.Int64Value,
},
}

case *metricspb.Point_DoubleValue:
tval = &monitoringpb.TypedValue{
Value: &monitoringpb.TypedValue_DoubleValue{
DoubleValue: v.DoubleValue,
},
}

case *metricspb.Point_DistributionValue:
dv := v.DistributionValue
var mv *monitoringpb.TypedValue_DistributionValue
if dv != nil {
var mean float64
if dv.Count > 0 {
mean = float64(dv.Sum) / float64(dv.Count)
}
mv = &monitoringpb.TypedValue_DistributionValue{
DistributionValue: &distributionpb.Distribution{
Count: dv.Count,
Mean: mean,
SumOfSquaredDeviation: dv.SumOfSquaredDeviation,
BucketCounts: bucketCounts(dv.Buckets),
},
}

if bopts := dv.BucketOptions; bopts != nil && bopts.Type != nil {
bexp, ok := bopts.Type.(*metricspb.DistributionValue_BucketOptions_Explicit_)
if ok && bexp != nil && bexp.Explicit != nil {
mv.DistributionValue.BucketOptions = &distributionpb.Distribution_BucketOptions{
Options: &distributionpb.Distribution_BucketOptions_ExplicitBuckets{
ExplicitBuckets: &distributionpb.Distribution_BucketOptions_Explicit{
Bounds: bexp.Explicit.Bounds[:],
},
},
}
}
}
}
tval = &monitoringpb.TypedValue{Value: mv}
}

return tval, err
}

func bucketCounts(buckets []*metricspb.DistributionValue_Bucket) []int64 {
bucketCounts := make([]int64, len(buckets))
for i, bucket := range buckets {
if bucket != nil {
bucketCounts[i] = bucket.Count
}
}
return bucketCounts
}
146 changes: 146 additions & 0 deletions metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2018, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package stackdriver

import (
"encoding/json"
"reflect"
"testing"

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

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

func TestProtoMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
startTimestamp := &timestamp.Timestamp{
Seconds: 1543160298,
Nanos: 100000090,
}
endTimestamp := &timestamp.Timestamp{
Seconds: 1543160298,
Nanos: 100000997,
}

tests := []struct {
in *metricspb.Point
want *monitoringpb.Point
wantErr string
}{
{
in: &metricspb.Point{
Timestamp: endTimestamp,
Value: &metricspb.Point_DistributionValue{
DistributionValue: &metricspb.DistributionValue{
Count: 1,
Sum: 11.9,
SumOfSquaredDeviation: 0,
Buckets: []*metricspb.DistributionValue_Bucket{
{}, {Count: 1}, {}, {}, {},
},
BucketOptions: &metricspb.DistributionValue_BucketOptions{
Type: &metricspb.DistributionValue_BucketOptions_Explicit_{
Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{
Bounds: []float64{0, 10, 20, 30, 40},
},
},
},
},
},
},
want: &monitoringpb.Point{
Interval: &monitoringpb.TimeInterval{
StartTime: startTimestamp,
EndTime: endTimestamp,
},
Value: &monitoringpb.TypedValue{
Value: &monitoringpb.TypedValue_DistributionValue{
DistributionValue: &distributionpb.Distribution{
Count: 1,
Mean: 11.9,
SumOfSquaredDeviation: 0,
BucketCounts: []int64{0, 1, 0, 0, 0},
BucketOptions: &distributionpb.Distribution_BucketOptions{
Options: &distributionpb.Distribution_BucketOptions_ExplicitBuckets{
ExplicitBuckets: &distributionpb.Distribution_BucketOptions_Explicit{
Bounds: []float64{0, 10, 20, 30, 40},
},
},
},
},
},
},
},
},
{
in: &metricspb.Point{
Timestamp: endTimestamp,
Value: &metricspb.Point_DoubleValue{DoubleValue: 50},
},
want: &monitoringpb.Point{
Interval: &monitoringpb.TimeInterval{
StartTime: startTimestamp,
EndTime: endTimestamp,
},
Value: &monitoringpb.TypedValue{
Value: &monitoringpb.TypedValue_DoubleValue{DoubleValue: 50},
},
},
},
{
in: &metricspb.Point{
Timestamp: endTimestamp,
Value: &metricspb.Point_Int64Value{Int64Value: 17},
},
want: &monitoringpb.Point{
Interval: &monitoringpb.TimeInterval{
StartTime: startTimestamp,
EndTime: endTimestamp,
},
Value: &monitoringpb.TypedValue{
Value: &monitoringpb.TypedValue_Int64Value{Int64Value: 17},
},
},
},
}

for i, tt := range tests {
mpt, err := fromProtoPoint(startTimestamp, tt.in)
if tt.wantErr != "" {
continue
}

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

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

func serializeAsJSON(v interface{}) string {
blob, _ := json.MarshalIndent(v, "", " ")
return string(blob)
}

0 comments on commit c719bc1

Please sign in to comment.