-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Exponential Histogram mapping functions for public use (#2502)
* Exponential Histogram mapping functions for public use * pr num * Apply suggestions from code review :+1: Co-authored-by: Anthony Mirabella <[email protected]> * mapping interface comments * missed add * Update CHANGELOG.md Co-authored-by: Anthony Mirabella <[email protected]> Co-authored-by: Tyler Yahn <[email protected]>
- Loading branch information
1 parent
8a7dcd9
commit 07ad32d
Showing
8 changed files
with
996 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# 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. |
117 changes: 117 additions & 0 deletions
117
sdk/metric/aggregator/exponential/mapping/exponent/exponent.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// 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) | ||
} |
Oops, something went wrong.