ocagent: convert Int64Data and Float64Data metrics to *wire.Metric.

Histogram data still needs to be converted and
timestamps are not attached as they are not yet available.

What works:

* convertMetric will now convert Int64Data and Float64Data.

What does not work yet:

* Histogram64Int and Histogram64Float will still not be converted.
* StartTime and EndTime will not be attached to timeseries and points.
* MetricDescriptors will not have a unit attached.
* no labels will be attached to timeseries.

Updates golang/go#33819

Change-Id: I65f9af716ba6282e809d0a9d10777d70475e4c83
GitHub-Last-Rev: 10820a9971e1f4c0529fadc567b2533256c2e961
GitHub-Pull-Request: golang/tools#170
Reviewed-on: https://go-review.googlesource.com/c/tools/+/199857
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
diff --git a/internal/telemetry/export/ocagent/metrics.go b/internal/telemetry/export/ocagent/metrics.go
new file mode 100644
index 0000000..d49151f
--- /dev/null
+++ b/internal/telemetry/export/ocagent/metrics.go
@@ -0,0 +1,149 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ocagent
+
+import (
+	"fmt"
+
+	"golang.org/x/tools/internal/telemetry"
+	"golang.org/x/tools/internal/telemetry/export/ocagent/wire"
+	"golang.org/x/tools/internal/telemetry/metric"
+)
+
+// dataToMetricDescriptor return a *wire.MetricDescriptor based on data.
+func dataToMetricDescriptor(data telemetry.MetricData) *wire.MetricDescriptor {
+	if data == nil {
+		return nil
+	}
+	descriptor := &wire.MetricDescriptor{
+		Name:        data.Handle(),
+		Description: getDescription(data),
+		// TODO: Unit?
+		Type:      dataToMetricDescriptorType(data),
+		LabelKeys: getLabelKeys(data),
+	}
+
+	return descriptor
+}
+
+// getDescription returns the description of data.
+func getDescription(data telemetry.MetricData) string {
+	switch d := data.(type) {
+	case *metric.Int64Data:
+		return d.Info.Description
+
+	case *metric.Float64Data:
+		return d.Info.Description
+	}
+
+	return ""
+}
+
+// getLabelKeys returns a slice of *wire.LabelKeys based on the keys
+// in data.
+func getLabelKeys(data telemetry.MetricData) []*wire.LabelKey {
+	switch d := data.(type) {
+	case *metric.Int64Data:
+		return infoKeysToLabelKeys(d.Info.Keys)
+
+	case *metric.Float64Data:
+		return infoKeysToLabelKeys(d.Info.Keys)
+	}
+
+	return nil
+}
+
+// dataToMetricDescriptorType returns a wire.MetricDescriptor_Type based on the
+// underlying type of data.
+func dataToMetricDescriptorType(data telemetry.MetricData) wire.MetricDescriptor_Type {
+	switch d := data.(type) {
+	case *metric.Int64Data:
+		if d.IsGauge {
+			return wire.MetricDescriptor_GAUGE_INT64
+		}
+		return wire.MetricDescriptor_CUMULATIVE_INT64
+
+	case *metric.Float64Data:
+		if d.IsGauge {
+			return wire.MetricDescriptor_GAUGE_DOUBLE
+		}
+		return wire.MetricDescriptor_CUMULATIVE_DOUBLE
+	}
+
+	return wire.MetricDescriptor_UNSPECIFIED
+}
+
+// dataToTimeseries returns a slice of *wire.TimeSeries based on the
+// points in data.
+func dataToTimeseries(data telemetry.MetricData) []*wire.TimeSeries {
+	if data == nil {
+		return nil
+	}
+
+	numRows := numRows(data)
+	timeseries := make([]*wire.TimeSeries, 0, numRows)
+
+	for i := 0; i < numRows; i++ {
+		timeseries = append(timeseries, &wire.TimeSeries{
+			// TODO: attach StartTimestamp
+			// TODO: labels?
+			Points: dataToPoints(data, i),
+		})
+	}
+
+	return timeseries
+}
+
+// numRows returns the number of rows in data.
+func numRows(data telemetry.MetricData) int {
+	switch d := data.(type) {
+	case *metric.Int64Data:
+		return len(d.Rows)
+	case *metric.Float64Data:
+		return len(d.Rows)
+	}
+
+	return 0
+}
+
+// dataToPoints returns an array of *wire.Points based on the point(s)
+// in data at index i.
+func dataToPoints(data telemetry.MetricData, i int) []*wire.Point {
+	switch d := data.(type) {
+	case *metric.Int64Data:
+		return []*wire.Point{
+			{
+				Value: wire.PointInt64Value{
+					Int64Value: d.Rows[i],
+				},
+				// TODO: attach Timestamp
+			},
+		}
+	case *metric.Float64Data:
+		return []*wire.Point{
+			{
+				Value: wire.PointDoubleValue{
+					DoubleValue: d.Rows[i],
+				},
+				// TODO: attach Timestamp
+			},
+		}
+	}
+
+	return nil
+}
+
+// infoKeysToLabelKeys returns an array of *wire.LabelKeys containing the
+// string values of the elements of labelKeys.
+func infoKeysToLabelKeys(infoKeys []interface{}) []*wire.LabelKey {
+	labelKeys := make([]*wire.LabelKey, 0, len(infoKeys))
+	for _, key := range infoKeys {
+		labelKeys = append(labelKeys, &wire.LabelKey{
+			Key: fmt.Sprintf("%v", key),
+		})
+	}
+
+	return labelKeys
+}
diff --git a/internal/telemetry/export/ocagent/metrics_test.go b/internal/telemetry/export/ocagent/metrics_test.go
new file mode 100644
index 0000000..6ec986f
--- /dev/null
+++ b/internal/telemetry/export/ocagent/metrics_test.go
@@ -0,0 +1,483 @@
+package ocagent
+
+import (
+	"reflect"
+	"testing"
+
+	"golang.org/x/tools/internal/telemetry"
+	"golang.org/x/tools/internal/telemetry/export/ocagent/wire"
+	"golang.org/x/tools/internal/telemetry/metric"
+)
+
+func TestDataToMetricDescriptor(t *testing.T) {
+	tests := []struct {
+		name string
+		data telemetry.MetricData
+		want *wire.MetricDescriptor
+	}{
+		{
+			"nil data",
+			nil,
+			nil,
+		},
+		{
+			"Int64Data gauge",
+			&metric.Int64Data{
+				Info: &metric.Scalar{
+					Name:        "int",
+					Description: "int metric",
+					Keys:        []interface{}{"hello"},
+				},
+				IsGauge: true,
+			},
+			&wire.MetricDescriptor{
+				Name:        "int",
+				Description: "int metric",
+				Type:        wire.MetricDescriptor_GAUGE_INT64,
+				LabelKeys: []*wire.LabelKey{
+					&wire.LabelKey{
+						Key: "hello",
+					},
+				},
+			},
+		},
+		{
+			"Float64Data cumulative",
+			&metric.Float64Data{
+				Info: &metric.Scalar{
+					Name:        "float",
+					Description: "float metric",
+					Keys:        []interface{}{"world"},
+				},
+				IsGauge: false,
+			},
+			&wire.MetricDescriptor{
+				Name:        "float",
+				Description: "float metric",
+				Type:        wire.MetricDescriptor_CUMULATIVE_DOUBLE,
+				LabelKeys: []*wire.LabelKey{
+					&wire.LabelKey{
+						Key: "world",
+					},
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := dataToMetricDescriptor(tt.data)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+}
+
+func TestGetDescription(t *testing.T) {
+	tests := []struct {
+		name string
+		data telemetry.MetricData
+		want string
+	}{
+		{
+			"nil data",
+			nil,
+			"",
+		},
+		{
+			"Int64Data description",
+			&metric.Int64Data{
+				Info: &metric.Scalar{
+					Description: "int metric",
+				},
+			},
+			"int metric",
+		},
+		{
+			"Float64Data description",
+			&metric.Float64Data{
+				Info: &metric.Scalar{
+					Description: "float metric",
+				},
+			},
+			"float metric",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getDescription(tt.data)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+
+}
+
+func TestGetLabelKeys(t *testing.T) {
+	tests := []struct {
+		name string
+		data telemetry.MetricData
+		want []*wire.LabelKey
+	}{
+		{
+			"nil label keys",
+			nil,
+			nil,
+		},
+		{
+			"Int64Data label keys",
+			&metric.Int64Data{
+				Info: &metric.Scalar{
+					Keys: []interface{}{
+						"hello",
+					},
+				},
+			},
+			[]*wire.LabelKey{
+				&wire.LabelKey{
+					Key: "hello",
+				},
+			},
+		},
+		{
+			"Float64Data label keys",
+			&metric.Float64Data{
+				Info: &metric.Scalar{
+					Keys: []interface{}{
+						"world",
+					},
+				},
+			},
+			[]*wire.LabelKey{
+				&wire.LabelKey{
+					Key: "world",
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getLabelKeys(tt.data)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+}
+
+func TestDataToMetricDescriptorType(t *testing.T) {
+	tests := []struct {
+		name string
+		data telemetry.MetricData
+		want wire.MetricDescriptor_Type
+	}{
+		{
+			"Nil data",
+			nil,
+			wire.MetricDescriptor_UNSPECIFIED,
+		},
+		{
+			"Gauge Int64",
+			&metric.Int64Data{
+				IsGauge: true,
+			},
+			wire.MetricDescriptor_GAUGE_INT64,
+		},
+		{
+			"Cumulative Int64",
+			&metric.Int64Data{
+				IsGauge: false,
+			},
+			wire.MetricDescriptor_CUMULATIVE_INT64,
+		},
+		{
+			"Gauge Float64",
+			&metric.Float64Data{
+				IsGauge: true,
+			},
+			wire.MetricDescriptor_GAUGE_DOUBLE,
+		},
+		{
+			"Cumulative Float64",
+			&metric.Float64Data{
+				IsGauge: false,
+			},
+			wire.MetricDescriptor_CUMULATIVE_DOUBLE,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := dataToMetricDescriptorType(tt.data)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+}
+
+func TestDataToTimeseries(t *testing.T) {
+	tests := []struct {
+		name string
+		data telemetry.MetricData
+		want []*wire.TimeSeries
+	}{
+		{
+			"nil data",
+			nil,
+			nil,
+		},
+		{
+			"Int64Data",
+			&metric.Int64Data{
+				Rows: []int64{
+					1,
+					2,
+					3,
+				},
+			},
+			[]*wire.TimeSeries{
+				&wire.TimeSeries{
+					Points: []*wire.Point{
+						&wire.Point{
+							Value: wire.PointInt64Value{Int64Value: 1},
+						},
+					},
+				},
+				&wire.TimeSeries{
+					Points: []*wire.Point{
+						&wire.Point{
+							Value: wire.PointInt64Value{Int64Value: 2},
+						},
+					},
+				},
+				&wire.TimeSeries{
+					Points: []*wire.Point{
+						&wire.Point{
+							Value: wire.PointInt64Value{Int64Value: 3},
+						},
+					},
+				},
+			},
+		},
+		{
+			"Float64Data",
+			&metric.Float64Data{
+				Rows: []float64{
+					1.5,
+					4.5,
+				},
+			},
+			[]*wire.TimeSeries{
+				&wire.TimeSeries{
+					Points: []*wire.Point{
+						&wire.Point{
+							Value: wire.PointDoubleValue{DoubleValue: 1.5},
+						},
+					},
+				},
+				&wire.TimeSeries{
+					Points: []*wire.Point{
+						&wire.Point{
+							Value: wire.PointDoubleValue{DoubleValue: 4.5},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := dataToTimeseries(tt.data)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+}
+
+func TestNumRows(t *testing.T) {
+	tests := []struct {
+		name string
+		data telemetry.MetricData
+		want int
+	}{
+		{
+			"nil data",
+			nil,
+			0,
+		},
+		{
+			"1 row Int64Data",
+			&metric.Int64Data{
+				Rows: []int64{
+					0,
+				},
+			},
+			1,
+		},
+		{
+			"2 row Float64Data",
+			&metric.Float64Data{
+				Rows: []float64{
+					0,
+					1.0,
+				},
+			},
+			2,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := numRows(tt.data)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+}
+
+func TestDataToPoints(t *testing.T) {
+	int64Data := &metric.Int64Data{
+		Rows: []int64{
+			0,
+			10,
+		},
+	}
+
+	float64Data := &metric.Float64Data{
+		Rows: []float64{
+			0.5,
+			0.25,
+		},
+	}
+
+	tests := []struct {
+		name string
+		data telemetry.MetricData
+		i    int
+		want []*wire.Point
+	}{
+		{
+			"nil data",
+			nil,
+			0,
+			nil,
+		},
+		{
+			"Int64data index 0",
+			int64Data,
+			0,
+			[]*wire.Point{
+				{
+					Value: wire.PointInt64Value{
+						Int64Value: 0,
+					},
+				},
+			},
+		},
+		{
+			"Int64data index 1",
+			int64Data,
+			1,
+			[]*wire.Point{
+				{
+					Value: wire.PointInt64Value{
+						Int64Value: 10,
+					},
+				},
+			},
+		},
+		{
+			"Float64Data index 0",
+			float64Data,
+			0,
+			[]*wire.Point{
+				{
+					Value: wire.PointDoubleValue{
+						DoubleValue: 0.5,
+					},
+				},
+			},
+		},
+		{
+			"Float64Data index 1",
+			float64Data,
+			1,
+			[]*wire.Point{
+				{
+					Value: wire.PointDoubleValue{
+						DoubleValue: 0.25,
+					},
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := dataToPoints(tt.data, tt.i)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+}
+
+func TestInfoKeysToLabelKeys(t *testing.T) {
+	tests := []struct {
+		name     string
+		infoKeys []interface{}
+		want     []*wire.LabelKey
+	}{
+		{
+			"empty infoKeys",
+			[]interface{}{},
+			[]*wire.LabelKey{},
+		},
+		{
+			"empty string infoKey",
+			[]interface{}{""},
+			[]*wire.LabelKey{
+				&wire.LabelKey{
+					Key: "",
+				},
+			},
+		},
+		{
+			"non-empty string infoKey",
+			[]interface{}{"hello"},
+			[]*wire.LabelKey{
+				&wire.LabelKey{
+					Key: "hello",
+				},
+			},
+		},
+		{
+			"multiple element infoKey",
+			[]interface{}{"hello", "world"},
+			[]*wire.LabelKey{
+				&wire.LabelKey{
+					Key: "hello",
+				},
+				&wire.LabelKey{
+					Key: "world",
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := infoKeysToLabelKeys(tt.infoKeys)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Fatalf("Got:\n%s\nWant:\n%s", marshaled(got), marshaled(tt.want))
+			}
+		})
+	}
+}
diff --git a/internal/telemetry/export/ocagent/ocagent.go b/internal/telemetry/export/ocagent/ocagent.go
index bd2ac70..d1ebfd2 100644
--- a/internal/telemetry/export/ocagent/ocagent.go
+++ b/internal/telemetry/export/ocagent/ocagent.go
@@ -203,7 +203,19 @@
 }
 
 func convertMetric(data telemetry.MetricData) *wire.Metric {
-	return nil //TODO:
+	descriptor := dataToMetricDescriptor(data)
+	timeseries := dataToTimeseries(data)
+
+	if descriptor == nil && timeseries == nil {
+		return nil
+	}
+
+	// TODO: handle Histogram metrics
+	return &wire.Metric{
+		MetricDescriptor: descriptor,
+		Timeseries:       timeseries,
+		// TODO: attach Resource?
+	}
 }
 
 func convertAttributes(tags telemetry.TagList) *wire.Attributes {