From 16a71dcfab39ff4a6e71a75daac2b6f5d8d2837d Mon Sep 17 00:00:00 2001 From: Batuhan Saka Date: Sun, 26 Jan 2025 21:45:24 +0100 Subject: [PATCH] feat(metrics): add synchronous gauge support for Double and Long types - Implemented DoubleGauge and LongGauge protocols for synchronous gauge recording - Added build() methods to DoubleGaugeBuilder and LongGaugeBuilder - Introduced DoubleGaugeSdk and LongGaugeSdk implementations - Updated unit tests to verify synchronous gauge functionality - Added example usage in sample code - Maintained backward compatibility with existing asynchronous gauge API This change provides developers with more flexibility in metric collection by supporting both synchronous and asynchronous gauge patterns, aligning with OpenTelemetry specifications. --- Examples/Stable Metric Sample/main.swift | 9 ++++++- .../Metrics/Stable/DefaultStableMeter.swift | 22 ++++++++++++++- .../Metrics/Stable/DoubleGauge.swift | 11 ++++++++ .../Metrics/Stable/DoubleGaugeBuilder.swift | 1 + .../Metrics/Stable/LongGauge.swift | 11 ++++++++ .../Metrics/Stable/LongGaugeBuilder.swift | 1 + .../Stable/DoubleGaugeBuilderSdk.swift | 4 +++ .../Metrics/Stable/DoubleGaugeSdk.swift | 26 ++++++++++++++++++ .../Metrics/Stable/LongGaugeBuilderSdk.swift | 5 ++++ .../Metrics/Stable/LongGaugeSdk.swift | 27 +++++++++++++++++++ .../Metrics/StableMetrics/BuilderTests.swift | 2 ++ .../StableMetrics/StableMeterSdkTests.swift | 23 +++++++++++++--- 12 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 Sources/OpenTelemetryApi/Metrics/Stable/DoubleGauge.swift create mode 100644 Sources/OpenTelemetryApi/Metrics/Stable/LongGauge.swift create mode 100644 Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeSdk.swift create mode 100644 Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeSdk.swift diff --git a/Examples/Stable Metric Sample/main.swift b/Examples/Stable Metric Sample/main.swift index 27cc343b..0e262233 100644 --- a/Examples/Stable Metric Sample/main.swift +++ b/Examples/Stable Metric Sample/main.swift @@ -69,6 +69,13 @@ basicConfiguration() // creating a new meter & instrument let meter = OpenTelemetry.instance.stableMeterProvider?.meterBuilder(name: "MyMeter").build() -var gaugeBuilder = meter!.gaugeBuilder(name: "Gauge").buildWithCallback({ ObservableDoubleMeasurement in +var gaugeBuilder = meter!.gaugeBuilder(name: "Gauge") + +// observable gauge +var observableGauge = gaugeBuilder.buildWithCallback({ ObservableDoubleMeasurement in ObservableDoubleMeasurement.record(value: 1.0, attributes: ["test": AttributeValue.bool(true)]) }) + +// gauge +var gauge = gaugeBuilder.build() +gauge.record(value: 1.0, attributes: ["test": AttributeValue.bool(true)]) diff --git a/Sources/OpenTelemetryApi/Metrics/Stable/DefaultStableMeter.swift b/Sources/OpenTelemetryApi/Metrics/Stable/DefaultStableMeter.swift index b9b6a3cd..8f366f00 100644 --- a/Sources/OpenTelemetryApi/Metrics/Stable/DefaultStableMeter.swift +++ b/Sources/OpenTelemetryApi/Metrics/Stable/DefaultStableMeter.swift @@ -54,17 +54,37 @@ public class DefaultStableMeter: StableMeter { NoopLongGaugeBuilder() } + func build() -> DoubleGauge { + NoopDoubleGauge() + } + func buildWithCallback(_ callback: @escaping (ObservableDoubleMeasurement) -> Void) -> ObservableDoubleGauge { NoopObservableDoubleGauge() } } - private class NoopLongGaugeBuilder: LongGaugeBuilder { + private class NoopLongGaugeBuilder: LongGaugeBuilder { + func build() -> LongGauge { + NoopLongGauge() + } + func buildWithCallback(_ callback: @escaping (ObservableLongMeasurement) -> Void) -> ObservableLongGauge { NoopObservableLongGauge() } } + private struct NoopDoubleGauge: DoubleGauge { + func record(value: Double) {} + + func record(value: Double, attributes: [String : AttributeValue]) {} + } + + private struct NoopLongGauge: LongGauge { + func record(value: Int) {} + + func record(value: Int, attributes: [String : AttributeValue]) {} + } + private struct NoopObservableLongGauge: ObservableLongGauge { func close() {} } diff --git a/Sources/OpenTelemetryApi/Metrics/Stable/DoubleGauge.swift b/Sources/OpenTelemetryApi/Metrics/Stable/DoubleGauge.swift new file mode 100644 index 00000000..ad1878d1 --- /dev/null +++ b/Sources/OpenTelemetryApi/Metrics/Stable/DoubleGauge.swift @@ -0,0 +1,11 @@ +// +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +public protocol DoubleGauge { + mutating func record(value: Double) + mutating func record(value: Double, attributes: [String: AttributeValue]) +} diff --git a/Sources/OpenTelemetryApi/Metrics/Stable/DoubleGaugeBuilder.swift b/Sources/OpenTelemetryApi/Metrics/Stable/DoubleGaugeBuilder.swift index 969ccb57..f52a6d5f 100644 --- a/Sources/OpenTelemetryApi/Metrics/Stable/DoubleGaugeBuilder.swift +++ b/Sources/OpenTelemetryApi/Metrics/Stable/DoubleGaugeBuilder.swift @@ -7,5 +7,6 @@ import Foundation public protocol DoubleGaugeBuilder: AnyObject { func ofLongs() -> LongGaugeBuilder + func build() -> DoubleGauge func buildWithCallback(_ callback: @escaping (ObservableDoubleMeasurement) -> Void) -> ObservableDoubleGauge } diff --git a/Sources/OpenTelemetryApi/Metrics/Stable/LongGauge.swift b/Sources/OpenTelemetryApi/Metrics/Stable/LongGauge.swift new file mode 100644 index 00000000..f9811c8b --- /dev/null +++ b/Sources/OpenTelemetryApi/Metrics/Stable/LongGauge.swift @@ -0,0 +1,11 @@ +// +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +public protocol LongGauge { + mutating func record(value: Int) + mutating func record(value: Int, attributes: [String: AttributeValue]) +} diff --git a/Sources/OpenTelemetryApi/Metrics/Stable/LongGaugeBuilder.swift b/Sources/OpenTelemetryApi/Metrics/Stable/LongGaugeBuilder.swift index 52587fad..769868bc 100644 --- a/Sources/OpenTelemetryApi/Metrics/Stable/LongGaugeBuilder.swift +++ b/Sources/OpenTelemetryApi/Metrics/Stable/LongGaugeBuilder.swift @@ -6,5 +6,6 @@ import Foundation public protocol LongGaugeBuilder: AnyObject { + func build() -> LongGauge func buildWithCallback(_ callback: @escaping (ObservableLongMeasurement) -> Void) -> ObservableLongGauge } diff --git a/Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeBuilderSdk.swift b/Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeBuilderSdk.swift index 20c35680..e390063f 100644 --- a/Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeBuilderSdk.swift +++ b/Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeBuilderSdk.swift @@ -32,6 +32,10 @@ public class DoubleGaugeBuilderSdk: DoubleGaugeBuilder, InstrumentBuilder { swapBuilder(LongGaugeBuilderSdk.init) } + public func build() -> DoubleGauge { + return buildSynchronousInstrument(DoubleGaugeSdk.init) + } + public func buildWithCallback(_ callback: @escaping (OpenTelemetryApi.ObservableDoubleMeasurement) -> Void) -> OpenTelemetryApi.ObservableDoubleGauge { registerDoubleAsynchronousInstrument(type: type, updater: callback) } diff --git a/Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeSdk.swift b/Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeSdk.swift new file mode 100644 index 00000000..64ad5dd5 --- /dev/null +++ b/Sources/OpenTelemetrySdk/Metrics/Stable/DoubleGaugeSdk.swift @@ -0,0 +1,26 @@ +// +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import OpenTelemetryApi + +public class DoubleGaugeSdk: DoubleGauge, Instrument { + + public var instrumentDescriptor: InstrumentDescriptor + private var storage: WritableMetricStorage + + init(descriptor: InstrumentDescriptor, storage: WritableMetricStorage) { + self.storage = storage + self.instrumentDescriptor = descriptor + } + + public func record(value: Double) { + record(value: value, attributes: [String: AttributeValue]()) + } + + public func record(value: Double, attributes: [String : OpenTelemetryApi.AttributeValue]) { + storage.recordDouble(value: value, attributes: attributes) + } +} diff --git a/Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeBuilderSdk.swift b/Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeBuilderSdk.swift index 41bc7fd3..7ebfee99 100644 --- a/Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeBuilderSdk.swift +++ b/Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeBuilderSdk.swift @@ -5,6 +5,7 @@ import Foundation import OpenTelemetryApi + public class LongGaugeBuilderSdk: LongGaugeBuilder, InstrumentBuilder { var meterProviderSharedState: MeterProviderSharedState @@ -29,6 +30,10 @@ public class LongGaugeBuilderSdk: LongGaugeBuilder, InstrumentBuilder { self.meterProviderSharedState = meterProviderSharedState } + public func build() -> OpenTelemetryApi.LongGauge { + return buildSynchronousInstrument(LongGaugeSdk.init) + } + public func buildWithCallback(_ callback: @escaping (OpenTelemetryApi.ObservableLongMeasurement) -> Void) -> OpenTelemetryApi.ObservableLongGauge { registerLongAsynchronousInstrument(type: type, updater: callback) } diff --git a/Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeSdk.swift b/Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeSdk.swift new file mode 100644 index 00000000..75a87d6b --- /dev/null +++ b/Sources/OpenTelemetrySdk/Metrics/Stable/LongGaugeSdk.swift @@ -0,0 +1,27 @@ +// +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import OpenTelemetryApi + +public class LongGaugeSdk: LongGauge, Instrument { + + public var instrumentDescriptor: InstrumentDescriptor + private var storage: WritableMetricStorage + + init(descriptor: InstrumentDescriptor, storage: WritableMetricStorage) { + self.storage = storage + self.instrumentDescriptor = descriptor + } + + public func record(value: Int) { + record(value: value, attributes: [String: AttributeValue]()) + } + + public func record(value: Int, attributes: [String: OpenTelemetryApi.AttributeValue]) { + storage.recordLong(value: value, attributes: attributes) + } + +} diff --git a/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/BuilderTests.swift b/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/BuilderTests.swift index ac222371..24f4340d 100644 --- a/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/BuilderTests.swift +++ b/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/BuilderTests.swift @@ -14,7 +14,9 @@ class BuilderTests : XCTestCase { XCTAssertTrue(type(of:meter) == DefaultStableMeter.self) XCTAssertNotNil(meter.counterBuilder(name: "counter").ofDoubles().build()) XCTAssertNotNil(meter.counterBuilder(name: "counter").build()) + XCTAssertNotNil(meter.gaugeBuilder(name: "gauge").build()) XCTAssertNotNil(meter.gaugeBuilder(name: "gauge").buildWithCallback({ _ in })) + XCTAssertNotNil(meter.gaugeBuilder(name: "gauge").ofLongs().build()) XCTAssertNotNil(meter.gaugeBuilder(name: "gauge").ofLongs().buildWithCallback({ _ in })) XCTAssertNotNil(meter.histogramBuilder(name: "histogram").build()) XCTAssertNotNil(meter.histogramBuilder(name: "histogram").ofLongs().build()) diff --git a/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/StableMeterSdkTests.swift b/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/StableMeterSdkTests.swift index e5275eba..b512668e 100644 --- a/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/StableMeterSdkTests.swift +++ b/Tests/OpenTelemetrySdkTests/Metrics/StableMetrics/StableMeterSdkTests.swift @@ -40,14 +40,20 @@ class StableMeterProviderTests : XCTestCase { var counter = meterSdk.counterBuilder(name: "counter").build() - var _ = meterSdk.gaugeBuilder(name: "gauge").buildWithCallback { measurement in + var _ = meterSdk.gaugeBuilder(name: "observable_gauge").buildWithCallback { measurement in measurement.record(value: 1.0) } var histogram = meterSdk.histogramBuilder(name: "histogram").build() var upDown = meterSdk.upDownCounterBuilder(name: "upDown").build() - + + var doubleGauge = meterSdk.gaugeBuilder(name: "double_gauge").build() + doubleGauge.record(value: 5.0) + + var longGauge = meterSdk.gaugeBuilder(name: "long_gauge").ofLongs().build() + longGauge.record(value: 10) + counter.add(value: 1) -// histogram.record(value: 2.0) + histogram.record(value: 2.0) upDown.add(value: 100) let metrics = mockExporter.waitForExport() @@ -56,7 +62,16 @@ class StableMeterProviderTests : XCTestCase { metric.name == "counter" && (metric.data.points[0] as! LongPointData).value == 1 })) XCTAssertTrue(metrics.contains(where: { metric in - metric.name == "gauge" && (metric.data.points[0] as! DoublePointData).value == 1.0 + metric.name == "observable_gauge" && (metric.data.points[0] as! DoublePointData).value == 1.0 + })) + XCTAssertTrue(metrics.contains(where: { metric in + metric.name == "double_gauge" && (metric.data.points[0] as! DoublePointData).value == 5.0 + })) + XCTAssertTrue(metrics.contains(where: { metric in + metric.name == "long_gauge" && (metric.data.points[0] as! LongPointData).value == 10 + })) + XCTAssertTrue(metrics.contains(where: { metric in + metric.name == "histogram" && (metric.getHistogramData()[0].min == 2.0 && metric.getHistogramData()[0].max == 2.0) })) XCTAssertTrue(metrics.contains(where: { metric in metric.name == "upDown" && (metric.data.points[0] as! LongPointData).value == 100