diff --git a/sdk/metric/aggregator/exponential/README.md b/sdk/metric/aggregator/exponential/README.md deleted file mode 100644 index b58d6aecbb2..00000000000 --- a/sdk/metric/aggregator/exponential/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Base-2 Exponential Histogram - -## Design - -This document is a placeholder for future Aggregator, once seen in [PR -2393](https://github.com/open-telemetry/opentelemetry-go/pull/2393). - -Only the mapping functions have been made available at this time. The -equations tested here are specified in the [data model for Exponential -Histogram data points](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md#exponentialhistogram). - -### Mapping function - -There are two mapping functions used, depending on the sign of the -scale. Negative and zero scales use the `mapping/exponent` mapping -function, which computes the bucket index directly from the bits of -the `float64` exponent. This mapping function is used with scale `-10 -<= scale <= 0`. Scales smaller than -10 map the entire normal -`float64` number range into a single bucket, thus are not considered -useful. - -The `mapping/logarithm` mapping function uses `math.Log(value)` times -the scaling factor `math.Ldexp(math.Log2E, scale)`. This mapping -function is used with `0 < scale <= 20`. The maximum scale is -selected because at scale 21, simply, it becomes difficult to test -correctness--at this point `math.MaxFloat64` maps to index -`math.MaxInt32` and the `math/big` logic used in testing breaks down. diff --git a/sdk/metric/aggregator/exponential/mapping/exponent/exponent.go b/sdk/metric/aggregator/exponential/mapping/exponent/exponent.go deleted file mode 100644 index 93dff7ef007..00000000000 --- a/sdk/metric/aggregator/exponential/mapping/exponent/exponent.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright The OpenTelemetry 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 exponent // import "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping/exponent" - -import ( - "fmt" - "math" - - "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping" -) - -const ( - // MinScale defines the point at which the exponential mapping - // function becomes useless for float64. With scale -10, ignoring - // subnormal values, bucket indices range from -1 to 1. - MinScale int32 = -10 - - // MaxScale is the largest scale supported in this code. Use - // ../logarithm for larger scales. - MaxScale int32 = 0 -) - -type exponentMapping struct { - shift uint8 // equals negative scale -} - -// exponentMapping is used for negative scales, effectively a -// mapping of the base-2 logarithm of the exponent. -var prebuiltMappings = [-MinScale + 1]exponentMapping{ - {10}, - {9}, - {8}, - {7}, - {6}, - {5}, - {4}, - {3}, - {2}, - {1}, - {0}, -} - -// NewMapping constructs an exponential mapping function, used for scales <= 0. -func NewMapping(scale int32) (mapping.Mapping, error) { - if scale > MaxScale { - return nil, fmt.Errorf("exponent mapping requires scale <= 0") - } - if scale < MinScale { - return nil, fmt.Errorf("scale too low") - } - return &prebuiltMappings[scale-MinScale], nil -} - -// MapToIndex implements mapping.Mapping. -func (e *exponentMapping) MapToIndex(value float64) int32 { - // Note: we can assume not a 0, Inf, or NaN; positive sign bit. - - // Note: bit-shifting does the right thing for negative - // exponents, e.g., -1 >> 1 == -1. - return getBase2(value) >> e.shift -} - -func (e *exponentMapping) minIndex() int32 { - return int32(MinNormalExponent) >> e.shift -} - -func (e *exponentMapping) maxIndex() int32 { - return int32(MaxNormalExponent) >> e.shift -} - -// LowerBoundary implements mapping.Mapping. -func (e *exponentMapping) LowerBoundary(index int32) (float64, error) { - if min := e.minIndex(); index < min { - return 0, mapping.ErrUnderflow - } - - if max := e.maxIndex(); index > max { - return 0, mapping.ErrOverflow - } - - unbiased := int64(index << e.shift) - - // Note: although the mapping function rounds subnormal values - // up to the smallest normal value, there are still buckets - // that may be filled that start at subnormal values. The - // following code handles this correctly. It's equivalent to and - // faster than math.Ldexp(1, int(unbiased)). - if unbiased < int64(MinNormalExponent) { - subnormal := uint64(1 << SignificandWidth) - for unbiased < int64(MinNormalExponent) { - unbiased++ - subnormal >>= 1 - } - return math.Float64frombits(subnormal), nil - } - exponent := unbiased + ExponentBias - - bits := uint64(exponent << SignificandWidth) - return math.Float64frombits(bits), nil -} - -// Scale implements mapping.Mapping. -func (e *exponentMapping) Scale() int32 { - return -int32(e.shift) -} diff --git a/sdk/metric/aggregator/exponential/mapping/exponent/exponent_test.go b/sdk/metric/aggregator/exponential/mapping/exponent/exponent_test.go deleted file mode 100644 index 0258aaf8dc6..00000000000 --- a/sdk/metric/aggregator/exponential/mapping/exponent/exponent_test.go +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright The OpenTelemetry 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 exponent - -import ( - "fmt" - "math" - "math/big" - "testing" - - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping" -) - -type expectMapping struct { - value float64 - index int32 -} - -// Tests that getBase2 returns the base-2 exponent as documented, unlike -// math.Frexp. -func TestGetBase2(t *testing.T) { - require.Equal(t, int32(-1022), MinNormalExponent) - require.Equal(t, int32(+1023), MaxNormalExponent) - - require.Equal(t, MaxNormalExponent, getBase2(0x1p+1023)) - require.Equal(t, int32(1022), getBase2(0x1p+1022)) - - require.Equal(t, int32(0), getBase2(1)) - - require.Equal(t, int32(-1021), getBase2(0x1p-1021)) - require.Equal(t, int32(-1022), getBase2(0x1p-1022)) - - // Subnormals below this point - require.Equal(t, int32(-1022), getBase2(0x1p-1023)) - require.Equal(t, int32(-1022), getBase2(0x1p-1024)) - require.Equal(t, int32(-1022), getBase2(0x1p-1025)) - require.Equal(t, int32(-1022), getBase2(0x1p-1074)) -} - -// Tests a few cases with scale=0. -func TestExponentMappingZero(t *testing.T) { - m, err := NewMapping(0) - require.NoError(t, err) - - require.Equal(t, int32(0), m.Scale()) - - for _, pair := range []expectMapping{ - {math.MaxFloat64, MaxNormalExponent}, - {0x1p+1023, MaxNormalExponent}, - {0x1p-1022, MinNormalExponent}, - {math.SmallestNonzeroFloat64, MinNormalExponent}, - {4, 2}, - {3, 1}, - {2, 1}, - {1.5, 0}, - {1, 0}, - {0.75, -1}, - {0.5, -1}, - {0.25, -2}, - } { - idx := m.MapToIndex(pair.value) - - require.Equal(t, pair.index, idx) - } -} - -// Tests a few cases with scale=MinScale. -func TestExponentMappingMinScale(t *testing.T) { - m, err := NewMapping(MinScale) - require.NoError(t, err) - - require.Equal(t, MinScale, m.Scale()) - - for _, pair := range []expectMapping{ - {1, 0}, - {math.MaxFloat64 / 2, 0}, - {math.MaxFloat64, 0}, - {math.SmallestNonzeroFloat64, -1}, - {0.5, -1}, - } { - t.Run(fmt.Sprint(pair.value), func(t *testing.T) { - idx := m.MapToIndex(pair.value) - - require.Equal(t, pair.index, idx) - }) - } -} - -// Tests invalid scales. -func TestInvalidScale(t *testing.T) { - m, err := NewMapping(1) - require.Error(t, err) - require.Nil(t, m) - - m, err = NewMapping(MinScale - 1) - require.Error(t, err) - require.Nil(t, m) -} - -// Tests a few cases with scale=-1. -func TestExponentMappingNegOne(t *testing.T) { - m, _ := NewMapping(-1) - - for _, pair := range []expectMapping{ - {16, 2}, - {15, 1}, - {9, 1}, - {8, 1}, - {5, 1}, - {4, 1}, - {3, 0}, - {2, 0}, - {1.5, 0}, - {1, 0}, - {0.75, -1}, - {0.5, -1}, - {0.25, -1}, - {0.20, -2}, - {0.13, -2}, - {0.125, -2}, - {0.10, -2}, - {0.0625, -2}, - {0.06, -3}, - } { - idx := m.MapToIndex(pair.value) - require.Equal(t, pair.index, idx, "value: %v", pair.value) - } -} - -// Tests a few cases with scale=-4. -func TestExponentMappingNegFour(t *testing.T) { - m, err := NewMapping(-4) - require.NoError(t, err) - require.Equal(t, int32(-4), m.Scale()) - - for _, pair := range []expectMapping{ - {float64(0x1), 0}, - {float64(0x10), 0}, - {float64(0x100), 0}, - {float64(0x1000), 0}, - {float64(0x10000), 1}, // Base == 2**16 - {float64(0x100000), 1}, - {float64(0x1000000), 1}, - {float64(0x10000000), 1}, - {float64(0x100000000), 2}, // == 2**32 - {float64(0x1000000000), 2}, - {float64(0x10000000000), 2}, - {float64(0x100000000000), 2}, - {float64(0x1000000000000), 3}, // 2**48 - {float64(0x10000000000000), 3}, - {float64(0x100000000000000), 3}, - {float64(0x1000000000000000), 3}, - {float64(0x10000000000000000), 4}, // 2**64 - {float64(0x100000000000000000), 4}, - {float64(0x1000000000000000000), 4}, - {float64(0x10000000000000000000), 4}, - {float64(0x100000000000000000000), 5}, - - {1 / float64(0x1), 0}, - {1 / float64(0x10), -1}, - {1 / float64(0x100), -1}, - {1 / float64(0x1000), -1}, - {1 / float64(0x10000), -1}, // 2**-16 - {1 / float64(0x100000), -2}, - {1 / float64(0x1000000), -2}, - {1 / float64(0x10000000), -2}, - {1 / float64(0x100000000), -2}, // 2**-32 - {1 / float64(0x1000000000), -3}, - {1 / float64(0x10000000000), -3}, - {1 / float64(0x100000000000), -3}, - {1 / float64(0x1000000000000), -3}, // 2**-48 - {1 / float64(0x10000000000000), -4}, - {1 / float64(0x100000000000000), -4}, - {1 / float64(0x1000000000000000), -4}, - {1 / float64(0x10000000000000000), -4}, // 2**-64 - {1 / float64(0x100000000000000000), -5}, - - // Max values - {0x1.FFFFFFFFFFFFFp1023, 63}, - {0x1p1023, 63}, - {0x1p1019, 63}, - {0x1p1008, 63}, - {0x1p1007, 62}, - {0x1p1000, 62}, - {0x1p0992, 62}, - {0x1p0991, 61}, - - // Min and subnormal values - {0x1p-1074, -64}, - {0x1p-1073, -64}, - {0x1p-1072, -64}, - {0x1p-1057, -64}, - {0x1p-1056, -64}, - {0x1p-1041, -64}, - {0x1p-1040, -64}, - {0x1p-1025, -64}, - {0x1p-1024, -64}, - {0x1p-1023, -64}, - {0x1p-1022, -64}, - {0x1p-1009, -64}, - {0x1p-1008, -63}, - {0x1p-0993, -63}, - {0x1p-0992, -62}, - {0x1p-0977, -62}, - {0x1p-0976, -61}, - } { - t.Run(fmt.Sprintf("%x", pair.value), func(t *testing.T) { - index := m.MapToIndex(pair.value) - - require.Equal(t, pair.index, index, "value: %#x", pair.value) - }) - } -} - -// roundedBoundary computes the correct boundary rounded to a float64 -// using math/big. Note that this function uses a Square() where the -// one in ../logarithm uses a SquareRoot(). -func roundedBoundary(scale, index int32) float64 { - one := big.NewFloat(1) - f := (&big.Float{}).SetMantExp(one, int(index)) - for i := scale; i < 0; i++ { - f = (&big.Float{}).Mul(f, f) - } - - result, _ := f.Float64() - return result -} - -// TestExponentIndexMax ensures that for every valid scale, MaxFloat -// maps into the correct maximum index. Also tests that the reverse -// lookup does not produce infinity and the following index produces -// an overflow error. -func TestExponentIndexMax(t *testing.T) { - for scale := MinScale; scale <= MaxScale; scale++ { - m, err := NewMapping(scale) - require.NoError(t, err) - - index := m.MapToIndex(MaxValue) - - // Correct max index is one less than the first index - // that overflows math.MaxFloat64, i.e., one less than - // the index of +Inf. - maxIndex := (int32(MaxNormalExponent+1) >> -scale) - 1 - require.Equal(t, index, int32(maxIndex)) - - // The index maps to a finite boundary. - bound, err := m.LowerBoundary(index) - require.NoError(t, err) - - require.Equal(t, bound, roundedBoundary(scale, maxIndex)) - - // One larger index will overflow. - _, err = m.LowerBoundary(index + 1) - require.Equal(t, err, mapping.ErrOverflow) - } -} - -// TestExponentIndexMin ensures that for every valid scale, the -// smallest normal number and all smaller numbers map to the correct -// index, which is that of the smallest normal number. -// -// Tests that the lower boundary of the smallest bucket is correct, -// even when that number is subnormal. -func TestExponentIndexMin(t *testing.T) { - for scale := MinScale; scale <= MaxScale; scale++ { - m, err := NewMapping(scale) - require.NoError(t, err) - - minIndex := m.MapToIndex(MinValue) - - boundary, err := m.LowerBoundary(minIndex) - require.NoError(t, err) - - correctMinIndex := int64(MinNormalExponent) >> -scale - require.Greater(t, correctMinIndex, int64(math.MinInt32)) - require.Equal(t, int32(correctMinIndex), minIndex) - - correctBoundary := roundedBoundary(scale, int32(correctMinIndex)) - - require.Equal(t, correctBoundary, boundary) - require.Greater(t, roundedBoundary(scale, int32(correctMinIndex+1)), boundary) - - // Subnormal values map to the min index: - require.Equal(t, m.MapToIndex(MinValue/2), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(MinValue/3), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(MinValue/100), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1050), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1073), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1.1p-1073), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1074), int32(correctMinIndex)) - - // One smaller index will underflow. - _, err = m.LowerBoundary(minIndex - 1) - require.Equal(t, err, mapping.ErrUnderflow) - } -} diff --git a/sdk/metric/aggregator/exponential/mapping/exponent/float64.go b/sdk/metric/aggregator/exponential/mapping/exponent/float64.go deleted file mode 100644 index 6deb81192de..00000000000 --- a/sdk/metric/aggregator/exponential/mapping/exponent/float64.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright The OpenTelemetry 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 exponent // import "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping/exponent" - -import "math" - -const ( - // SignificandWidth is the size of an IEEE 754 double-precision - // floating-point significand. - SignificandWidth = 52 - // ExponentWidth is the size of an IEEE 754 double-precision - // floating-point exponent. - ExponentWidth = 11 - - // SignificandMask is the mask for the significand of an IEEE 754 - // double-precision floating-point value: 0xFFFFFFFFFFFFF. - SignificandMask = 1<> SignificandWidth - return int32(rawExponent - ExponentBias) -} diff --git a/sdk/metric/aggregator/exponential/mapping/logarithm/logarithm.go b/sdk/metric/aggregator/exponential/mapping/logarithm/logarithm.go deleted file mode 100644 index 223c93d16a9..00000000000 --- a/sdk/metric/aggregator/exponential/mapping/logarithm/logarithm.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright The OpenTelemetry 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 logarithm // import "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping/logarithm" - -import ( - "fmt" - "math" - "sync" - - "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping" - "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping/exponent" -) - -const ( - // MinScale ensures that the ../exponent mapper is used for - // zero and negative scale values. Do not use the logarithm - // mapper for scales <= 0. - MinScale int32 = 1 - - // MaxScale is selected as the largest scale that is possible - // in current code, considering there are 10 bits of base-2 - // exponent combined with scale-bits of range. At this scale, - // the growth factor is 0.0000661%. - // - // Scales larger than 20 complicate the logic in cmd/prebuild, - // because math/big overflows when exponent is math.MaxInt32 - // (== the index of math.MaxFloat64 at scale=21), - // - // At scale=20, index values are in the interval [-0x3fe00000, - // 0x3fffffff], having 31 bits of information. This is - // sensible given that the OTLP exponential histogram data - // point uses a signed 32 bit integer for indices. - MaxScale int32 = 20 - - // MaxValue is the largest normal number. - MaxValue = math.MaxFloat64 - - // MinValue is the smallest normal number. - MinValue = 0x1p-1022 -) - -// logarithmMapping contains the constants used to implement the -// exponential mapping function for a particular scale > 0. Note that -// these structs are compiled in using code generated by the -// ./cmd/prebuild package, this way no allocations are required as the -// aggregators switch between mapping functions and the two mapping -// functions are kept separate. -// -// Note that some of these fields could be calculated easily at -// runtime, but they are compiled in to avoid those operations at -// runtime (e.g., calls to math.Ldexp(math.Log2E, scale) for every -// measurement). -type logarithmMapping struct { - // scale is between MinScale and MaxScale - scale int32 - - // minIndex is the index of MinValue - minIndex int32 - // maxIndex is the index of MaxValue - maxIndex int32 - - // scaleFactor is used and computed as follows: - // index = log(value) / log(base) - // = log(value) / log(2^(2^-scale)) - // = log(value) / (2^-scale * log(2)) - // = log(value) * (1/log(2) * 2^scale) - // = log(value) * scaleFactor - // where: - // scaleFactor = (1/log(2) * 2^scale) - // = math.Log2E * math.Exp2(scale) - // = math.Ldexp(math.Log2E, scale) - // Because multiplication is faster than division, we define scaleFactor as a multiplier. - // This implementation was copied from a Java prototype. See: - // https://github.com/newrelic-experimental/newrelic-sketch-java/blob/1ce245713603d61ba3a4510f6df930a5479cd3f6/src/main/java/com/newrelic/nrsketch/indexer/LogIndexer.java - // for the equations used here. - scaleFactor float64 - - // log(boundary) = index * log(base) - // log(boundary) = index * log(2^(2^-scale)) - // log(boundary) = index * 2^-scale * log(2) - // boundary = exp(index * inverseFactor) - // where: - // inverseFactor = 2^-scale * log(2) - // = math.Ldexp(math.Ln2, -scale) - inverseFactor float64 -} - -var ( - _ mapping.Mapping = &logarithmMapping{} - - prebuiltMappingsLock sync.Mutex - prebuiltMappings = map[int32]*logarithmMapping{} -) - -// NewMapping constructs a logarithm mapping function, used for scales > 0. -func NewMapping(scale int32) (mapping.Mapping, error) { - // An assumption used in this code is that scale is > 0. If - // scale is <= 0 it's better to use the exponent mapping. - if scale < MinScale || scale > MaxScale { - // scale 20 can represent the entire float64 range - // with a 30 bit index, and we don't handle larger - // scales to simplify range tests in this package. - return nil, fmt.Errorf("scale out of bounds") - } - prebuiltMappingsLock.Lock() - defer prebuiltMappingsLock.Unlock() - - if p := prebuiltMappings[scale]; p != nil { - return p, nil - } - l := &logarithmMapping{ - scale: scale, - maxIndex: int32((int64(exponent.MaxNormalExponent+1) << scale) - 1), - minIndex: int32(int64(exponent.MinNormalExponent) << scale), - scaleFactor: math.Ldexp(math.Log2E, int(scale)), - inverseFactor: math.Ldexp(math.Ln2, int(-scale)), - } - prebuiltMappings[scale] = l - return l, nil -} - -// MapToIndex implements mapping.Mapping. -func (l *logarithmMapping) MapToIndex(value float64) int32 { - // Note: we can assume not a 0, Inf, or NaN; positive sign bit. - if value <= MinValue { - return l.minIndex - } - // Use Floor() to round toward 0. - index := int32(math.Floor(math.Log(value) * l.scaleFactor)) - - if index > l.maxIndex { - return l.maxIndex - } - return index -} - -// LowerBoundary implements mapping.Mapping. -func (l *logarithmMapping) LowerBoundary(index int32) (float64, error) { - if index >= l.maxIndex { - if index == l.maxIndex { - // Note that the equation on the last line of this - // function returns +Inf. Use the alternate equation. - return 2 * math.Exp(float64(index-(int32(1)< 0; i-- { - f = (&big.Float{}).Sqrt(f) - } - - result, _ := f.Float64() - return result -} - -// TestLogarithmIndexMax ensures that for every valid scale, MaxFloat -// maps into the correct maximum index. Also tests that the reverse -// lookup does not produce infinity and the following index produces -// an overflow error. -func TestLogarithmIndexMax(t *testing.T) { - for scale := MinScale; scale <= MaxScale; scale++ { - m, err := NewMapping(scale) - require.NoError(t, err) - - index := m.MapToIndex(MaxValue) - - // Correct max index is one less than the first index - // that overflows math.MaxFloat64, i.e., one less than - // the index of +Inf. - maxIndex64 := (int64(exponent.MaxNormalExponent+1) << scale) - 1 - require.Less(t, maxIndex64, int64(math.MaxInt32)) - require.Equal(t, index, int32(maxIndex64)) - - // The index maps to a finite boundary near MaxFloat. - bound, err := m.LowerBoundary(index) - require.NoError(t, err) - - base, _ := m.LowerBoundary(1) - - require.Less(t, bound, MaxValue) - - // The expected ratio equals the base factor. - require.InEpsilon(t, (MaxValue-bound)/bound, base-1, 1e-6) - - // One larger index will overflow. - _, err = m.LowerBoundary(index + 1) - require.Equal(t, err, mapping.ErrOverflow) - } -} - -// TestLogarithmIndexMin ensures that for every valid scale, Non-zero numbers -func TestLogarithmIndexMin(t *testing.T) { - for scale := MinScale; scale <= MaxScale; scale++ { - m, err := NewMapping(scale) - require.NoError(t, err) - - minIndex := m.MapToIndex(MinValue) - - mapped, err := m.LowerBoundary(minIndex) - require.NoError(t, err) - - correctMinIndex := int64(exponent.MinNormalExponent) << scale - require.Greater(t, correctMinIndex, int64(math.MinInt32)) - - correctMapped := roundedBoundary(scale, int32(correctMinIndex)) - require.Equal(t, correctMapped, MinValue) - require.InEpsilon(t, mapped, MinValue, 1e-6) - - require.Equal(t, minIndex, int32(correctMinIndex)) - - // Subnormal values map to the min index: - require.Equal(t, m.MapToIndex(MinValue/2), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(MinValue/3), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(MinValue/100), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1050), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1073), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1.1p-1073), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1074), int32(correctMinIndex)) - - // One smaller index will underflow. - _, err = m.LowerBoundary(minIndex - 1) - require.Equal(t, err, mapping.ErrUnderflow) - } -} - -// TestExponentIndexMax ensures that for every valid scale, MaxFloat -// maps into the correct maximum index. Also tests that the reverse -// lookup does not produce infinity and the following index produces -// an overflow error. -func TestExponentIndexMax(t *testing.T) { - for scale := MinScale; scale <= MaxScale; scale++ { - m, err := NewMapping(scale) - require.NoError(t, err) - - index := m.MapToIndex(MaxValue) - - // Correct max index is one less than the first index - // that overflows math.MaxFloat64, i.e., one less than - // the index of +Inf. - maxIndex64 := (int64(exponent.MaxNormalExponent+1) << scale) - 1 - require.Less(t, maxIndex64, int64(math.MaxInt32)) - require.Equal(t, index, int32(maxIndex64)) - - // The index maps to a finite boundary near MaxFloat. - bound, err := m.LowerBoundary(index) - require.NoError(t, err) - - base, _ := m.LowerBoundary(1) - - require.Less(t, bound, MaxValue) - - // The expected ratio equals the base factor. - require.InEpsilon(t, (MaxValue-bound)/bound, base-1, 1e-6) - - // One larger index will overflow. - _, err = m.LowerBoundary(index + 1) - require.Equal(t, err, mapping.ErrOverflow) - } -} - -// TestExponentIndexMin ensures that for every valid scale, the -// smallest normal number and all smaller numbers map to the correct -// index, which is that of the smallest normal number. -func TestExponentIndexMin(t *testing.T) { - for scale := MinScale; scale <= MaxScale; scale++ { - m, err := NewMapping(scale) - require.NoError(t, err) - - minIndex := m.MapToIndex(MinValue) - - mapped, err := m.LowerBoundary(minIndex) - require.NoError(t, err) - - correctMinIndex := int64(exponent.MinNormalExponent) << scale - require.Greater(t, correctMinIndex, int64(math.MinInt32)) - - correctMapped := roundedBoundary(scale, int32(correctMinIndex)) - require.Equal(t, correctMapped, MinValue) - require.InEpsilon(t, mapped, MinValue, 1e-6) - - require.Equal(t, minIndex, int32(correctMinIndex)) - - // Subnormal values map to the min index: - require.Equal(t, m.MapToIndex(MinValue/2), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(MinValue/3), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(MinValue/100), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1050), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1073), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1.1p-1073), int32(correctMinIndex)) - require.Equal(t, m.MapToIndex(0x1p-1074), int32(correctMinIndex)) - - // One smaller index will underflow. - _, err = m.LowerBoundary(minIndex - 1) - require.Equal(t, err, mapping.ErrUnderflow) - } -} diff --git a/sdk/metric/aggregator/exponential/mapping/mapping.go b/sdk/metric/aggregator/exponential/mapping/mapping.go deleted file mode 100644 index 19bf9df72d1..00000000000 --- a/sdk/metric/aggregator/exponential/mapping/mapping.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright The OpenTelemetry 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 mapping // import "go.opentelemetry.io/otel/sdk/metric/aggregator/exponential/mapping" - -import "fmt" - -// Mapping is the interface of an exponential histogram mapper. -type Mapping interface { - // MapToIndex maps positive floating point values to indexes - // corresponding to Scale(). Implementations are not expected - // to handle zeros, +Inf, NaN, or negative values. - MapToIndex(value float64) int32 - - // LowerBoundary returns the lower boundary of a given bucket - // index. The index is expected to map onto a range that is - // at least partially inside the range of normalized floating - // point values. If the corresponding bucket's upper boundary - // is less than or equal to 0x1p-1022, ErrUnderflow will be - // returned. If the corresponding bucket's lower boundary is - // greater than math.MaxFloat64, ErrOverflow will be returned. - LowerBoundary(index int32) (float64, error) - - // Scale returns the parameter that controls the resolution of - // this mapping. For details see: - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md#exponential-scale - Scale() int32 -} - -var ( - // ErrUnderflow is returned when computing the lower boundary - // of an index that maps into a denormalized floating point value. - ErrUnderflow = fmt.Errorf("underflow") - // ErrOverflow is returned when computing the lower boundary - // of an index that maps into +Inf. - ErrOverflow = fmt.Errorf("overflow") -) diff --git a/sdk/metric/number/doc.go b/sdk/metric/doc.go similarity index 59% rename from sdk/metric/number/doc.go rename to sdk/metric/doc.go index 6f947400a4b..dde763d634e 100644 --- a/sdk/metric/number/doc.go +++ b/sdk/metric/doc.go @@ -12,12 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -/* -Package number provides a number abstraction for instruments that -either support int64 or float64 input values. - -This package is currently in a pre-GA phase. Backwards incompatible changes -may be introduced in subsequent minor version releases as we work to track the -evolving OpenTelemetry specification and user feedback. -*/ -package number // import "go.opentelemetry.io/otel/sdk/metric/number" +package metric // import "go.opentelemetry.io/otel/sdk/metric" diff --git a/sdk/metric/go.mod b/sdk/metric/go.mod index a7cf0da0525..276ce287f70 100644 --- a/sdk/metric/go.mod +++ b/sdk/metric/go.mod @@ -38,11 +38,6 @@ replace go.opentelemetry.io/otel/sdk/metric => ./ replace go.opentelemetry.io/otel/trace => ../../trace -require ( - github.com/stretchr/testify v1.7.1 - go.opentelemetry.io/otel v1.6.3 -) - replace go.opentelemetry.io/otel/example/passthrough => ../../example/passthrough replace go.opentelemetry.io/otel/exporters/otlp/otlptrace => ../../exporters/otlp/otlptrace diff --git a/sdk/metric/go.sum b/sdk/metric/go.sum index 79af86d6596..e69de29bb2d 100644 --- a/sdk/metric/go.sum +++ b/sdk/metric/go.sum @@ -1,16 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdk/metric/number/kind_string.go b/sdk/metric/number/kind_string.go deleted file mode 100644 index 6288c7ea295..00000000000 --- a/sdk/metric/number/kind_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -type=Kind"; DO NOT EDIT. - -package number - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[Int64Kind-0] - _ = x[Float64Kind-1] -} - -const _Kind_name = "Int64KindFloat64Kind" - -var _Kind_index = [...]uint8{0, 9, 20} - -func (i Kind) String() string { - if i < 0 || i >= Kind(len(_Kind_index)-1) { - return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] -} diff --git a/sdk/metric/number/number.go b/sdk/metric/number/number.go deleted file mode 100644 index 5fd2f38f663..00000000000 --- a/sdk/metric/number/number.go +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright The OpenTelemetry 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 number // import "go.opentelemetry.io/otel/sdk/metric/number" - -//go:generate stringer -type=Kind - -import ( - "fmt" - "math" - "sync/atomic" - - "go.opentelemetry.io/otel/internal" -) - -// Kind describes the data type of the Number. -type Kind int8 - -const ( - // Int64Kind means that the Number stores int64. - Int64Kind Kind = iota - // Float64Kind means that the Number stores float64. - Float64Kind -) - -// Zero returns a zero value for a given Kind -func (k Kind) Zero() Number { - switch k { - case Int64Kind: - return NewInt64Number(0) - case Float64Kind: - return NewFloat64Number(0.) - default: - return Number(0) - } -} - -// Minimum returns the minimum representable value -// for a given Kind -func (k Kind) Minimum() Number { - switch k { - case Int64Kind: - return NewInt64Number(math.MinInt64) - case Float64Kind: - return NewFloat64Number(-1. * math.MaxFloat64) - default: - return Number(0) - } -} - -// Maximum returns the maximum representable value -// for a given Kind -func (k Kind) Maximum() Number { - switch k { - case Int64Kind: - return NewInt64Number(math.MaxInt64) - case Float64Kind: - return NewFloat64Number(math.MaxFloat64) - default: - return Number(0) - } -} - -// Number represents either an integral or a floating point value. It -// needs to be accompanied with a source of Kind that describes -// the actual type of the value stored within Number. -type Number uint64 - -// - constructors - -// NewNumberFromRaw creates a new Number from a raw value. -func NewNumberFromRaw(r uint64) Number { - return Number(r) -} - -// NewInt64Number creates an integral Number. -func NewInt64Number(i int64) Number { - return NewNumberFromRaw(internal.Int64ToRaw(i)) -} - -// NewFloat64Number creates a floating point Number. -func NewFloat64Number(f float64) Number { - return NewNumberFromRaw(internal.Float64ToRaw(f)) -} - -// NewNumberSignChange returns a number with the same magnitude and -// the opposite sign. `kind` must describe the kind of number in `nn`. -func NewNumberSignChange(kind Kind, nn Number) Number { - switch kind { - case Int64Kind: - return NewInt64Number(-nn.AsInt64()) - case Float64Kind: - return NewFloat64Number(-nn.AsFloat64()) - } - return nn -} - -// - as x - -// AsNumber gets the Number. -func (n *Number) AsNumber() Number { - return *n -} - -// AsRaw gets the uninterpreted raw value. Might be useful for some -// atomic operations. -func (n *Number) AsRaw() uint64 { - return uint64(*n) -} - -// AsInt64 assumes that the value contains an int64 and returns it as -// such. -func (n *Number) AsInt64() int64 { - return internal.RawToInt64(n.AsRaw()) -} - -// AsFloat64 assumes that the measurement value contains a float64 and -// returns it as such. -func (n *Number) AsFloat64() float64 { - return internal.RawToFloat64(n.AsRaw()) -} - -// - as x atomic - -// AsNumberAtomic gets the Number atomically. -func (n *Number) AsNumberAtomic() Number { - return NewNumberFromRaw(n.AsRawAtomic()) -} - -// AsRawAtomic gets the uninterpreted raw value atomically. Might be -// useful for some atomic operations. -func (n *Number) AsRawAtomic() uint64 { - return atomic.LoadUint64(n.AsRawPtr()) -} - -// AsInt64Atomic assumes that the number contains an int64 and returns -// it as such atomically. -func (n *Number) AsInt64Atomic() int64 { - return atomic.LoadInt64(n.AsInt64Ptr()) -} - -// AsFloat64Atomic assumes that the measurement value contains a -// float64 and returns it as such atomically. -func (n *Number) AsFloat64Atomic() float64 { - return internal.RawToFloat64(n.AsRawAtomic()) -} - -// - as x ptr - -// AsRawPtr gets the pointer to the raw, uninterpreted raw -// value. Might be useful for some atomic operations. -func (n *Number) AsRawPtr() *uint64 { - return (*uint64)(n) -} - -// AsInt64Ptr assumes that the number contains an int64 and returns a -// pointer to it. -func (n *Number) AsInt64Ptr() *int64 { - return internal.RawPtrToInt64Ptr(n.AsRawPtr()) -} - -// AsFloat64Ptr assumes that the number contains a float64 and returns a -// pointer to it. -func (n *Number) AsFloat64Ptr() *float64 { - return internal.RawPtrToFloat64Ptr(n.AsRawPtr()) -} - -// - coerce - -// CoerceToInt64 casts the number to int64. May result in -// data/precision loss. -func (n *Number) CoerceToInt64(kind Kind) int64 { - switch kind { - case Int64Kind: - return n.AsInt64() - case Float64Kind: - return int64(n.AsFloat64()) - default: - // you get what you deserve - return 0 - } -} - -// CoerceToFloat64 casts the number to float64. May result in -// data/precision loss. -func (n *Number) CoerceToFloat64(kind Kind) float64 { - switch kind { - case Int64Kind: - return float64(n.AsInt64()) - case Float64Kind: - return n.AsFloat64() - default: - // you get what you deserve - return 0 - } -} - -// - set - -// SetNumber sets the number to the passed number. Both should be of -// the same kind. -func (n *Number) SetNumber(nn Number) { - *n.AsRawPtr() = nn.AsRaw() -} - -// SetRaw sets the number to the passed raw value. Both number and the -// raw number should represent the same kind. -func (n *Number) SetRaw(r uint64) { - *n.AsRawPtr() = r -} - -// SetInt64 assumes that the number contains an int64 and sets it to -// the passed value. -func (n *Number) SetInt64(i int64) { - *n.AsInt64Ptr() = i -} - -// SetFloat64 assumes that the number contains a float64 and sets it -// to the passed value. -func (n *Number) SetFloat64(f float64) { - *n.AsFloat64Ptr() = f -} - -// - set atomic - -// SetNumberAtomic sets the number to the passed number -// atomically. Both should be of the same kind. -func (n *Number) SetNumberAtomic(nn Number) { - atomic.StoreUint64(n.AsRawPtr(), nn.AsRaw()) -} - -// SetRawAtomic sets the number to the passed raw value -// atomically. Both number and the raw number should represent the -// same kind. -func (n *Number) SetRawAtomic(r uint64) { - atomic.StoreUint64(n.AsRawPtr(), r) -} - -// SetInt64Atomic assumes that the number contains an int64 and sets -// it to the passed value atomically. -func (n *Number) SetInt64Atomic(i int64) { - atomic.StoreInt64(n.AsInt64Ptr(), i) -} - -// SetFloat64Atomic assumes that the number contains a float64 and -// sets it to the passed value atomically. -func (n *Number) SetFloat64Atomic(f float64) { - atomic.StoreUint64(n.AsRawPtr(), internal.Float64ToRaw(f)) -} - -// - swap - -// SwapNumber sets the number to the passed number and returns the old -// number. Both this number and the passed number should be of the -// same kind. -func (n *Number) SwapNumber(nn Number) Number { - old := *n - n.SetNumber(nn) - return old -} - -// SwapRaw sets the number to the passed raw value and returns the old -// raw value. Both number and the raw number should represent the same -// kind. -func (n *Number) SwapRaw(r uint64) uint64 { - old := n.AsRaw() - n.SetRaw(r) - return old -} - -// SwapInt64 assumes that the number contains an int64, sets it to the -// passed value and returns the old int64 value. -func (n *Number) SwapInt64(i int64) int64 { - old := n.AsInt64() - n.SetInt64(i) - return old -} - -// SwapFloat64 assumes that the number contains an float64, sets it to -// the passed value and returns the old float64 value. -func (n *Number) SwapFloat64(f float64) float64 { - old := n.AsFloat64() - n.SetFloat64(f) - return old -} - -// - swap atomic - -// SwapNumberAtomic sets the number to the passed number and returns -// the old number atomically. Both this number and the passed number -// should be of the same kind. -func (n *Number) SwapNumberAtomic(nn Number) Number { - return NewNumberFromRaw(atomic.SwapUint64(n.AsRawPtr(), nn.AsRaw())) -} - -// SwapRawAtomic sets the number to the passed raw value and returns -// the old raw value atomically. Both number and the raw number should -// represent the same kind. -func (n *Number) SwapRawAtomic(r uint64) uint64 { - return atomic.SwapUint64(n.AsRawPtr(), r) -} - -// SwapInt64Atomic assumes that the number contains an int64, sets it -// to the passed value and returns the old int64 value atomically. -func (n *Number) SwapInt64Atomic(i int64) int64 { - return atomic.SwapInt64(n.AsInt64Ptr(), i) -} - -// SwapFloat64Atomic assumes that the number contains an float64, sets -// it to the passed value and returns the old float64 value -// atomically. -func (n *Number) SwapFloat64Atomic(f float64) float64 { - return internal.RawToFloat64(atomic.SwapUint64(n.AsRawPtr(), internal.Float64ToRaw(f))) -} - -// - add - -// AddNumber assumes that this and the passed number are of the passed -// kind and adds the passed number to this number. -func (n *Number) AddNumber(kind Kind, nn Number) { - switch kind { - case Int64Kind: - n.AddInt64(nn.AsInt64()) - case Float64Kind: - n.AddFloat64(nn.AsFloat64()) - } -} - -// AddRaw assumes that this number and the passed raw value are of the -// passed kind and adds the passed raw value to this number. -func (n *Number) AddRaw(kind Kind, r uint64) { - n.AddNumber(kind, NewNumberFromRaw(r)) -} - -// AddInt64 assumes that the number contains an int64 and adds the -// passed int64 to it. -func (n *Number) AddInt64(i int64) { - *n.AsInt64Ptr() += i -} - -// AddFloat64 assumes that the number contains a float64 and adds the -// passed float64 to it. -func (n *Number) AddFloat64(f float64) { - *n.AsFloat64Ptr() += f -} - -// - add atomic - -// AddNumberAtomic assumes that this and the passed number are of the -// passed kind and adds the passed number to this number atomically. -func (n *Number) AddNumberAtomic(kind Kind, nn Number) { - switch kind { - case Int64Kind: - n.AddInt64Atomic(nn.AsInt64()) - case Float64Kind: - n.AddFloat64Atomic(nn.AsFloat64()) - } -} - -// AddRawAtomic assumes that this number and the passed raw value are -// of the passed kind and adds the passed raw value to this number -// atomically. -func (n *Number) AddRawAtomic(kind Kind, r uint64) { - n.AddNumberAtomic(kind, NewNumberFromRaw(r)) -} - -// AddInt64Atomic assumes that the number contains an int64 and adds -// the passed int64 to it atomically. -func (n *Number) AddInt64Atomic(i int64) { - atomic.AddInt64(n.AsInt64Ptr(), i) -} - -// AddFloat64Atomic assumes that the number contains a float64 and -// adds the passed float64 to it atomically. -func (n *Number) AddFloat64Atomic(f float64) { - for { - o := n.AsFloat64Atomic() - if n.CompareAndSwapFloat64(o, o+f) { - break - } - } -} - -// - compare and swap (atomic only) - -// CompareAndSwapNumber does the atomic CAS operation on this -// number. This number and passed old and new numbers should be of the -// same kind. -func (n *Number) CompareAndSwapNumber(on, nn Number) bool { - return atomic.CompareAndSwapUint64(n.AsRawPtr(), on.AsRaw(), nn.AsRaw()) -} - -// CompareAndSwapRaw does the atomic CAS operation on this -// number. This number and passed old and new raw values should be of -// the same kind. -func (n *Number) CompareAndSwapRaw(or, nr uint64) bool { - return atomic.CompareAndSwapUint64(n.AsRawPtr(), or, nr) -} - -// CompareAndSwapInt64 assumes that this number contains an int64 and -// does the atomic CAS operation on it. -func (n *Number) CompareAndSwapInt64(oi, ni int64) bool { - return atomic.CompareAndSwapInt64(n.AsInt64Ptr(), oi, ni) -} - -// CompareAndSwapFloat64 assumes that this number contains a float64 and -// does the atomic CAS operation on it. -func (n *Number) CompareAndSwapFloat64(of, nf float64) bool { - return atomic.CompareAndSwapUint64(n.AsRawPtr(), internal.Float64ToRaw(of), internal.Float64ToRaw(nf)) -} - -// - compare - -// CompareNumber compares two Numbers given their kind. Both numbers -// should have the same kind. This returns: -// 0 if the numbers are equal -// -1 if the subject `n` is less than the argument `nn` -// +1 if the subject `n` is greater than the argument `nn` -func (n *Number) CompareNumber(kind Kind, nn Number) int { - switch kind { - case Int64Kind: - return n.CompareInt64(nn.AsInt64()) - case Float64Kind: - return n.CompareFloat64(nn.AsFloat64()) - default: - // you get what you deserve - return 0 - } -} - -// CompareRaw compares two numbers, where one is input as a raw -// uint64, interpreting both values as a `kind` of number. -func (n *Number) CompareRaw(kind Kind, r uint64) int { - return n.CompareNumber(kind, NewNumberFromRaw(r)) -} - -// CompareInt64 assumes that the Number contains an int64 and performs -// a comparison between the value and the other value. It returns the -// typical result of the compare function: -1 if the value is less -// than the other, 0 if both are equal, 1 if the value is greater than -// the other. -func (n *Number) CompareInt64(i int64) int { - this := n.AsInt64() - if this < i { - return -1 - } else if this > i { - return 1 - } - return 0 -} - -// CompareFloat64 assumes that the Number contains a float64 and -// performs a comparison between the value and the other value. It -// returns the typical result of the compare function: -1 if the value -// is less than the other, 0 if both are equal, 1 if the value is -// greater than the other. -// -// Do not compare NaN values. -func (n *Number) CompareFloat64(f float64) int { - this := n.AsFloat64() - if this < f { - return -1 - } else if this > f { - return 1 - } - return 0 -} - -// - relations to zero - -// IsPositive returns true if the actual value is greater than zero. -func (n *Number) IsPositive(kind Kind) bool { - return n.compareWithZero(kind) > 0 -} - -// IsNegative returns true if the actual value is less than zero. -func (n *Number) IsNegative(kind Kind) bool { - return n.compareWithZero(kind) < 0 -} - -// IsZero returns true if the actual value is equal to zero. -func (n *Number) IsZero(kind Kind) bool { - return n.compareWithZero(kind) == 0 -} - -// - misc - -// Emit returns a string representation of the raw value of the -// Number. A %d is used for integral values, %f for floating point -// values. -func (n *Number) Emit(kind Kind) string { - switch kind { - case Int64Kind: - return fmt.Sprintf("%d", n.AsInt64()) - case Float64Kind: - return fmt.Sprintf("%f", n.AsFloat64()) - default: - return "" - } -} - -// AsInterface returns the number as an interface{}, typically used -// for Kind-correct JSON conversion. -func (n *Number) AsInterface(kind Kind) interface{} { - switch kind { - case Int64Kind: - return n.AsInt64() - case Float64Kind: - return n.AsFloat64() - default: - return math.NaN() - } -} - -// - private stuff - -func (n *Number) compareWithZero(kind Kind) int { - switch kind { - case Int64Kind: - return n.CompareInt64(0) - case Float64Kind: - return n.CompareFloat64(0.) - default: - // you get what you deserve - return 0 - } -} diff --git a/sdk/metric/number/number_test.go b/sdk/metric/number/number_test.go deleted file mode 100644 index e8d675c7fc3..00000000000 --- a/sdk/metric/number/number_test.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright The OpenTelemetry 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 number - -import ( - "math" - "testing" - "unsafe" - - "github.com/stretchr/testify/require" -) - -func TestNumber(t *testing.T) { - iNeg := NewInt64Number(-42) - iZero := NewInt64Number(0) - iPos := NewInt64Number(42) - i64Numbers := [3]Number{iNeg, iZero, iPos} - - for idx, i := range []int64{-42, 0, 42} { - n := i64Numbers[idx] - if got := n.AsInt64(); got != i { - t.Errorf("Number %#v (%s) int64 check failed, expected %d, got %d", n, n.Emit(Int64Kind), i, got) - } - } - - for _, n := range i64Numbers { - expected := unsafe.Pointer(&n) - got := unsafe.Pointer(n.AsRawPtr()) - if expected != got { - t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected) - } - } - - fNeg := NewFloat64Number(-42.) - fZero := NewFloat64Number(0.) - fPos := NewFloat64Number(42.) - f64Numbers := [3]Number{fNeg, fZero, fPos} - - for idx, f := range []float64{-42., 0., 42.} { - n := f64Numbers[idx] - if got := n.AsFloat64(); got != f { - t.Errorf("Number %#v (%s) float64 check failed, expected %f, got %f", n, n.Emit(Int64Kind), f, got) - } - } - - for _, n := range f64Numbers { - expected := unsafe.Pointer(&n) - got := unsafe.Pointer(n.AsRawPtr()) - if expected != got { - t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected) - } - } - - cmpsForNeg := [3]int{0, -1, -1} - cmpsForZero := [3]int{1, 0, -1} - cmpsForPos := [3]int{1, 1, 0} - - type testcase struct { - // n needs to be aligned for 64-bit atomic operations. - n Number - // nums needs to be aligned for 64-bit atomic operations. - nums [3]Number - kind Kind - pos bool - zero bool - neg bool - cmps [3]int - } - testcases := []testcase{ - { - n: iNeg, - kind: Int64Kind, - pos: false, - zero: false, - neg: true, - nums: i64Numbers, - cmps: cmpsForNeg, - }, - { - n: iZero, - kind: Int64Kind, - pos: false, - zero: true, - neg: false, - nums: i64Numbers, - cmps: cmpsForZero, - }, - { - n: iPos, - kind: Int64Kind, - pos: true, - zero: false, - neg: false, - nums: i64Numbers, - cmps: cmpsForPos, - }, - { - n: fNeg, - kind: Float64Kind, - pos: false, - zero: false, - neg: true, - nums: f64Numbers, - cmps: cmpsForNeg, - }, - { - n: fZero, - kind: Float64Kind, - pos: false, - zero: true, - neg: false, - nums: f64Numbers, - cmps: cmpsForZero, - }, - { - n: fPos, - kind: Float64Kind, - pos: true, - zero: false, - neg: false, - nums: f64Numbers, - cmps: cmpsForPos, - }, - } - for _, tt := range testcases { - if got := tt.n.IsPositive(tt.kind); got != tt.pos { - t.Errorf("Number %#v (%s) positive check failed, expected %v, got %v", tt.n, tt.n.Emit(tt.kind), tt.pos, got) - } - if got := tt.n.IsZero(tt.kind); got != tt.zero { - t.Errorf("Number %#v (%s) zero check failed, expected %v, got %v", tt.n, tt.n.Emit(tt.kind), tt.pos, got) - } - if got := tt.n.IsNegative(tt.kind); got != tt.neg { - t.Errorf("Number %#v (%s) negative check failed, expected %v, got %v", tt.n, tt.n.Emit(tt.kind), tt.pos, got) - } - for i := 0; i < 3; i++ { - if got := tt.n.CompareRaw(tt.kind, tt.nums[i].AsRaw()); got != tt.cmps[i] { - t.Errorf("Number %#v (%s) compare check with %#v (%s) failed, expected %d, got %d", tt.n, tt.n.Emit(tt.kind), tt.nums[i], tt.nums[i].Emit(tt.kind), tt.cmps[i], got) - } - } - } -} - -func TestNumberZero(t *testing.T) { - zero := Number(0) - zerof := NewFloat64Number(0) - zeroi := NewInt64Number(0) - - if zero != zerof || zero != zeroi { - t.Errorf("Invalid zero representations") - } -} - -func TestNumberAsInterface(t *testing.T) { - i64 := NewInt64Number(10) - f64 := NewFloat64Number(11.11) - require.Equal(t, int64(10), (&i64).AsInterface(Int64Kind).(int64)) - require.Equal(t, 11.11, (&f64).AsInterface(Float64Kind).(float64)) -} - -func TestNumberSignChange(t *testing.T) { - t.Run("Int64", func(t *testing.T) { - posInt := NewInt64Number(10) - negInt := NewInt64Number(-10) - - require.Equal(t, posInt, NewNumberSignChange(Int64Kind, negInt)) - require.Equal(t, negInt, NewNumberSignChange(Int64Kind, posInt)) - }) - - t.Run("Float64", func(t *testing.T) { - posFloat := NewFloat64Number(10) - negFloat := NewFloat64Number(-10) - - require.Equal(t, posFloat, NewNumberSignChange(Float64Kind, negFloat)) - require.Equal(t, negFloat, NewNumberSignChange(Float64Kind, posFloat)) - }) - - t.Run("Float64Zero", func(t *testing.T) { - posFloat := NewFloat64Number(0) - negFloat := NewFloat64Number(math.Copysign(0, -1)) - - require.Equal(t, posFloat, NewNumberSignChange(Float64Kind, negFloat)) - require.Equal(t, negFloat, NewNumberSignChange(Float64Kind, posFloat)) - }) - - t.Run("Float64Inf", func(t *testing.T) { - posFloat := NewFloat64Number(math.Inf(+1)) - negFloat := NewFloat64Number(math.Inf(-1)) - - require.Equal(t, posFloat, NewNumberSignChange(Float64Kind, negFloat)) - require.Equal(t, negFloat, NewNumberSignChange(Float64Kind, posFloat)) - }) - - t.Run("Float64NaN", func(t *testing.T) { - posFloat := NewFloat64Number(math.NaN()) - negFloat := NewFloat64Number(math.Copysign(math.NaN(), -1)) - - require.Equal(t, posFloat, NewNumberSignChange(Float64Kind, negFloat)) - require.Equal(t, negFloat, NewNumberSignChange(Float64Kind, posFloat)) - }) -}