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

Commit 5978da2

Browse files
committed
metrics: OpenCensus-Go stats+view to Metrics converter
This is the first step of piecemeal updates to allow OpenCensus-Go to add metrics exporting before https://github.com/census-instrumentation/opencensus-go#956 is resolved, as that issue is going to take a long time and a lot of work. After that, a follow-up change will be to then implement the metrics service that will enable this exporter to be an OpenCensus-Go stats+view exporter, then transform that data into metricspb.Metric Updates #31
1 parent 48c455c commit 5978da2

File tree

2 files changed

+507
-0
lines changed

2 files changed

+507
-0
lines changed

transform_stats_to_metrics.go

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Copyright 2018, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ocagent
16+
17+
import (
18+
"errors"
19+
"time"
20+
21+
"go.opencensus.io/exemplar"
22+
"go.opencensus.io/stats"
23+
"go.opencensus.io/stats/view"
24+
"go.opencensus.io/tag"
25+
26+
"github.com/golang/protobuf/ptypes/timestamp"
27+
28+
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
29+
)
30+
31+
var (
32+
errNilMeasure = errors.New("expecting a non-nil stats.Measure")
33+
errNilView = errors.New("expecting a non-nil view.View")
34+
errNilViewData = errors.New("expecting a non-nil view.Data")
35+
)
36+
37+
func viewDataToMetrics(vd *view.Data) (*metricspb.Metric, error) {
38+
if vd == nil {
39+
return nil, errNilViewData
40+
}
41+
42+
descriptor, err := viewToMetricDescriptor(vd.View)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
timeseries, err := viewDataToTimeseries(vd)
48+
if err != nil {
49+
return nil, err
50+
}
51+
metric := &metricspb.Metric{
52+
Descriptor_: descriptor,
53+
Timeseries: timeseries,
54+
// TODO: (@odeke-em) figure out how to derive
55+
// the Resource from the view or from environment?
56+
// Resource: derivedResource,
57+
}
58+
return metric, nil
59+
}
60+
61+
func viewToMetricDescriptor(v *view.View) (*metricspb.Metric_MetricDescriptor, error) {
62+
if v == nil {
63+
return nil, errNilView
64+
}
65+
if v.Measure == nil {
66+
return nil, errNilMeasure
67+
}
68+
69+
desc := &metricspb.Metric_MetricDescriptor{
70+
MetricDescriptor: &metricspb.MetricDescriptor{
71+
Name: stringOrCall(v.Name, v.Measure.Name),
72+
Description: stringOrCall(v.Description, v.Measure.Description),
73+
Unit: v.Measure.Unit(),
74+
Type: aggregationToMetricDescriptorType(v),
75+
LabelKeys: tagKeysToLabelKeys(v.TagKeys),
76+
},
77+
}
78+
return desc, nil
79+
}
80+
81+
func stringOrCall(first string, call func() string) string {
82+
if first != "" {
83+
return first
84+
}
85+
return call()
86+
}
87+
88+
func nameToMetricName(name string) *metricspb.Metric_Name {
89+
if name == "" {
90+
return nil
91+
}
92+
return &metricspb.Metric_Name{Name: name}
93+
}
94+
95+
type measureType uint
96+
97+
const (
98+
measureUnknown measureType = iota
99+
measureInt64
100+
measureFloat64
101+
)
102+
103+
func measureTypeFromMeasure(m stats.Measure) measureType {
104+
switch m.(type) {
105+
default:
106+
return measureUnknown
107+
case *stats.Float64Measure:
108+
return measureFloat64
109+
case *stats.Int64Measure:
110+
return measureInt64
111+
}
112+
}
113+
114+
func aggregationToMetricDescriptorType(v *view.View) metricspb.MetricDescriptor_Type {
115+
if v == nil || v.Aggregation == nil {
116+
return metricspb.MetricDescriptor_UNSPECIFIED
117+
}
118+
if v.Measure == nil {
119+
return metricspb.MetricDescriptor_UNSPECIFIED
120+
}
121+
122+
switch v.Aggregation.Type {
123+
default:
124+
return metricspb.MetricDescriptor_UNSPECIFIED
125+
126+
case view.AggTypeCount:
127+
// Cumulative on int64
128+
return metricspb.MetricDescriptor_CUMULATIVE_INT64
129+
130+
case view.AggTypeDistribution:
131+
// Cumulative types
132+
return metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION
133+
134+
case view.AggTypeLastValue:
135+
// Gauge types
136+
switch measureTypeFromMeasure(v.Measure) {
137+
case measureFloat64:
138+
return metricspb.MetricDescriptor_GAUGE_DOUBLE
139+
case measureInt64:
140+
return metricspb.MetricDescriptor_GAUGE_INT64
141+
}
142+
143+
case view.AggTypeSum:
144+
// Cumulative types
145+
switch measureTypeFromMeasure(v.Measure) {
146+
case measureFloat64:
147+
return metricspb.MetricDescriptor_CUMULATIVE_DOUBLE
148+
case measureInt64:
149+
return metricspb.MetricDescriptor_CUMULATIVE_INT64
150+
}
151+
}
152+
153+
// For all other cases, return unspecified.
154+
return metricspb.MetricDescriptor_UNSPECIFIED
155+
}
156+
157+
func tagKeysToLabelKeys(tagKeys []tag.Key) []*metricspb.LabelKey {
158+
labelKeys := make([]*metricspb.LabelKey, 0, len(tagKeys))
159+
for _, tagKey := range tagKeys {
160+
labelKeys = append(labelKeys, &metricspb.LabelKey{
161+
Key: tagKey.Name(),
162+
})
163+
}
164+
return labelKeys
165+
}
166+
167+
func viewDataToTimeseries(vd *view.Data) ([]*metricspb.TimeSeries, error) {
168+
if vd == nil || len(vd.Rows) == 0 {
169+
return nil, nil
170+
}
171+
172+
// Given that view.Data only contains Start, End
173+
// the timestamps for all the row data will be the exact same
174+
// per aggregation. However, the values will differ.
175+
// Each row has its own tags.
176+
startTimestamp := timeToProtoTimestamp(vd.Start)
177+
endTimestamp := timeToProtoTimestamp(vd.End)
178+
179+
timeseries := make([]*metricspb.TimeSeries, 0, len(vd.Rows))
180+
// It is imperative that the ordering of "LabelValues" matches those
181+
// of the Label keys in the metric descriptor.
182+
for _, row := range vd.Rows {
183+
labelValues := labelValuesFromTags(row.Tags)
184+
point := rowToPoint(row, endTimestamp)
185+
timeseries = append(timeseries, &metricspb.TimeSeries{
186+
StartTimestamp: startTimestamp,
187+
LabelValues: labelValues,
188+
Points: []*metricspb.Point{point},
189+
})
190+
}
191+
192+
if len(timeseries) == 0 {
193+
return nil, nil
194+
}
195+
196+
return timeseries, nil
197+
}
198+
199+
func timeToProtoTimestamp(t time.Time) *timestamp.Timestamp {
200+
unixNano := t.UnixNano()
201+
return &timestamp.Timestamp{
202+
Seconds: int64(unixNano / 1e9),
203+
Nanos: int32(unixNano % 1e9),
204+
}
205+
}
206+
207+
func rowToPoint(row *view.Row, endTimestamp *timestamp.Timestamp) *metricspb.Point {
208+
pt := &metricspb.Point{
209+
Timestamp: endTimestamp,
210+
}
211+
212+
switch data := row.Data.(type) {
213+
case *view.CountData:
214+
pt.Value = &metricspb.Point_Int64Value{Int64Value: data.Value}
215+
216+
case *view.DistributionData:
217+
pt.Value = &metricspb.Point_DistributionValue{
218+
DistributionValue: &metricspb.DistributionValue{
219+
Count: data.Count,
220+
Sum: float64(data.Count) * data.Mean, // because Mean := Sum/Count
221+
Buckets: exemplarsToDistributionBuckets(data.ExemplarsPerBucket),
222+
223+
SumOfSquaredDeviation: data.SumOfSquaredDev,
224+
}}
225+
226+
case *view.LastValueData:
227+
pt.Value = &metricspb.Point_DoubleValue{DoubleValue: data.Value}
228+
229+
case *view.SumData:
230+
pt.Value = &metricspb.Point_DoubleValue{DoubleValue: data.Value}
231+
}
232+
233+
return pt
234+
}
235+
236+
func exemplarsToDistributionBuckets(exemplars []*exemplar.Exemplar) []*metricspb.DistributionValue_Bucket {
237+
if len(exemplars) == 0 {
238+
return nil
239+
}
240+
241+
distBuckets := make([]*metricspb.DistributionValue_Bucket, 0, len(exemplars))
242+
for _, exmplr := range exemplars {
243+
if exmplr == nil {
244+
continue
245+
}
246+
247+
distBuckets = append(distBuckets, &metricspb.DistributionValue_Bucket{
248+
Count: 1, // TODO: (@odeke-em) examine if OpenCensus-Go stores the count of values in the bucket
249+
Exemplar: &metricspb.DistributionValue_Exemplar{
250+
Value: exmplr.Value,
251+
Timestamp: timeToTimestamp(exmplr.Timestamp),
252+
Attachments: exmplr.Attachments,
253+
},
254+
})
255+
}
256+
257+
return distBuckets
258+
}
259+
260+
func labelValuesFromTags(tags []tag.Tag) []*metricspb.LabelValue {
261+
if len(tags) == 0 {
262+
return nil
263+
}
264+
265+
labelValues := make([]*metricspb.LabelValue, 0, len(tags))
266+
for _, tag_ := range tags {
267+
labelValues = append(labelValues, &metricspb.LabelValue{
268+
Value: tag_.Value,
269+
// It is imperative that we set the "HasValue" attribute,
270+
// in order to distinguish missing a label from the empty string.
271+
// https://godoc.org/github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1#LabelValue.HasValue
272+
HasValue: true,
273+
})
274+
}
275+
return labelValues
276+
}

0 commit comments

Comments
 (0)