internal/telemetry/export/ocagent: correctly marshal points to JSON

This change adds a custom MarshalJSON func to Point so that values
are formatted the same way jsonpb formats them. This allows the
OpenCensus service to determine the concrete type of the point's
value when unmarshaling.

What works:
* Points with Int64, Double, and Distribution values will marshal correctly.

What does not work:
* Points with Summary values will not marshal.

Updates golang/go#33819

Change-Id: Ia76ebff4e0e2b6ff2ddf72b8d6187f49069d4cad
Reviewed-on: https://go-review.googlesource.com/c/tools/+/207838
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/telemetry/export/ocagent/wire/metrics.go b/internal/telemetry/export/ocagent/wire/metrics.go
index ea82bec..d9ff519 100644
--- a/internal/telemetry/export/ocagent/wire/metrics.go
+++ b/internal/telemetry/export/ocagent/wire/metrics.go
@@ -4,6 +4,11 @@
 
 package wire
 
+import (
+	"encoding/json"
+	"fmt"
+)
+
 type ExportMetricsServiceRequest struct {
 	Node     *Node     `json:"node,omitempty"`
 	Metrics  []*Metric `json:"metrics,omitempty"`
@@ -62,6 +67,48 @@
 	Int64Value int64 `json:"int64Value,omitempty"`
 }
 
+// MarshalJSON creates JSON formatted the same way as jsonpb so that the
+// OpenCensus service can correctly determine the underlying value type.
+// This custom MarshalJSON exists because,
+// by default *Point is JSON marshalled as:
+//     {"value": {"int64Value": 1}}
+// but it should be marshalled as:
+//     {"int64Value": 1}
+func (p *Point) MarshalJSON() ([]byte, error) {
+	if p == nil {
+		return []byte("null"), nil
+	}
+
+	switch d := p.Value.(type) {
+	case PointInt64Value:
+		return json.Marshal(&struct {
+			Timestamp *Timestamp `json:"timestamp,omitempty"`
+			Value     int64      `json:"int64Value,omitempty"`
+		}{
+			Timestamp: p.Timestamp,
+			Value:     d.Int64Value,
+		})
+	case PointDoubleValue:
+		return json.Marshal(&struct {
+			Timestamp *Timestamp `json:"timestamp,omitempty"`
+			Value     float64    `json:"doubleValue,omitempty"`
+		}{
+			Timestamp: p.Timestamp,
+			Value:     d.DoubleValue,
+		})
+	case PointDistributionValue:
+		return json.Marshal(&struct {
+			Timestamp *Timestamp         `json:"timestamp,omitempty"`
+			Value     *DistributionValue `json:"distributionValue,omitempty"`
+		}{
+			Timestamp: p.Timestamp,
+			Value:     d.DistributionValue,
+		})
+	default:
+		return nil, fmt.Errorf("unknown point type %T", p.Value)
+	}
+}
+
 type PointDoubleValue struct {
 	DoubleValue float64 `json:"doubleValue,omitempty"`
 }
diff --git a/internal/telemetry/export/ocagent/wire/metrics_test.go b/internal/telemetry/export/ocagent/wire/metrics_test.go
new file mode 100644
index 0000000..b510ee2
--- /dev/null
+++ b/internal/telemetry/export/ocagent/wire/metrics_test.go
@@ -0,0 +1,76 @@
+package wire
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestMarshalJSON(t *testing.T) {
+	tests := []struct {
+		name string
+		pt   *Point
+		want string
+	}{
+		{
+			"PointInt64",
+			&Point{
+				Value: PointInt64Value{
+					Int64Value: 5,
+				},
+			},
+			`{"int64Value":5}`,
+		},
+		{
+			"PointDouble",
+			&Point{
+				Value: PointDoubleValue{
+					DoubleValue: 3.14,
+				},
+			},
+			`{"doubleValue":3.14}`,
+		},
+		{
+			"PointDistribution",
+			&Point{
+				Value: PointDistributionValue{
+					DistributionValue: &DistributionValue{
+						Count: 3,
+						Sum:   10,
+						Buckets: []*Bucket{
+							{
+								Count: 1,
+							},
+							{
+								Count: 2,
+							},
+						},
+						BucketOptions: BucketOptionsExplicit{
+							Bounds: []float64{
+								0, 5,
+							},
+						},
+					},
+				},
+			},
+			`{"distributionValue":{"count":3,"sum":10,"bucket_options":{"bounds":[0,5]},"buckets":[{"count":1},{"count":2}]}}`,
+		},
+		{
+			"nil point",
+			nil,
+			`null`,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			buf, err := tt.pt.MarshalJSON()
+			if err != nil {
+				t.Fatalf("Got:\n%v\nWant:\n%v", err, nil)
+			}
+			got := string(buf)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", got, tt.want)
+			}
+		})
+	}
+}