Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / contrib.go.opencensus.io / exporter / ocagent / transform_stats_to_metrics.go
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/stats"
22         "go.opencensus.io/stats/view"
23         "go.opencensus.io/tag"
24
25         "github.com/golang/protobuf/ptypes/timestamp"
26
27         metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
28 )
29
30 var (
31         errNilMeasure  = errors.New("expecting a non-nil stats.Measure")
32         errNilView     = errors.New("expecting a non-nil view.View")
33         errNilViewData = errors.New("expecting a non-nil view.Data")
34 )
35
36 func viewDataToMetric(vd *view.Data) (*metricspb.Metric, error) {
37         if vd == nil {
38                 return nil, errNilViewData
39         }
40
41         descriptor, err := viewToMetricDescriptor(vd.View)
42         if err != nil {
43                 return nil, err
44         }
45
46         timeseries, err := viewDataToTimeseries(vd)
47         if err != nil {
48                 return nil, err
49         }
50
51         metric := &metricspb.Metric{
52                 MetricDescriptor: descriptor,
53                 Timeseries:       timeseries,
54         }
55         return metric, nil
56 }
57
58 func viewToMetricDescriptor(v *view.View) (*metricspb.MetricDescriptor, error) {
59         if v == nil {
60                 return nil, errNilView
61         }
62         if v.Measure == nil {
63                 return nil, errNilMeasure
64         }
65
66         desc := &metricspb.MetricDescriptor{
67                 Name:        stringOrCall(v.Name, v.Measure.Name),
68                 Description: stringOrCall(v.Description, v.Measure.Description),
69                 Unit:        v.Measure.Unit(),
70                 Type:        aggregationToMetricDescriptorType(v),
71                 LabelKeys:   tagKeysToLabelKeys(v.TagKeys),
72         }
73         return desc, nil
74 }
75
76 func stringOrCall(first string, call func() string) string {
77         if first != "" {
78                 return first
79         }
80         return call()
81 }
82
83 type measureType uint
84
85 const (
86         measureUnknown measureType = iota
87         measureInt64
88         measureFloat64
89 )
90
91 func measureTypeFromMeasure(m stats.Measure) measureType {
92         switch m.(type) {
93         default:
94                 return measureUnknown
95         case *stats.Float64Measure:
96                 return measureFloat64
97         case *stats.Int64Measure:
98                 return measureInt64
99         }
100 }
101
102 func aggregationToMetricDescriptorType(v *view.View) metricspb.MetricDescriptor_Type {
103         if v == nil || v.Aggregation == nil {
104                 return metricspb.MetricDescriptor_UNSPECIFIED
105         }
106         if v.Measure == nil {
107                 return metricspb.MetricDescriptor_UNSPECIFIED
108         }
109
110         switch v.Aggregation.Type {
111         case view.AggTypeCount:
112                 // Cumulative on int64
113                 return metricspb.MetricDescriptor_CUMULATIVE_INT64
114
115         case view.AggTypeDistribution:
116                 // Cumulative types
117                 return metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION
118
119         case view.AggTypeLastValue:
120                 // Gauge types
121                 switch measureTypeFromMeasure(v.Measure) {
122                 case measureFloat64:
123                         return metricspb.MetricDescriptor_GAUGE_DOUBLE
124                 case measureInt64:
125                         return metricspb.MetricDescriptor_GAUGE_INT64
126                 }
127
128         case view.AggTypeSum:
129                 // Cumulative types
130                 switch measureTypeFromMeasure(v.Measure) {
131                 case measureFloat64:
132                         return metricspb.MetricDescriptor_CUMULATIVE_DOUBLE
133                 case measureInt64:
134                         return metricspb.MetricDescriptor_CUMULATIVE_INT64
135                 }
136         }
137
138         // For all other cases, return unspecified.
139         return metricspb.MetricDescriptor_UNSPECIFIED
140 }
141
142 func tagKeysToLabelKeys(tagKeys []tag.Key) []*metricspb.LabelKey {
143         labelKeys := make([]*metricspb.LabelKey, 0, len(tagKeys))
144         for _, tagKey := range tagKeys {
145                 labelKeys = append(labelKeys, &metricspb.LabelKey{
146                         Key: tagKey.Name(),
147                 })
148         }
149         return labelKeys
150 }
151
152 func viewDataToTimeseries(vd *view.Data) ([]*metricspb.TimeSeries, error) {
153         if vd == nil || len(vd.Rows) == 0 {
154                 return nil, nil
155         }
156
157         // Given that view.Data only contains Start, End
158         // the timestamps for all the row data will be the exact same
159         // per aggregation. However, the values will differ.
160         // Each row has its own tags.
161         startTimestamp := timeToProtoTimestamp(vd.Start)
162         endTimestamp := timeToProtoTimestamp(vd.End)
163
164         mType := measureTypeFromMeasure(vd.View.Measure)
165         timeseries := make([]*metricspb.TimeSeries, 0, len(vd.Rows))
166         // It is imperative that the ordering of "LabelValues" matches those
167         // of the Label keys in the metric descriptor.
168         for _, row := range vd.Rows {
169                 labelValues := labelValuesFromTags(row.Tags)
170                 point := rowToPoint(vd.View, row, endTimestamp, mType)
171                 timeseries = append(timeseries, &metricspb.TimeSeries{
172                         StartTimestamp: startTimestamp,
173                         LabelValues:    labelValues,
174                         Points:         []*metricspb.Point{point},
175                 })
176         }
177
178         if len(timeseries) == 0 {
179                 return nil, nil
180         }
181
182         return timeseries, nil
183 }
184
185 func timeToProtoTimestamp(t time.Time) *timestamp.Timestamp {
186         unixNano := t.UnixNano()
187         return &timestamp.Timestamp{
188                 Seconds: int64(unixNano / 1e9),
189                 Nanos:   int32(unixNano % 1e9),
190         }
191 }
192
193 func rowToPoint(v *view.View, row *view.Row, endTimestamp *timestamp.Timestamp, mType measureType) *metricspb.Point {
194         pt := &metricspb.Point{
195                 Timestamp: endTimestamp,
196         }
197
198         switch data := row.Data.(type) {
199         case *view.CountData:
200                 pt.Value = &metricspb.Point_Int64Value{Int64Value: data.Value}
201
202         case *view.DistributionData:
203                 pt.Value = &metricspb.Point_DistributionValue{
204                         DistributionValue: &metricspb.DistributionValue{
205                                 Count: data.Count,
206                                 Sum:   float64(data.Count) * data.Mean, // because Mean := Sum/Count
207                                 // TODO: Add Exemplar
208                                 Buckets: bucketsToProtoBuckets(data.CountPerBucket),
209                                 BucketOptions: &metricspb.DistributionValue_BucketOptions{
210                                         Type: &metricspb.DistributionValue_BucketOptions_Explicit_{
211                                                 Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{
212                                                         Bounds: v.Aggregation.Buckets,
213                                                 },
214                                         },
215                                 },
216                                 SumOfSquaredDeviation: data.SumOfSquaredDev,
217                         }}
218
219         case *view.LastValueData:
220                 setPointValue(pt, data.Value, mType)
221
222         case *view.SumData:
223                 setPointValue(pt, data.Value, mType)
224         }
225
226         return pt
227 }
228
229 // Not returning anything from this function because metricspb.Point.is_Value is an unexported
230 // interface hence we just have to set its value by pointer.
231 func setPointValue(pt *metricspb.Point, value float64, mType measureType) {
232         if mType == measureInt64 {
233                 pt.Value = &metricspb.Point_Int64Value{Int64Value: int64(value)}
234         } else {
235                 pt.Value = &metricspb.Point_DoubleValue{DoubleValue: value}
236         }
237 }
238
239 func bucketsToProtoBuckets(countPerBucket []int64) []*metricspb.DistributionValue_Bucket {
240         distBuckets := make([]*metricspb.DistributionValue_Bucket, len(countPerBucket))
241         for i := 0; i < len(countPerBucket); i++ {
242                 count := countPerBucket[i]
243
244                 distBuckets[i] = &metricspb.DistributionValue_Bucket{
245                         Count: count,
246                 }
247         }
248
249         return distBuckets
250 }
251
252 func labelValuesFromTags(tags []tag.Tag) []*metricspb.LabelValue {
253         if len(tags) == 0 {
254                 return nil
255         }
256
257         labelValues := make([]*metricspb.LabelValue, 0, len(tags))
258         for _, tag_ := range tags {
259                 labelValues = append(labelValues, &metricspb.LabelValue{
260                         Value: tag_.Value,
261
262                         // It is imperative that we set the "HasValue" attribute,
263                         // in order to distinguish missing a label from the empty string.
264                         // https://godoc.org/github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1#LabelValue.HasValue
265                         //
266                         // OpenCensus-Go uses non-pointers for tags as seen by this function's arguments,
267                         // so the best case that we can use to distinguish missing labels/tags from the
268                         // empty string is by checking if the Tag.Key.Name() != "" to indicate that we have
269                         // a value.
270                         HasValue: tag_.Key.Name() != "",
271                 })
272         }
273         return labelValues
274 }