mirror of https://github.com/restic/restic.git
295 lines
8.2 KiB
Go
295 lines
8.2 KiB
Go
// Copyright 2018, OpenCensus Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ocagent
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"go.opencensus.io/exemplar"
|
|
"go.opencensus.io/stats"
|
|
"go.opencensus.io/stats/view"
|
|
"go.opencensus.io/tag"
|
|
|
|
"github.com/golang/protobuf/ptypes/timestamp"
|
|
|
|
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
|
|
)
|
|
|
|
var (
|
|
errNilMeasure = errors.New("expecting a non-nil stats.Measure")
|
|
errNilView = errors.New("expecting a non-nil view.View")
|
|
errNilViewData = errors.New("expecting a non-nil view.Data")
|
|
)
|
|
|
|
func viewDataToMetric(vd *view.Data) (*metricspb.Metric, error) {
|
|
if vd == nil {
|
|
return nil, errNilViewData
|
|
}
|
|
|
|
descriptor, err := viewToMetricDescriptor(vd.View)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
timeseries, err := viewDataToTimeseries(vd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
metric := &metricspb.Metric{
|
|
Descriptor_: descriptor,
|
|
Timeseries: timeseries,
|
|
}
|
|
return metric, nil
|
|
}
|
|
|
|
func viewToMetricDescriptor(v *view.View) (*metricspb.Metric_MetricDescriptor, error) {
|
|
if v == nil {
|
|
return nil, errNilView
|
|
}
|
|
if v.Measure == nil {
|
|
return nil, errNilMeasure
|
|
}
|
|
|
|
desc := &metricspb.Metric_MetricDescriptor{
|
|
MetricDescriptor: &metricspb.MetricDescriptor{
|
|
Name: stringOrCall(v.Name, v.Measure.Name),
|
|
Description: stringOrCall(v.Description, v.Measure.Description),
|
|
Unit: v.Measure.Unit(),
|
|
Type: aggregationToMetricDescriptorType(v),
|
|
LabelKeys: tagKeysToLabelKeys(v.TagKeys),
|
|
},
|
|
}
|
|
return desc, nil
|
|
}
|
|
|
|
func stringOrCall(first string, call func() string) string {
|
|
if first != "" {
|
|
return first
|
|
}
|
|
return call()
|
|
}
|
|
|
|
type measureType uint
|
|
|
|
const (
|
|
measureUnknown measureType = iota
|
|
measureInt64
|
|
measureFloat64
|
|
)
|
|
|
|
func measureTypeFromMeasure(m stats.Measure) measureType {
|
|
switch m.(type) {
|
|
default:
|
|
return measureUnknown
|
|
case *stats.Float64Measure:
|
|
return measureFloat64
|
|
case *stats.Int64Measure:
|
|
return measureInt64
|
|
}
|
|
}
|
|
|
|
func aggregationToMetricDescriptorType(v *view.View) metricspb.MetricDescriptor_Type {
|
|
if v == nil || v.Aggregation == nil {
|
|
return metricspb.MetricDescriptor_UNSPECIFIED
|
|
}
|
|
if v.Measure == nil {
|
|
return metricspb.MetricDescriptor_UNSPECIFIED
|
|
}
|
|
|
|
switch v.Aggregation.Type {
|
|
case view.AggTypeCount:
|
|
// Cumulative on int64
|
|
return metricspb.MetricDescriptor_CUMULATIVE_INT64
|
|
|
|
case view.AggTypeDistribution:
|
|
// Cumulative types
|
|
return metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION
|
|
|
|
case view.AggTypeLastValue:
|
|
// Gauge types
|
|
switch measureTypeFromMeasure(v.Measure) {
|
|
case measureFloat64:
|
|
return metricspb.MetricDescriptor_GAUGE_DOUBLE
|
|
case measureInt64:
|
|
return metricspb.MetricDescriptor_GAUGE_INT64
|
|
}
|
|
|
|
case view.AggTypeSum:
|
|
// Cumulative types
|
|
switch measureTypeFromMeasure(v.Measure) {
|
|
case measureFloat64:
|
|
return metricspb.MetricDescriptor_CUMULATIVE_DOUBLE
|
|
case measureInt64:
|
|
return metricspb.MetricDescriptor_CUMULATIVE_INT64
|
|
}
|
|
}
|
|
|
|
// For all other cases, return unspecified.
|
|
return metricspb.MetricDescriptor_UNSPECIFIED
|
|
}
|
|
|
|
func tagKeysToLabelKeys(tagKeys []tag.Key) []*metricspb.LabelKey {
|
|
labelKeys := make([]*metricspb.LabelKey, 0, len(tagKeys))
|
|
for _, tagKey := range tagKeys {
|
|
labelKeys = append(labelKeys, &metricspb.LabelKey{
|
|
Key: tagKey.Name(),
|
|
})
|
|
}
|
|
return labelKeys
|
|
}
|
|
|
|
func viewDataToTimeseries(vd *view.Data) ([]*metricspb.TimeSeries, error) {
|
|
if vd == nil || len(vd.Rows) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Given that view.Data only contains Start, End
|
|
// the timestamps for all the row data will be the exact same
|
|
// per aggregation. However, the values will differ.
|
|
// Each row has its own tags.
|
|
startTimestamp := timeToProtoTimestamp(vd.Start)
|
|
endTimestamp := timeToProtoTimestamp(vd.End)
|
|
|
|
mType := measureTypeFromMeasure(vd.View.Measure)
|
|
timeseries := make([]*metricspb.TimeSeries, 0, len(vd.Rows))
|
|
// It is imperative that the ordering of "LabelValues" matches those
|
|
// of the Label keys in the metric descriptor.
|
|
for _, row := range vd.Rows {
|
|
labelValues := labelValuesFromTags(row.Tags)
|
|
point := rowToPoint(vd.View, row, endTimestamp, mType)
|
|
timeseries = append(timeseries, &metricspb.TimeSeries{
|
|
StartTimestamp: startTimestamp,
|
|
LabelValues: labelValues,
|
|
Points: []*metricspb.Point{point},
|
|
})
|
|
}
|
|
|
|
if len(timeseries) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return timeseries, nil
|
|
}
|
|
|
|
func timeToProtoTimestamp(t time.Time) *timestamp.Timestamp {
|
|
unixNano := t.UnixNano()
|
|
return ×tamp.Timestamp{
|
|
Seconds: int64(unixNano / 1e9),
|
|
Nanos: int32(unixNano % 1e9),
|
|
}
|
|
}
|
|
|
|
func rowToPoint(v *view.View, row *view.Row, endTimestamp *timestamp.Timestamp, mType measureType) *metricspb.Point {
|
|
pt := &metricspb.Point{
|
|
Timestamp: endTimestamp,
|
|
}
|
|
|
|
switch data := row.Data.(type) {
|
|
case *view.CountData:
|
|
pt.Value = &metricspb.Point_Int64Value{Int64Value: data.Value}
|
|
|
|
case *view.DistributionData:
|
|
pt.Value = &metricspb.Point_DistributionValue{
|
|
DistributionValue: &metricspb.DistributionValue{
|
|
Count: data.Count,
|
|
Sum: float64(data.Count) * data.Mean, // because Mean := Sum/Count
|
|
Buckets: bucketsToProtoBuckets(data.CountPerBucket, data.ExemplarsPerBucket),
|
|
BucketOptions: &metricspb.DistributionValue_BucketOptions{
|
|
Type: &metricspb.DistributionValue_BucketOptions_Explicit_{
|
|
Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{
|
|
Bounds: v.Aggregation.Buckets,
|
|
},
|
|
},
|
|
},
|
|
SumOfSquaredDeviation: data.SumOfSquaredDev,
|
|
}}
|
|
|
|
case *view.LastValueData:
|
|
setPointValue(pt, data.Value, mType)
|
|
|
|
case *view.SumData:
|
|
setPointValue(pt, data.Value, mType)
|
|
}
|
|
|
|
return pt
|
|
}
|
|
|
|
// Not returning anything from this function because metricspb.Point.is_Value is an unexported
|
|
// interface hence we just have to set its value by pointer.
|
|
func setPointValue(pt *metricspb.Point, value float64, mType measureType) {
|
|
if mType == measureInt64 {
|
|
pt.Value = &metricspb.Point_Int64Value{Int64Value: int64(value)}
|
|
} else {
|
|
pt.Value = &metricspb.Point_DoubleValue{DoubleValue: value}
|
|
}
|
|
}
|
|
|
|
// countPerBucket and exemplars are of the same length in well formed data,
|
|
// otherwise ensure that even if exemplars are non-existent that we always
|
|
// insert counts and create distribution value buckets.
|
|
func bucketsToProtoBuckets(countPerBucket []int64, exemplars []*exemplar.Exemplar) []*metricspb.DistributionValue_Bucket {
|
|
distBuckets := make([]*metricspb.DistributionValue_Bucket, len(countPerBucket))
|
|
for i := 0; i < len(countPerBucket); i++ {
|
|
count := countPerBucket[i]
|
|
|
|
var exmplr *exemplar.Exemplar
|
|
if i < len(exemplars) {
|
|
exmplr = exemplars[i]
|
|
}
|
|
|
|
var protoExemplar *metricspb.DistributionValue_Exemplar
|
|
if exmplr != nil {
|
|
protoExemplar = &metricspb.DistributionValue_Exemplar{
|
|
Value: exmplr.Value,
|
|
Timestamp: timeToTimestamp(exmplr.Timestamp),
|
|
Attachments: exmplr.Attachments,
|
|
}
|
|
}
|
|
|
|
distBuckets[i] = &metricspb.DistributionValue_Bucket{
|
|
Count: count,
|
|
Exemplar: protoExemplar,
|
|
}
|
|
}
|
|
|
|
return distBuckets
|
|
}
|
|
|
|
func labelValuesFromTags(tags []tag.Tag) []*metricspb.LabelValue {
|
|
if len(tags) == 0 {
|
|
return nil
|
|
}
|
|
|
|
labelValues := make([]*metricspb.LabelValue, 0, len(tags))
|
|
for _, tag_ := range tags {
|
|
labelValues = append(labelValues, &metricspb.LabelValue{
|
|
Value: tag_.Value,
|
|
|
|
// It is imperative that we set the "HasValue" attribute,
|
|
// in order to distinguish missing a label from the empty string.
|
|
// https://godoc.org/github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1#LabelValue.HasValue
|
|
//
|
|
// OpenCensus-Go uses non-pointers for tags as seen by this function's arguments,
|
|
// so the best case that we can use to distinguish missing labels/tags from the
|
|
// empty string is by checking if the Tag.Key.Name() != "" to indicate that we have
|
|
// a value.
|
|
HasValue: tag_.Key.Name() != "",
|
|
})
|
|
}
|
|
return labelValues
|
|
}
|