diff --git a/internal/telemetry/export/ocagent/metrics.go b/internal/telemetry/export/ocagent/metrics.go index d49151fd70..11c2a83a2d 100644 --- a/internal/telemetry/export/ocagent/metrics.go +++ b/internal/telemetry/export/ocagent/metrics.go @@ -36,6 +36,12 @@ func getDescription(data telemetry.MetricData) string { case *metric.Float64Data: return d.Info.Description + + case *metric.HistogramInt64Data: + return d.Info.Description + + case *metric.HistogramFloat64Data: + return d.Info.Description } return "" @@ -50,6 +56,12 @@ func getLabelKeys(data telemetry.MetricData) []*wire.LabelKey { case *metric.Float64Data: return infoKeysToLabelKeys(d.Info.Keys) + + case *metric.HistogramInt64Data: + return infoKeysToLabelKeys(d.Info.Keys) + + case *metric.HistogramFloat64Data: + return infoKeysToLabelKeys(d.Info.Keys) } return nil @@ -70,6 +82,12 @@ func dataToMetricDescriptorType(data telemetry.MetricData) wire.MetricDescriptor return wire.MetricDescriptor_GAUGE_DOUBLE } return wire.MetricDescriptor_CUMULATIVE_DOUBLE + + case *metric.HistogramInt64Data: + return wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION + + case *metric.HistogramFloat64Data: + return wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION } return wire.MetricDescriptor_UNSPECIFIED @@ -103,6 +121,10 @@ func numRows(data telemetry.MetricData) int { return len(d.Rows) case *metric.Float64Data: return len(d.Rows) + case *metric.HistogramInt64Data: + return len(d.Rows) + case *metric.HistogramFloat64Data: + return len(d.Rows) } return 0 @@ -130,11 +152,48 @@ func dataToPoints(data telemetry.MetricData, i int) []*wire.Point { // TODO: attach Timestamp }, } + case *metric.HistogramInt64Data: + row := d.Rows[i] + bucketBounds := make([]float64, len(d.Info.Buckets)) + for i, val := range d.Info.Buckets { + bucketBounds[i] = float64(val) + } + return distributionToPoints(row.Values, row.Count, float64(row.Sum), bucketBounds) + case *metric.HistogramFloat64Data: + row := d.Rows[i] + return distributionToPoints(row.Values, row.Count, row.Sum, d.Info.Buckets) } return nil } +// distributionToPoints returns an array of *wire.Points containing a +// wire.PointDistributionValue representing a distribution with the +// supplied counts, count, and sum. +func distributionToPoints(counts []int64, count int64, sum float64, bucketBounds []float64) []*wire.Point { + buckets := make([]*wire.Bucket, len(counts)) + for i := 0; i < len(counts); i++ { + buckets[i] = &wire.Bucket{ + Count: counts[i], + } + } + return []*wire.Point{ + { + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: count, + Sum: sum, + // TODO: SumOfSquaredDeviation? + Buckets: buckets, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: bucketBounds, + }, + }, + }, + }, + } +} + // infoKeysToLabelKeys returns an array of *wire.LabelKeys containing the // string values of the elements of labelKeys. func infoKeysToLabelKeys(infoKeys []interface{}) []*wire.LabelKey { diff --git a/internal/telemetry/export/ocagent/metrics_test.go b/internal/telemetry/export/ocagent/metrics_test.go index 6ec986f381..85e90dd512 100644 --- a/internal/telemetry/export/ocagent/metrics_test.go +++ b/internal/telemetry/export/ocagent/metrics_test.go @@ -62,6 +62,46 @@ func TestDataToMetricDescriptor(t *testing.T) { }, }, }, + { + "HistogramInt64", + &metric.HistogramInt64Data{ + Info: &metric.HistogramInt64{ + Name: "histogram int", + Description: "histogram int metric", + Keys: []interface{}{"hello"}, + }, + }, + &wire.MetricDescriptor{ + Name: "histogram int", + Description: "histogram int metric", + Type: wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*wire.LabelKey{ + &wire.LabelKey{ + Key: "hello", + }, + }, + }, + }, + { + "HistogramFloat64", + &metric.HistogramFloat64Data{ + Info: &metric.HistogramFloat64{ + Name: "histogram float", + Description: "histogram float metric", + Keys: []interface{}{"hello"}, + }, + }, + &wire.MetricDescriptor{ + Name: "histogram float", + Description: "histogram float metric", + Type: wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + LabelKeys: []*wire.LabelKey{ + &wire.LabelKey{ + Key: "hello", + }, + }, + }, + }, } for _, tt := range tests { @@ -103,6 +143,24 @@ func TestGetDescription(t *testing.T) { }, "float metric", }, + { + "HistogramInt64Data description", + &metric.HistogramInt64Data{ + Info: &metric.HistogramInt64{ + Description: "histogram int metric", + }, + }, + "histogram int metric", + }, + { + "HistogramFloat64Data description", + &metric.HistogramFloat64Data{ + Info: &metric.HistogramFloat64{ + Description: "histogram float metric", + }, + }, + "histogram float metric", + }, } for _, tt := range tests { @@ -157,6 +215,36 @@ func TestGetLabelKeys(t *testing.T) { }, }, }, + { + "HistogramInt64Data label keys", + &metric.HistogramInt64Data{ + Info: &metric.HistogramInt64{ + Keys: []interface{}{ + "hello", + }, + }, + }, + []*wire.LabelKey{ + &wire.LabelKey{ + Key: "hello", + }, + }, + }, + { + "HistogramFloat64Data label keys", + &metric.HistogramFloat64Data{ + Info: &metric.HistogramFloat64{ + Keys: []interface{}{ + "hello", + }, + }, + }, + []*wire.LabelKey{ + &wire.LabelKey{ + Key: "hello", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -207,6 +295,16 @@ func TestDataToMetricDescriptorType(t *testing.T) { }, wire.MetricDescriptor_CUMULATIVE_DOUBLE, }, + { + "HistogramInt64", + &metric.HistogramInt64Data{}, + wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + }, + { + "HistogramFloat64", + &metric.HistogramFloat64Data{}, + wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION, + }, } for _, tt := range tests { @@ -288,6 +386,104 @@ func TestDataToTimeseries(t *testing.T) { }, }, }, + { + "HistogramInt64Data", + &metric.HistogramInt64Data{ + Rows: []*metric.HistogramInt64Row{ + { + Count: 6, + Sum: 40, + Values: []int64{ + 1, + 2, + 3, + }, + }, + }, + Info: &metric.HistogramInt64{ + Buckets: []int64{ + 0, 5, 10, + }, + }, + }, + []*wire.TimeSeries{ + &wire.TimeSeries{ + Points: []*wire.Point{ + &wire.Point{ + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: 6, + Sum: 40, + Buckets: []*wire.Bucket{ + { + Count: 1, + }, + { + Count: 2, + }, + { + Count: 3, + }, + }, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, 10, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "HistogramFloat64Data", + &metric.HistogramFloat64Data{ + Rows: []*metric.HistogramFloat64Row{ + { + Count: 3, + Sum: 10, + Values: []int64{ + 1, + 2, + }, + }, + }, + Info: &metric.HistogramFloat64{ + Buckets: []float64{ + 0, 5, + }, + }, + }, + []*wire.TimeSeries{ + &wire.TimeSeries{ + Points: []*wire.Point{ + &wire.Point{ + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: 3, + Sum: 10, + Buckets: []*wire.Bucket{ + { + Count: 1, + }, + { + Count: 2, + }, + }, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, + }, + }, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { @@ -330,6 +526,26 @@ func TestNumRows(t *testing.T) { }, 2, }, + { + "1 row HistogramInt64Data", + &metric.HistogramInt64Data{ + Rows: []*metric.HistogramInt64Row{ + {}, + }, + }, + 1, + }, + { + "3 row HistogramFloat64Data", + &metric.HistogramFloat64Data{ + Rows: []*metric.HistogramFloat64Row{ + {}, + {}, + {}, + }, + }, + 3, + }, } for _, tt := range tests { @@ -357,6 +573,62 @@ func TestDataToPoints(t *testing.T) { }, } + histogramInt64Data := &metric.HistogramInt64Data{ + Rows: []*metric.HistogramInt64Row{ + { + Count: 6, + Sum: 40, + Values: []int64{ + 1, + 2, + 3, + }, + }, + { + Count: 12, + Sum: 80, + Values: []int64{ + 2, + 4, + 6, + }, + }, + }, + Info: &metric.HistogramInt64{ + Buckets: []int64{ + 0, 5, 10, + }, + }, + } + + histogramFloat64Data := &metric.HistogramFloat64Data{ + Rows: []*metric.HistogramFloat64Row{ + { + Count: 6, + Sum: 40, + Values: []int64{ + 1, + 2, + 3, + }, + }, + { + Count: 18, + Sum: 80, + Values: []int64{ + 3, + 6, + 9, + }, + }, + }, + Info: &metric.HistogramFloat64{ + Buckets: []float64{ + 0, 5, 10, + }, + }, + } + tests := []struct { name string data telemetry.MetricData @@ -417,6 +689,130 @@ func TestDataToPoints(t *testing.T) { }, }, }, + { + "HistogramInt64Data index 0", + histogramInt64Data, + 0, + []*wire.Point{ + { + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: 6, + Sum: 40, + Buckets: []*wire.Bucket{ + { + Count: 1, + }, + { + Count: 2, + }, + { + Count: 3, + }, + }, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, 10, + }, + }, + }, + }, + }, + }, + }, + { + "HistogramInt64Data index 1", + histogramInt64Data, + 1, + []*wire.Point{ + { + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: 12, + Sum: 80, + Buckets: []*wire.Bucket{ + { + Count: 2, + }, + { + Count: 4, + }, + { + Count: 6, + }, + }, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, 10, + }, + }, + }, + }, + }, + }, + }, + { + "HistogramFloat64Data index 0", + histogramFloat64Data, + 0, + []*wire.Point{ + { + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: 6, + Sum: 40, + Buckets: []*wire.Bucket{ + { + Count: 1, + }, + { + Count: 2, + }, + { + Count: 3, + }, + }, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, 10, + }, + }, + }, + }, + }, + }, + }, + { + "HistogramFloat64Data index 1", + histogramFloat64Data, + 1, + []*wire.Point{ + { + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: 18, + Sum: 80, + Buckets: []*wire.Bucket{ + { + Count: 3, + }, + { + Count: 6, + }, + { + Count: 9, + }, + }, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, 10, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { @@ -429,6 +825,68 @@ func TestDataToPoints(t *testing.T) { } } +func TestDistributionToPoints(t *testing.T) { + tests := []struct { + name string + counts []int64 + count int64 + sum float64 + buckets []float64 + want []*wire.Point + }{ + { + name: "3 buckets", + counts: []int64{ + 1, + 2, + 3, + }, + count: 6, + sum: 40, + buckets: []float64{ + 0, 5, 10, + }, + want: []*wire.Point{ + { + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: 6, + Sum: 40, + // TODO: SumOfSquaredDeviation? + Buckets: []*wire.Bucket{ + &wire.Bucket{ + Count: 1, + }, + &wire.Bucket{ + Count: 2, + }, + &wire.Bucket{ + Count: 3, + }, + }, + BucketOptions: wire.BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, 10, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := distributionToPoints(tt.counts, tt.count, tt.sum, tt.buckets) + 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