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 {