opentelemetry_collector/receiver/metricgenerator/metric_generator.go (136 lines of code) (raw):

package metricgenerator import ( "math" "time" timestamp "google.golang.org/protobuf/types/known/timestamppb" metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" ) // MakeInt64TimeSeries generates a proto representation of a timeseries containing a single point for an int64 metric. func MakeInt64TimeSeries(val int64, startTime, now time.Time, labels []*metricspb.LabelValue) *metricspb.TimeSeries { return &metricspb.TimeSeries{ StartTimestamp: timestamp.New(startTime), LabelValues: labels, Points: []*metricspb.Point{{Timestamp: timestamp.New(now), Value: &metricspb.Point_Int64Value{Int64Value: val}}}, } } // MakeDoubleTimeSeries generates a proto representation of a timeseries containing a single point for an double metric. func MakeDoubleTimeSeries(val float64, startTime, now time.Time, labels []*metricspb.LabelValue) *metricspb.TimeSeries { return &metricspb.TimeSeries{ StartTimestamp: timestamp.New(startTime), LabelValues: labels, Points: []*metricspb.Point{{Timestamp: timestamp.New(now), Value: &metricspb.Point_DoubleValue{DoubleValue: val}}}, } } // MakeExponentialBucketOptions generates a proto representation of a config which, // defines a distribution's bounds. This defines maxExponent + 2 buckets. The boundaries for bucket // index i are: // // [0, boundsBase ^ i) for i == 0 // [boundsBase ^ (i - 1)], boundsBase ^ i) for 0 < i <= maxExponent // [boundsBase ^ (i - 1), +infinity) for i == maxExponent + 1 func MakeExponentialBucketOptions(boundsBase, maxExponent float64) *metricspb.DistributionValue_BucketOptions { bounds := make([]float64, 0, int(maxExponent)) for i := float64(0); i <= maxExponent; i++ { bounds = append(bounds, math.Pow(boundsBase, i)) } return &metricspb.DistributionValue_BucketOptions{ Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ Bounds: bounds, }, }, } } // FormatBucketOptions formats a []float64 bounds as a DistributionValue_BucketOptions proto. // bounds should list the upper boundaries of the distribution buckets, // except for the last bucket which has +infinity as an implied upper bound. func FormatBucketOptions(bounds []float64) *metricspb.DistributionValue_BucketOptions { return &metricspb.DistributionValue_BucketOptions{ Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ Bounds: bounds, }, }, } } // MakeBuckets generates a proto representation of a distribution containing a single value. // bounds defines the bucket boundaries of the distribution. func MakeBuckets(values, bounds []float64) []*metricspb.DistributionValue_Bucket { buckets := make([]*metricspb.DistributionValue_Bucket, len(bounds)+1) for i := 0; i <= len(bounds); i++ { buckets[i] = &metricspb.DistributionValue_Bucket{} } for _, val := range values { index := getBucketIndex(val, bounds) buckets[index].Count++ } return buckets } func getBucketIndex(val float64, bounds []float64) int { if val < bounds[0] { return 0 } for i, lowerBound := range bounds { if i < len(bounds)-1 && val >= lowerBound && val < bounds[i+1] { return i + 1 } } return len(bounds) } func formatBuckets(distribution []int64) []*metricspb.DistributionValue_Bucket { buckets := make([]*metricspb.DistributionValue_Bucket, len(distribution)) for i := 0; i < len(distribution); i++ { buckets[i] = &metricspb.DistributionValue_Bucket{ Count: distribution[i], } } return buckets } // MakeLabelValue generates a proto representation of a metric label with value as its value. func MakeLabelValue(value string) *metricspb.LabelValue { return &metricspb.LabelValue{ Value: value, HasValue: true, } } // MakeSingleValueDistributionTimeSeries generates a proto representation of a timeseries // containing a single point consisting of a distribution containing a single value. // The distribution bucket bounds are defined by the bucketOptions argument. func MakeSingleValueDistributionTimeSeries( val float64, startTime, currentTime time.Time, bucketOptions *metricspb.DistributionValue_BucketOptions, labels []*metricspb.LabelValue) *metricspb.TimeSeries { bounds := bucketOptions.GetExplicit().Bounds distribution := metricspb.DistributionValue{ Count: 1, Sum: val, SumOfSquaredDeviation: 0, BucketOptions: bucketOptions, Buckets: MakeBuckets([]float64{val}, bounds), } return makeTimeseriesFromDistribution(&distribution, startTime, currentTime, labels) } // GetSumOfSquaredDeviationsFromIntDist calculates the sum of squared deviations from the mean. // For values x_i this is: Sum[i=1..n]((x_i - mean)^2) // Calculated from the count, sum, and sum of squares of the values. func GetSumOfSquaredDeviationsFromIntDist(sum, sumSquares, count int64) float64 { if count <= 0 { return 0 } diff := count*sumSquares - sum*sum return float64(diff) / float64(count) } // MakeDistributionTimeSeries formats a distribution and its metadata as a TimeSeries. func MakeDistributionTimeSeries( distribution []int64, sum float64, sumSquaredDeviation float64, count int64, startTime, currentTime time.Time, bucketOptions *metricspb.DistributionValue_BucketOptions, labels []*metricspb.LabelValue) *metricspb.TimeSeries { distributionProto := metricspb.DistributionValue{ Count: count, Sum: sum, SumOfSquaredDeviation: sumSquaredDeviation, BucketOptions: bucketOptions, Buckets: formatBuckets(distribution), } return makeTimeseriesFromDistribution( &distributionProto, startTime, currentTime, labels) } // makeTimeseriesFromDistribution formats a distribution proto as a TimeSeries. func makeTimeseriesFromDistribution( distribution *metricspb.DistributionValue, startTime, currentTime time.Time, labels []*metricspb.LabelValue) *metricspb.TimeSeries { point := metricspb.Point{ Timestamp: timestamp.New(currentTime), Value: &metricspb.Point_DistributionValue{DistributionValue: distribution}, } return &metricspb.TimeSeries{ StartTimestamp: timestamp.New(startTime), LabelValues: labels, Points: []*metricspb.Point{&point}, } }