1 // Copyright 2018, OpenCensus Authors
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
21 "go.opencensus.io/stats"
22 "go.opencensus.io/stats/view"
23 "go.opencensus.io/tag"
25 "github.com/golang/protobuf/ptypes/timestamp"
27 metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
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")
36 func viewDataToMetric(vd *view.Data) (*metricspb.Metric, error) {
38 return nil, errNilViewData
41 descriptor, err := viewToMetricDescriptor(vd.View)
46 timeseries, err := viewDataToTimeseries(vd)
51 metric := &metricspb.Metric{
52 MetricDescriptor: descriptor,
53 Timeseries: timeseries,
58 func viewToMetricDescriptor(v *view.View) (*metricspb.MetricDescriptor, error) {
60 return nil, errNilView
63 return nil, errNilMeasure
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),
76 func stringOrCall(first string, call func() string) string {
86 measureUnknown measureType = iota
91 func measureTypeFromMeasure(m stats.Measure) measureType {
95 case *stats.Float64Measure:
97 case *stats.Int64Measure:
102 func aggregationToMetricDescriptorType(v *view.View) metricspb.MetricDescriptor_Type {
103 if v == nil || v.Aggregation == nil {
104 return metricspb.MetricDescriptor_UNSPECIFIED
106 if v.Measure == nil {
107 return metricspb.MetricDescriptor_UNSPECIFIED
110 switch v.Aggregation.Type {
111 case view.AggTypeCount:
112 // Cumulative on int64
113 return metricspb.MetricDescriptor_CUMULATIVE_INT64
115 case view.AggTypeDistribution:
117 return metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION
119 case view.AggTypeLastValue:
121 switch measureTypeFromMeasure(v.Measure) {
123 return metricspb.MetricDescriptor_GAUGE_DOUBLE
125 return metricspb.MetricDescriptor_GAUGE_INT64
128 case view.AggTypeSum:
130 switch measureTypeFromMeasure(v.Measure) {
132 return metricspb.MetricDescriptor_CUMULATIVE_DOUBLE
134 return metricspb.MetricDescriptor_CUMULATIVE_INT64
138 // For all other cases, return unspecified.
139 return metricspb.MetricDescriptor_UNSPECIFIED
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{
152 func viewDataToTimeseries(vd *view.Data) ([]*metricspb.TimeSeries, error) {
153 if vd == nil || len(vd.Rows) == 0 {
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)
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},
178 if len(timeseries) == 0 {
182 return timeseries, nil
185 func timeToProtoTimestamp(t time.Time) *timestamp.Timestamp {
186 unixNano := t.UnixNano()
187 return ×tamp.Timestamp{
188 Seconds: int64(unixNano / 1e9),
189 Nanos: int32(unixNano % 1e9),
193 func rowToPoint(v *view.View, row *view.Row, endTimestamp *timestamp.Timestamp, mType measureType) *metricspb.Point {
194 pt := &metricspb.Point{
195 Timestamp: endTimestamp,
198 switch data := row.Data.(type) {
199 case *view.CountData:
200 pt.Value = &metricspb.Point_Int64Value{Int64Value: data.Value}
202 case *view.DistributionData:
203 pt.Value = &metricspb.Point_DistributionValue{
204 DistributionValue: &metricspb.DistributionValue{
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,
216 SumOfSquaredDeviation: data.SumOfSquaredDev,
219 case *view.LastValueData:
220 setPointValue(pt, data.Value, mType)
223 setPointValue(pt, data.Value, mType)
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)}
235 pt.Value = &metricspb.Point_DoubleValue{DoubleValue: value}
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]
244 distBuckets[i] = &metricspb.DistributionValue_Bucket{
252 func labelValuesFromTags(tags []tag.Tag) []*metricspb.LabelValue {
257 labelValues := make([]*metricspb.LabelValue, 0, len(tags))
258 for _, tag_ := range tags {
259 labelValues = append(labelValues, &metricspb.LabelValue{
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
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
270 HasValue: tag_.Key.Name() != "",