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 15package ocagent 16 17import ( 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 30var ( 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 36func 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 58func 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 76func stringOrCall(first string, call func() string) string { 77 if first != "" { 78 return first 79 } 80 return call() 81} 82 83type measureType uint 84 85const ( 86 measureUnknown measureType = iota 87 measureInt64 88 measureFloat64 89) 90 91func 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 102func 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 142func 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 152func 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 185func timeToProtoTimestamp(t time.Time) *timestamp.Timestamp { 186 unixNano := t.UnixNano() 187 return ×tamp.Timestamp{ 188 Seconds: int64(unixNano / 1e9), 189 Nanos: int32(unixNano % 1e9), 190 } 191} 192 193func 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. 231func 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 239func 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 252func 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} 275