Skip to content

Commit 7fabc89

Browse files
authored
Implement Exponential Histgoram metrics (#525)
Implement Exponential Histgoram metrics
1 parent 1535a05 commit 7fabc89

22 files changed

+1314
-237
lines changed

Sources/Exporters/OpenTelemetryProtocolCommon/metric/MetricsAdapter.swift

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,57 @@ public enum MetricsAdapter {
152152
protoMetric.histogram.aggregationTemporality = stableMetric.data.aggregationTemporality.convertToProtoEnum()
153153
protoMetric.histogram.dataPoints.append(protoDataPoint)
154154
case .ExponentialHistogram:
155-
// TODO: implement
156-
break
155+
guard let exponentialHistogramData = $0 as? ExponentialHistogramPointData else {
156+
break
157+
}
158+
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_ExponentialHistogramDataPoint()
159+
injectPointData(protoExponentialHistogramPoint: &protoDataPoint, pointData: exponentialHistogramData)
160+
protoDataPoint.scale = Int32(exponentialHistogramData.scale)
161+
protoDataPoint.sum = Double(exponentialHistogramData.sum)
162+
protoDataPoint.count = UInt64(exponentialHistogramData.count)
163+
protoDataPoint.zeroCount = UInt64(exponentialHistogramData.zeroCount)
164+
protoDataPoint.max = exponentialHistogramData.max
165+
protoDataPoint.min = exponentialHistogramData.min
166+
167+
var positiveBuckets = Opentelemetry_Proto_Metrics_V1_ExponentialHistogramDataPoint.Buckets()
168+
positiveBuckets.offset = Int32(exponentialHistogramData.positiveBuckets.offset)
169+
positiveBuckets.bucketCounts = exponentialHistogramData.positiveBuckets.bucketCounts.map { UInt64($0) }
170+
171+
var negativeBuckets = Opentelemetry_Proto_Metrics_V1_ExponentialHistogramDataPoint.Buckets()
172+
negativeBuckets.offset = Int32(exponentialHistogramData.negativeBuckets.offset)
173+
negativeBuckets.bucketCounts = exponentialHistogramData.negativeBuckets.bucketCounts.map { UInt64($0) }
174+
175+
protoDataPoint.positive = positiveBuckets
176+
protoDataPoint.negative = negativeBuckets
177+
178+
protoMetric.exponentialHistogram.aggregationTemporality = stableMetric.data.aggregationTemporality.convertToProtoEnum()
179+
protoMetric.exponentialHistogram.dataPoints.append(protoDataPoint)
157180
}
158181
}
159182
return protoMetric
160183
}
184+
185+
static func injectPointData(protoExponentialHistogramPoint protoPoint: inout Opentelemetry_Proto_Metrics_V1_ExponentialHistogramDataPoint, pointData: PointData) {
186+
protoPoint.timeUnixNano = pointData.endEpochNanos
187+
protoPoint.startTimeUnixNano = pointData.startEpochNanos
188+
189+
pointData.attributes.forEach {
190+
protoPoint.attributes.append(CommonAdapter.toProtoAttribute(key: $0.key, attributeValue: $0.value))
191+
}
192+
193+
pointData.exemplars.forEach {
194+
var protoExemplar = Opentelemetry_Proto_Metrics_V1_Exemplar()
195+
protoExemplar.timeUnixNano = $0.epochNanos
196+
197+
$0.filteredAttributes.forEach {
198+
protoExemplar.filteredAttributes.append(CommonAdapter.toProtoAttribute(key: $0.key, attributeValue: $0.value))
199+
}
200+
if let spanContext = $0.spanContext {
201+
protoExemplar.spanID = TraceProtoUtils.toProtoSpanId(spanId: spanContext.spanId)
202+
protoExemplar.traceID = TraceProtoUtils.toProtoTraceId(traceId: spanContext.traceId)
203+
}
204+
}
205+
}
161206

162207
static func injectPointData(protoHistogramPoint protoPoint: inout Opentelemetry_Proto_Metrics_V1_HistogramDataPoint, pointData: PointData) {
163208
protoPoint.timeUnixNano = pointData.endEpochNanos
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//
2+
// Copyright The OpenTelemetry Authors
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
import Foundation
7+
8+
class AdaptingCircularBufferCounter: NSCopying {
9+
func copy(with zone: NSZone? = nil) -> Any {
10+
let copy = AdaptingCircularBufferCounter(maxSize: maxSize)
11+
copy.startIndex = startIndex
12+
copy.endIndex = endIndex
13+
copy.baseIndex = baseIndex
14+
copy.backing = backing.copy() as! AdaptingIntegerArray
15+
return copy
16+
}
17+
18+
public private(set) var endIndex = Int.nullIndex
19+
public private(set) var startIndex = Int.nullIndex
20+
private var baseIndex = Int.nullIndex
21+
private var backing: AdaptingIntegerArray
22+
private let maxSize: Int
23+
24+
init(maxSize: Int) {
25+
backing = AdaptingIntegerArray(size: maxSize)
26+
self.maxSize = maxSize
27+
}
28+
29+
@discardableResult func increment(index: Int, delta: Int64) -> Bool{
30+
if baseIndex == Int.min {
31+
startIndex = index
32+
endIndex = index
33+
baseIndex = index
34+
backing.increment(index: 0, count: delta)
35+
return true
36+
}
37+
38+
if index > endIndex {
39+
if (index - startIndex + 1) > backing.length() {
40+
return false
41+
}
42+
endIndex = index
43+
} else if index < startIndex {
44+
if (endIndex - index + 1) > backing.length() {
45+
return false
46+
}
47+
self.startIndex = index
48+
}
49+
50+
let realIndex = toBufferIndex(index: index)
51+
backing.increment(index: realIndex, count: delta)
52+
return true
53+
}
54+
55+
func get(index: Int) -> Int64 {
56+
if (index < startIndex || index > endIndex) {
57+
return 0
58+
} else {
59+
return backing.get(index: toBufferIndex(index: index))
60+
}
61+
}
62+
63+
func isEmpty() -> Bool {
64+
return baseIndex == Int.nullIndex
65+
}
66+
67+
func getMaxSize() -> Int {
68+
return backing.length()
69+
}
70+
71+
func clear() {
72+
backing.clear()
73+
baseIndex = Int.nullIndex
74+
startIndex = Int.nullIndex
75+
endIndex = Int.nullIndex
76+
}
77+
78+
private func toBufferIndex(index: Int) -> Int {
79+
var result = index - baseIndex
80+
if (result >= backing.length()) {
81+
result -= backing.length()
82+
} else if (result < 0) {
83+
result += backing.length()
84+
}
85+
return result
86+
}
87+
}
88+
89+
extension Int {
90+
static let nullIndex = Int.min
91+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//
2+
// Copyright The OpenTelemetry Authors
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
import Foundation
7+
8+
class AdaptingIntegerArray: NSCopying {
9+
10+
func copy(with zone: NSZone? = nil) -> Any {
11+
let copy = AdaptingIntegerArray(size: size)
12+
copy.cellSize = cellSize
13+
switch (cellSize) {
14+
case .byte:
15+
copy.byteBacking = byteBacking
16+
case .short:
17+
copy.shortBacking = shortBacking
18+
case .int:
19+
copy.intBacking = intBacking
20+
case .long:
21+
copy.longBacking = longBacking
22+
}
23+
return copy
24+
}
25+
26+
var byteBacking: Array<Int8>?
27+
var shortBacking: Array<Int16>?
28+
var intBacking: Array<Int32>?
29+
var longBacking: Array<Int64>?
30+
var size: Int
31+
32+
enum ArrayCellSize {
33+
case byte
34+
case short
35+
case int
36+
case long
37+
}
38+
39+
var cellSize: ArrayCellSize
40+
41+
init(size: Int) {
42+
self.size = size
43+
cellSize = ArrayCellSize.byte
44+
byteBacking = Array<Int8>(repeating: Int8(0), count: size)
45+
}
46+
47+
func increment(index: Int, count: Int64) {
48+
49+
if cellSize == .byte, var byteBacking = self.byteBacking {
50+
let result = Int64(byteBacking[index]) + count
51+
if result > Int8.max {
52+
resizeToShort()
53+
increment(index: index, count: count)
54+
} else {
55+
byteBacking[index] = Int8(result)
56+
self.byteBacking = byteBacking
57+
}
58+
} else if cellSize == .short, var shortBacking = self.shortBacking {
59+
let result = Int64(shortBacking[index]) + count
60+
if result > Int16.max {
61+
resizeToInt()
62+
increment(index: index, count: count)
63+
} else {
64+
shortBacking[index] = Int16(result)
65+
self.shortBacking = shortBacking
66+
}
67+
} else if cellSize == .int, var intBacking = self.intBacking {
68+
let result = Int64(intBacking[index]) + count
69+
if result > Int32.max {
70+
resizeToLong()
71+
increment(index: index, count: count)
72+
} else {
73+
intBacking[index] = Int32(result)
74+
self.intBacking = intBacking
75+
}
76+
} else if cellSize == .long, var longBacking = self.longBacking {
77+
let result = longBacking[index] + count
78+
longBacking[index] = result
79+
self.longBacking = longBacking
80+
}
81+
}
82+
83+
func get(index: Int) -> Int64 {
84+
85+
if cellSize == .byte, let byteBacking = self.byteBacking, index < byteBacking.count {
86+
return Int64(byteBacking[index])
87+
} else if cellSize == .short, let shortBacking = self.shortBacking, index < shortBacking.count {
88+
return Int64(shortBacking[index])
89+
} else if cellSize == .int, let intBacking = self.intBacking, index < intBacking.count {
90+
return Int64(intBacking[index])
91+
} else if cellSize == .long, let longBacking = self.longBacking, index < longBacking.count {
92+
return longBacking[index]
93+
}
94+
95+
return Int64(0)
96+
}
97+
98+
func length() -> Int {
99+
var length = 0
100+
101+
if cellSize == .byte, let byteBacking = self.byteBacking {
102+
length = byteBacking.count
103+
} else if cellSize == .short, let shortBacking = self.shortBacking {
104+
length = shortBacking.count
105+
} else if cellSize == .int, let intBacking = self.intBacking {
106+
length = intBacking.count
107+
} else if cellSize == .long, let longBacking = self.longBacking {
108+
length = longBacking.count
109+
}
110+
111+
return length
112+
}
113+
114+
func clear() {
115+
switch (cellSize) {
116+
case .byte:
117+
byteBacking = Array(repeating: Int8(0), count: byteBacking?.count ?? 0)
118+
case .short:
119+
shortBacking = Array(repeating: Int16(0), count: shortBacking?.count ?? 0)
120+
case .int:
121+
intBacking = Array(repeating: Int32(0), count: intBacking?.count ?? 0)
122+
case .long:
123+
longBacking = Array(repeating: Int64(0), count: longBacking?.count ?? 0)
124+
}
125+
}
126+
127+
private func resizeToShort() {
128+
guard let byteBacking = byteBacking else { return }
129+
var tmpShortBacking: Array<Int16> = Array<Int16>(repeating: Int16(0), count: byteBacking.count)
130+
131+
for (index, value) in byteBacking.enumerated() {
132+
tmpShortBacking[index] = Int16(value)
133+
}
134+
cellSize = ArrayCellSize.short
135+
shortBacking = tmpShortBacking
136+
self.byteBacking = nil
137+
}
138+
139+
private func resizeToInt() {
140+
guard let shortBacking = shortBacking else { return }
141+
var tmpIntBacking: Array<Int32> = Array<Int32>(repeating: Int32(0), count: shortBacking.count)
142+
143+
for (index, value) in shortBacking.enumerated() {
144+
tmpIntBacking[index] = Int32(value)
145+
}
146+
cellSize = ArrayCellSize.int
147+
intBacking = tmpIntBacking
148+
self.shortBacking = nil
149+
}
150+
151+
private func resizeToLong() {
152+
guard let intBacking = intBacking else { return }
153+
var tmpLongBacking: Array<Int64> = Array<Int64>(repeating: Int64(0), count: intBacking.count)
154+
155+
for (index, value) in intBacking.enumerated() {
156+
tmpLongBacking[index] = Int64(value)
157+
}
158+
cellSize = ArrayCellSize.long
159+
longBacking = tmpLongBacking
160+
self.intBacking = nil
161+
}
162+
}
163+

Sources/OpenTelemetrySdk/Metrics/Stable/Aggregation/Aggregation.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ public enum Aggregations {
3030
ExplicitBucketHistogramAggregation(bucketBoundaries: buckets)
3131
}
3232

33-
static func base2ExponentialBucketHistogram() {
34-
// todo
33+
static func base2ExponentialBucketHistogram() -> Aggregation {
34+
Base2ExponentialHistogramAggregation.instance
3535
}
3636

37-
static func base2ExponentialBucketHistogram(maxBuckets: Int, maxScale: Int) {
38-
// todo
37+
static func base2ExponentialBucketHistogram(maxBuckets: Int, maxScale: Int) -> Aggregation {
38+
Base2ExponentialHistogramAggregation(maxBuckets: maxBuckets, maxScale: maxScale)
3939
}
4040
}

Sources/OpenTelemetrySdk/Metrics/Stable/Aggregation/Base2ExponentialBucketHistogramAggregation.swift

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)