-
Notifications
You must be signed in to change notification settings - Fork 158
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Histogram metrics (#244)
- Aggregator for histogram with set boundaries to sort into buckets - OTLP exporter support - Stub exporting support for Datadog and Prometheus - Added unit tests for histogram aggregator - Use cumulative aggregation for histogram data - Use a default boundaries for histograms, optionally pass explicit boundaries
- Loading branch information
1 parent
7387d58
commit eec1f8f
Showing
17 changed files
with
513 additions
and
1 deletion.
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
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
17 changes: 17 additions & 0 deletions
17
Sources/OpenTelemetryApi/Metrics/BoundHistogramMetric.swift
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,17 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import Foundation | ||
|
||
/// Bound histogram metric | ||
open class BoundHistogramMetric<T> { | ||
public init(explicitBoundaries: Array<T>? = nil) {} | ||
|
||
/// Record the given value to the bound histogram metric. | ||
/// - Parameters: | ||
/// - value: the histogram to be recorded. | ||
open func record(value: T) { | ||
} | ||
} |
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,71 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import Foundation | ||
|
||
/// Measure instrument. | ||
public protocol HistogramMetric { | ||
associatedtype T | ||
/// Gets the bound histogram metric with given labelset. | ||
/// - Parameters: | ||
/// - labelset: The labelset from which bound instrument should be constructed. | ||
/// - Returns: The bound histogram metric. | ||
|
||
func bind(labelset: LabelSet) -> BoundHistogramMetric<T> | ||
|
||
/// Gets the bound histogram metric with given labelset. | ||
/// - Parameters: | ||
/// - labels: The labels or dimensions associated with this value. | ||
/// - Returns: The bound histogram metric. | ||
func bind(labels: [String: String]) -> BoundHistogramMetric<T> | ||
} | ||
|
||
public extension HistogramMetric { | ||
/// Records a histogram. | ||
/// - Parameters: | ||
/// - value: value to record. | ||
/// - labelset: The labelset associated with this value. | ||
func record(value: T, labelset: LabelSet) { | ||
bind(labelset: labelset).record(value: value) | ||
} | ||
|
||
/// Records a histogram. | ||
/// - Parameters: | ||
/// - value: value to record. | ||
/// - labels: The labels or dimensions associated with this value. | ||
func record(value: T, labels: [String: String]) { | ||
bind(labels: labels).record(value: value) | ||
} | ||
} | ||
|
||
public struct AnyHistogramMetric<T>: HistogramMetric { | ||
private let _bindLabelSet: (LabelSet) -> BoundHistogramMetric<T> | ||
private let _bindLabels: ([String: String]) -> BoundHistogramMetric<T> | ||
|
||
public init<U: HistogramMetric>(_ histogram: U) where U.T == T { | ||
_bindLabelSet = histogram.bind(labelset:) | ||
_bindLabels = histogram.bind(labels:) | ||
} | ||
|
||
public func bind(labelset: LabelSet) -> BoundHistogramMetric<T> { | ||
_bindLabelSet(labelset) | ||
} | ||
|
||
public func bind(labels: [String: String]) -> BoundHistogramMetric<T> { | ||
_bindLabels(labels) | ||
} | ||
} | ||
|
||
public struct NoopHistogramMetric<T>: HistogramMetric { | ||
public init() {} | ||
|
||
public func bind(labelset: LabelSet) -> BoundHistogramMetric<T> { | ||
BoundHistogramMetric<T>() | ||
} | ||
|
||
public func bind(labels: [String: String]) -> BoundHistogramMetric<T> { | ||
BoundHistogramMetric<T>() | ||
} | ||
} |
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
104 changes: 104 additions & 0 deletions
104
Sources/OpenTelemetrySdk/Metrics/Aggregators/HistogramAggregator.swift
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,104 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import Foundation | ||
|
||
/// Aggregator which calculates histogram (bucket distribution, sum, count) from measures. | ||
public class HistogramAggregator<T: SignedNumeric & Comparable>: Aggregator<T> { | ||
fileprivate var histogram: Histogram<T> | ||
fileprivate var pointCheck: Histogram<T> | ||
fileprivate var boundaries: Array<T> | ||
|
||
private let lock = Lock() | ||
private let defaultBoundaries: Array<T> = [5, 10, 25, 50, 75, 100, 250, 500, 750, 1_000, 2_500, 5_000, 7_500, | ||
10_000] | ||
|
||
public init(explicitBoundaries: Array<T>? = nil) throws { | ||
if let explicitBoundaries = explicitBoundaries, explicitBoundaries.count > 0 { | ||
// we need to an ordered set to be able to correctly compute count for each | ||
// boundary since we'll iterate on each in order. | ||
self.boundaries = explicitBoundaries.sorted { $0 < $1 } | ||
} else { | ||
self.boundaries = defaultBoundaries | ||
} | ||
|
||
self.histogram = Histogram<T>(boundaries: self.boundaries) | ||
self.pointCheck = Histogram<T>(boundaries: self.boundaries) | ||
} | ||
|
||
override public func update(value: T) { | ||
lock.withLockVoid { | ||
self.histogram.count += 1 | ||
self.histogram.sum += value | ||
|
||
for i in 0..<self.boundaries.count { | ||
if value < self.boundaries[i] { | ||
self.histogram.buckets.counts[i] += 1 | ||
return | ||
} | ||
} | ||
// value is above all observed boundaries | ||
self.histogram.buckets.counts[self.boundaries.count] += 1 | ||
} | ||
} | ||
|
||
override public func checkpoint() { | ||
lock.withLockVoid { | ||
super.checkpoint() | ||
pointCheck = histogram | ||
histogram = Histogram<T>(boundaries: self.boundaries) | ||
} | ||
} | ||
|
||
public override func toMetricData() -> MetricData { | ||
return HistogramData<T>(startTimestamp: lastStart, | ||
timestamp: lastEnd, | ||
buckets: pointCheck.buckets, | ||
count: pointCheck.count, | ||
sum: pointCheck.sum) | ||
} | ||
|
||
public override func getAggregationType() -> AggregationType { | ||
if T.self == Double.Type.self { | ||
return .doubleHistogram | ||
} else { | ||
return .intHistogram | ||
} | ||
} | ||
} | ||
|
||
private struct Histogram<T> where T: SignedNumeric { | ||
/* | ||
* Buckets are implemented using two different arrays: | ||
* - boundaries: contains every finite bucket boundary, which are inclusive lower bounds | ||
* - counts: contains event counts for each bucket | ||
* | ||
* Note that we'll always have n+1 buckets, where n is the number of boundaries. | ||
* This is because we need to count events that are below the lowest boundary. | ||
* | ||
* Example: if we measure the values: [5, 30, 5, 40, 5, 15, 15, 15, 25] | ||
* with the boundaries [ 10, 20, 30 ], we will have the following state: | ||
* | ||
* buckets: { | ||
* boundaries: [10, 20, 30], | ||
* counts: [3, 3, 1, 2], | ||
* } | ||
*/ | ||
var buckets: ( | ||
boundaries: Array<T>, | ||
counts: Array<Int> | ||
) | ||
var sum: T | ||
var count: Int | ||
|
||
init(boundaries: Array<T>) { | ||
sum = 0 | ||
count = 0 | ||
buckets = ( | ||
boundaries: boundaries, | ||
counts: Array(repeating: 0, count: boundaries.count + 1) | ||
) | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
Sources/OpenTelemetrySdk/Metrics/BoundHistogramMetricSdk.swift
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,24 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import Foundation | ||
import OpenTelemetryApi | ||
|
||
internal class BoundHistogramMetricSdk<T: SignedNumeric & Comparable>: BoundHistogramMetricSdkBase<T> { | ||
private var histogramAggregator: HistogramAggregator<T> | ||
|
||
override init(explicitBoundaries: Array<T>? = nil) { | ||
self.histogramAggregator = try! HistogramAggregator(explicitBoundaries: explicitBoundaries) | ||
super.init(explicitBoundaries: explicitBoundaries) | ||
} | ||
|
||
override func record(value: T) { | ||
histogramAggregator.update(value: value) | ||
} | ||
|
||
override func getAggregator() -> HistogramAggregator<T> { | ||
return histogramAggregator | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
Sources/OpenTelemetrySdk/Metrics/BoundHistogramMetricSdkBase.swift
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,17 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import Foundation | ||
import OpenTelemetryApi | ||
|
||
class BoundHistogramMetricSdkBase<T>: BoundHistogramMetric<T> { | ||
override init(explicitBoundaries: Array<T>? = nil) { | ||
super.init(explicitBoundaries: explicitBoundaries) | ||
} | ||
|
||
func getAggregator() -> Aggregator<T> { | ||
fatalError() | ||
} | ||
} |
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
Oops, something went wrong.