2020-03-25 23:47:17 +02:00
// Copyright The OpenTelemetry Authors
2020-01-21 19:15:09 +02:00
//
// 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 histogram
import (
"context"
"math"
"math/rand"
"sort"
"testing"
"github.com/stretchr/testify/require"
2020-03-19 21:02:46 +02:00
"go.opentelemetry.io/otel/api/metric"
2020-01-21 19:15:09 +02:00
"go.opentelemetry.io/otel/sdk/metric/aggregator/test"
)
const count = 100
type policy struct {
name string
absolute bool
sign func ( ) int
}
var (
positiveOnly = policy {
name : "absolute" ,
absolute : true ,
sign : func ( ) int { return + 1 } ,
}
negativeOnly = policy {
name : "negative" ,
absolute : false ,
sign : func ( ) int { return - 1 } ,
}
positiveAndNegative = policy {
name : "positiveAndNegative" ,
absolute : false ,
sign : func ( ) int {
if rand . Uint32 ( ) > math . MaxUint32 / 2 {
return - 1
}
return 1
} ,
}
2020-05-11 08:44:42 +02:00
boundaries = map [ metric . NumberKind ] [ ] metric . Number {
metric . Float64NumberKind : { metric . NewFloat64Number ( 500 ) , metric . NewFloat64Number ( 250 ) , metric . NewFloat64Number ( 750 ) } ,
metric . Int64NumberKind : { metric . NewInt64Number ( 500 ) , metric . NewInt64Number ( 250 ) , metric . NewInt64Number ( 750 ) } ,
2020-01-21 19:15:09 +02:00
}
)
func TestHistogramAbsolute ( t * testing . T ) {
test . RunProfiles ( t , func ( t * testing . T , profile test . Profile ) {
histogram ( t , profile , positiveOnly )
} )
}
func TestHistogramNegativeOnly ( t * testing . T ) {
test . RunProfiles ( t , func ( t * testing . T , profile test . Profile ) {
histogram ( t , profile , negativeOnly )
} )
}
func TestHistogramPositiveAndNegative ( t * testing . T ) {
test . RunProfiles ( t , func ( t * testing . T , profile test . Profile ) {
histogram ( t , profile , positiveAndNegative )
} )
}
// Validates count, sum and buckets for a given profile and policy
func histogram ( t * testing . T , profile test . Profile , policy policy ) {
ctx := context . Background ( )
2020-05-16 07:11:12 +02:00
descriptor := test . NewAggregatorTest ( metric . ValueRecorderKind , profile . NumberKind )
2020-01-21 19:15:09 +02:00
agg := New ( descriptor , boundaries [ profile . NumberKind ] )
all := test . NewNumbers ( profile . NumberKind )
for i := 0 ; i < count ; i ++ {
x := profile . Random ( policy . sign ( ) )
all . Append ( x )
test . CheckedUpdate ( t , agg , x , descriptor )
}
agg . Checkpoint ( ctx , descriptor )
all . Sort ( )
asum , err := agg . Sum ( )
sum := all . Sum ( )
require . InEpsilon ( t ,
sum . CoerceToFloat64 ( profile . NumberKind ) ,
asum . CoerceToFloat64 ( profile . NumberKind ) ,
0.000000001 ,
"Same sum - " + policy . name )
require . Nil ( t , err )
count , err := agg . Count ( )
require . Equal ( t , all . Count ( ) , count , "Same count -" + policy . name )
require . Nil ( t , err )
2020-04-29 19:08:58 +02:00
require . Equal ( t , len ( agg . checkpoint . buckets . Counts ) , len ( boundaries [ profile . NumberKind ] ) + 1 , "There should be b + 1 counts, where b is the number of boundaries" )
2020-01-21 19:15:09 +02:00
counts := calcBuckets ( all . Points ( ) , profile )
for i , v := range counts {
2020-04-29 19:08:58 +02:00
bCount := agg . checkpoint . buckets . Counts [ i ] . AsUint64 ( )
require . Equal ( t , v , bCount , "Wrong bucket #%d count: %v != %v" , i , counts , agg . checkpoint . buckets . Counts )
2020-01-21 19:15:09 +02:00
}
}
func TestHistogramMerge ( t * testing . T ) {
ctx := context . Background ( )
test . RunProfiles ( t , func ( t * testing . T , profile test . Profile ) {
2020-05-16 07:11:12 +02:00
descriptor := test . NewAggregatorTest ( metric . ValueRecorderKind , profile . NumberKind )
2020-01-21 19:15:09 +02:00
agg1 := New ( descriptor , boundaries [ profile . NumberKind ] )
agg2 := New ( descriptor , boundaries [ profile . NumberKind ] )
all := test . NewNumbers ( profile . NumberKind )
for i := 0 ; i < count ; i ++ {
x := profile . Random ( + 1 )
all . Append ( x )
test . CheckedUpdate ( t , agg1 , x , descriptor )
}
for i := 0 ; i < count ; i ++ {
x := profile . Random ( + 1 )
all . Append ( x )
test . CheckedUpdate ( t , agg2 , x , descriptor )
}
agg1 . Checkpoint ( ctx , descriptor )
agg2 . Checkpoint ( ctx , descriptor )
test . CheckedMerge ( t , agg1 , agg2 , descriptor )
all . Sort ( )
asum , err := agg1 . Sum ( )
sum := all . Sum ( )
require . InEpsilon ( t ,
sum . CoerceToFloat64 ( profile . NumberKind ) ,
asum . CoerceToFloat64 ( profile . NumberKind ) ,
0.000000001 ,
"Same sum - absolute" )
require . Nil ( t , err )
count , err := agg1 . Count ( )
require . Equal ( t , all . Count ( ) , count , "Same count - absolute" )
require . Nil ( t , err )
2020-04-29 19:08:58 +02:00
require . Equal ( t , len ( agg1 . checkpoint . buckets . Counts ) , len ( boundaries [ profile . NumberKind ] ) + 1 , "There should be b + 1 counts, where b is the number of boundaries" )
2020-01-21 19:15:09 +02:00
counts := calcBuckets ( all . Points ( ) , profile )
for i , v := range counts {
2020-04-29 19:08:58 +02:00
bCount := agg1 . checkpoint . buckets . Counts [ i ] . AsUint64 ( )
require . Equal ( t , v , bCount , "Wrong bucket #%d count: %v != %v" , i , counts , agg1 . checkpoint . buckets . Counts )
2020-01-21 19:15:09 +02:00
}
} )
}
func TestHistogramNotSet ( t * testing . T ) {
ctx := context . Background ( )
test . RunProfiles ( t , func ( t * testing . T , profile test . Profile ) {
2020-05-16 07:11:12 +02:00
descriptor := test . NewAggregatorTest ( metric . ValueRecorderKind , profile . NumberKind )
2020-01-21 19:15:09 +02:00
agg := New ( descriptor , boundaries [ profile . NumberKind ] )
agg . Checkpoint ( ctx , descriptor )
asum , err := agg . Sum ( )
2020-05-11 08:44:42 +02:00
require . Equal ( t , metric . Number ( 0 ) , asum , "Empty checkpoint sum = 0" )
2020-01-21 19:15:09 +02:00
require . Nil ( t , err )
count , err := agg . Count ( )
require . Equal ( t , int64 ( 0 ) , count , "Empty checkpoint count = 0" )
require . Nil ( t , err )
2020-04-29 19:08:58 +02:00
require . Equal ( t , len ( agg . checkpoint . buckets . Counts ) , len ( boundaries [ profile . NumberKind ] ) + 1 , "There should be b + 1 counts, where b is the number of boundaries" )
for i , bCount := range agg . checkpoint . buckets . Counts {
2020-01-21 19:15:09 +02:00
require . Equal ( t , uint64 ( 0 ) , bCount . AsUint64 ( ) , "Bucket #%d must have 0 observed values" , i )
}
} )
}
2020-05-11 08:44:42 +02:00
func calcBuckets ( points [ ] metric . Number , profile test . Profile ) [ ] uint64 {
2020-01-21 19:15:09 +02:00
sortedBoundaries := numbers {
2020-05-11 08:44:42 +02:00
numbers : make ( [ ] metric . Number , len ( boundaries [ profile . NumberKind ] ) ) ,
2020-01-21 19:15:09 +02:00
kind : profile . NumberKind ,
}
copy ( sortedBoundaries . numbers , boundaries [ profile . NumberKind ] )
sort . Sort ( & sortedBoundaries )
boundaries := sortedBoundaries . numbers
counts := make ( [ ] uint64 , len ( boundaries ) + 1 )
idx := 0
for _ , p := range points {
for idx < len ( boundaries ) && p . CompareNumber ( profile . NumberKind , boundaries [ idx ] ) != - 1 {
idx ++
}
counts [ idx ] ++
}
return counts
}