Skip to content

Commit 62dfeac

Browse files
author
Ignacio Bonafonte
authored
Merge pull request #168 from nachoBonafonte/Add-addMetricProcessor-function-to-MetricProvider-
Improve usability of MetricProvider Add some consistency between the span provider and metric provider, so the metric provider can be configured without needing to use the constructor. Also add support for multiple metric exporters Fixes #167
2 parents ee11776 + 2adceff commit 62dfeac

File tree

8 files changed

+132
-72
lines changed

8 files changed

+132
-72
lines changed

Sources/OpenTelemetrySdk/Metrics/Configuration/MeterSharedState.swift

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

Sources/OpenTelemetrySdk/Metrics/Export/MetricExporter.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,48 @@ public enum MetricExporterResultCode {
1919
case success
2020
case failureNotRetryable
2121
case failureRetryable
22+
23+
/// Merges the current result code with other result code
24+
/// - Parameter newResultCode: the result code to merge with
25+
mutating func mergeResultCode(newResultCode: MetricExporterResultCode) {
26+
// If both results are success then return success.
27+
if self == .success, newResultCode == .success {
28+
self = .success
29+
return
30+
} else if self == .failureRetryable || self == .success,
31+
newResultCode == .failureRetryable || newResultCode == .success
32+
{
33+
self = .failureRetryable
34+
}
35+
self = .failureNotRetryable
36+
}
2237
}
2338

2439
public protocol MetricExporter {
2540
func export(metrics: [Metric], shouldCancel: (() -> Bool)?) -> MetricExporterResultCode
2641
}
2742

43+
/// Implementation of the SpanExporter that simply forwards all received spans to a list of
44+
/// SpanExporter.
45+
/// Can be used to export to multiple backends using the same SpanProcessor} like a impleSampledSpansProcessor
46+
/// or a BatchSampledSpansProcessor.
47+
struct MultiMetricExporter: MetricExporter {
48+
49+
var metricExporters: [MetricExporter]
50+
51+
init(metricExporters: [MetricExporter]) {
52+
self.metricExporters = metricExporters
53+
}
54+
55+
func export(metrics: [Metric], shouldCancel: (() -> Bool)?) -> MetricExporterResultCode {
56+
var currentResultCode = MetricExporterResultCode.success
57+
metricExporters.forEach {
58+
currentResultCode.mergeResultCode(newResultCode: $0.export(metrics: metrics, shouldCancel: shouldCancel))
59+
}
60+
return currentResultCode
61+
}
62+
}
63+
2864
struct NoopMetricExporter: MetricExporter {
2965
func export(metrics: [Metric], shouldCancel: (() -> Bool)?) -> MetricExporterResultCode {
3066
return .success

Sources/OpenTelemetrySdk/Metrics/Export/MetricProcessorSdk.swift

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,17 @@
1313
// limitations under the License.
1414
//
1515

16-
1716
import Foundation
1817

19-
public class MetricProcessorSdk : MetricProcessor {
20-
private let lock : Lock
21-
var metrics : [Metric]
22-
18+
public class MetricProcessorSdk: MetricProcessor {
19+
private let lock: Lock
20+
var metrics: [Metric]
21+
2322
public init() {
2423
metrics = [Metric]()
2524
lock = Lock()
2625
}
27-
26+
2827
/// Finish the current collection cycle and return the metrics it holds.
2928
/// This is called at the end of one collection cycle by the Controller.
3029
/// MetricProcessor can use this to clear its Metrics (in case of stateless).
@@ -36,7 +35,7 @@ public class MetricProcessorSdk : MetricProcessor {
3635
}
3736
return metrics
3837
}
39-
38+
4039
/// Process the metric. This method is called once every collection interval.
4140
/// - Parameters:
4241
/// - metric: the metric record.
@@ -47,8 +46,5 @@ public class MetricProcessorSdk : MetricProcessor {
4746
}
4847

4948
metrics.append(metric)
50-
5149
}
52-
53-
5450
}

Sources/OpenTelemetrySdk/Metrics/Export/PushMetricController.swift

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,31 @@
1515
import Foundation
1616

1717
class PushMetricController {
18-
public private(set) var pushInterval: TimeInterval
19-
var metricExporter: MetricExporter
20-
var metricProcessor: MetricProcessor
18+
var meterSharedState: MeterSharedState
2119
var meterProvider: MeterProviderSdk
2220

2321
let pushMetricQueue = DispatchQueue(label: "org.opentelemetry.PushMetricController.pushMetricQueue")
2422

25-
init(meterProvider: MeterProviderSdk, metricProcessor: MetricProcessor, metricExporter: MetricExporter, pushInterval: TimeInterval, shouldCancel: (() -> Bool)? = nil) {
23+
init(meterProvider: MeterProviderSdk, meterSharedState: MeterSharedState, shouldCancel: (() -> Bool)? = nil) {
2624
self.meterProvider = meterProvider
27-
self.metricProcessor = metricProcessor
28-
self.metricExporter = metricExporter
29-
self.pushInterval = pushInterval
30-
pushMetricQueue.asyncAfter(deadline: .now() + pushInterval) { [weak self] in
25+
self.meterSharedState = meterSharedState
26+
pushMetricQueue.asyncAfter(deadline: .now() + meterSharedState.metricPushInterval) { [weak self] in
3127
guard let self = self else {
3228
return
3329
}
3430
while !(shouldCancel?() ?? false) {
3531
autoreleasepool {
3632
let start = Date()
3733
let values = self.meterProvider.getMeters().values
38-
for index in values.indices {
39-
values[index].collect()
34+
values.forEach {
35+
$0.collect()
4036
}
4137

42-
let metricToExport = self.metricProcessor.finishCollectionCycle()
38+
let metricToExport = self.meterSharedState.metricProcessor.finishCollectionCycle()
4339

44-
_ = metricExporter.export(metrics: metricToExport, shouldCancel: shouldCancel)
40+
_ = meterSharedState.metricExporter.export(metrics: metricToExport, shouldCancel: shouldCancel)
4541
let timeInterval = Date().timeIntervalSince(start)
46-
let remainingWait = pushInterval - timeInterval
42+
let remainingWait = meterSharedState.metricPushInterval - timeInterval
4743
if remainingWait > 0 {
4844
usleep(UInt32(remainingWait * 1000000))
4945
}

Sources/OpenTelemetrySdk/Metrics/Configuration/MeterProviderSdk.swift renamed to Sources/OpenTelemetrySdk/Metrics/MeterProviderSdk.swift

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,35 @@ import OpenTelemetryApi
1818

1919
public class MeterProviderSdk: MeterProvider {
2020
private let lock = Lock()
21-
static public let defaultPushInterval: TimeInterval = 60
21+
public static let defaultPushInterval: TimeInterval = 60
2222

2323
var meterRegistry = [InstrumentationLibraryInfo: MeterSdk]()
2424

25-
var meterSharedState : MeterSharedState
25+
var meterSharedState: MeterSharedState
2626
var pushMetricController: PushMetricController!
2727
var defaultMeter: MeterSdk
2828

2929
public convenience init() {
3030
self.init(metricProcessor: NoopMetricProcessor(),
3131
metricExporter: NoopMetricExporter())
3232
}
33-
33+
3434
public init(metricProcessor: MetricProcessor,
3535
metricExporter: MetricExporter,
3636
metricPushInterval: TimeInterval = MeterProviderSdk.defaultPushInterval,
37-
resource: Resource = EnvVarResource.resource) {
38-
self.meterSharedState = MeterSharedState(metricProcessor:metricProcessor, metricPushInterval: metricPushInterval, resource: resource)
37+
resource: Resource = EnvVarResource.resource)
38+
{
39+
self.meterSharedState = MeterSharedState(metricProcessor: metricProcessor, metricPushInterval: metricPushInterval, metricExporter: metricExporter, resource: resource)
3940

4041
defaultMeter = MeterSdk(meterSharedState: self.meterSharedState, instrumentationLibraryInfo: InstrumentationLibraryInfo())
4142

4243
pushMetricController = PushMetricController(
4344
meterProvider: self,
44-
metricProcessor: metricProcessor,
45-
metricExporter: metricExporter,
46-
pushInterval: meterSharedState.metricPushInterval) {
47-
false
45+
meterSharedState: self.meterSharedState) {
46+
false
4847
}
4948
}
5049

51-
52-
5350
public func get(instrumentationName: String, instrumentationVersion: String? = nil) -> Meter {
5451
if instrumentationName.isEmpty {
5552
return defaultMeter
@@ -59,7 +56,7 @@ public class MeterProviderSdk: MeterProvider {
5956
defer {
6057
lock.unlock()
6158
}
62-
let instrumentationLibraryInfo = InstrumentationLibraryInfo(name: instrumentationName, version: instrumentationVersion)
59+
let instrumentationLibraryInfo = InstrumentationLibraryInfo(name: instrumentationName, version: instrumentationVersion)
6360
var meter: MeterSdk! = meterRegistry[instrumentationLibraryInfo]
6461
if meter == nil {
6562
meter = MeterSdk(meterSharedState: self.meterSharedState, instrumentationLibraryInfo: instrumentationLibraryInfo)
@@ -76,6 +73,30 @@ public class MeterProviderSdk: MeterProvider {
7673
return meterRegistry
7774
}
7875

76+
public func setMetricProcessor(_ metricProcessor: MetricProcessor) {
77+
pushMetricController.pushMetricQueue.sync {
78+
meterSharedState.metricProcessor = metricProcessor
79+
}
80+
}
81+
82+
public func addMetricExporter(_ metricExporter: MetricExporter) {
83+
pushMetricController.pushMetricQueue.sync {
84+
meterSharedState.addMetricExporter(metricExporter: metricExporter)
85+
}
86+
}
87+
88+
public func setMetricPushInterval(_ interval: TimeInterval) {
89+
pushMetricController.pushMetricQueue.sync {
90+
meterSharedState.metricPushInterval = interval
91+
}
92+
}
93+
94+
public func setResource(_ resource: Resource) {
95+
pushMetricController.pushMetricQueue.sync {
96+
meterSharedState.resource = resource
97+
}
98+
}
99+
79100
private static func createLibraryResourceLabels(name: String, version: String) -> [String: String] {
80101
var labels = ["name": name]
81102
if !version.isEmpty {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2020, OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
import Foundation
17+
18+
class MeterSharedState {
19+
/// Configures metric processor. (aka batcher).
20+
var metricProcessor: MetricProcessor
21+
/// Sets the push interval.
22+
var metricPushInterval: TimeInterval
23+
/// Sets the exporter
24+
var metricExporter: MetricExporter
25+
26+
var resource: Resource
27+
28+
init(metricProcessor: MetricProcessor, metricPushInterval: TimeInterval, metricExporter: MetricExporter, resource: Resource) {
29+
self.metricProcessor = metricProcessor
30+
self.metricPushInterval = metricPushInterval
31+
self.metricExporter = metricExporter
32+
self.resource = resource
33+
}
34+
35+
func addMetricExporter(metricExporter: MetricExporter) {
36+
if metricExporter is NoopMetricExporter {
37+
self.metricExporter = metricExporter
38+
} else if var multiMetricExporter = metricExporter as? MultiMetricExporter {
39+
multiMetricExporter.metricExporters.append(metricExporter)
40+
} else {
41+
self.metricExporter = MultiMetricExporter(metricExporters: [self.metricExporter, metricExporter])
42+
}
43+
}
44+
}

Sources/OpenTelemetrySdk/Trace/Export/SpanExporter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public enum SpanExporterResultCode {
4343

4444
/// Merges the current result code with other result code
4545
/// - Parameter newResultCode: the result code to merge with
46-
public mutating func mergeResultCode(newResultCode: SpanExporterResultCode) {
46+
mutating func mergeResultCode(newResultCode: SpanExporterResultCode) {
4747
// If both results are success then return success.
4848
if self == .success && newResultCode == .success {
4949
self = .success

Tests/OpenTelemetrySdkTests/Metrics/PushControllerTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ final class PushControllerTests: XCTestCase {
3838
// Setup 2 meters whose Collect will increment the collect count.
3939
var meter1CollectCount = 0
4040
var meter2CollectCount = 0
41-
let meterSharedState = MeterSharedState(metricProcessor: testProcessor, metricPushInterval: MeterProviderSdk.defaultPushInterval, resource: Resource())
41+
let meterSharedState = MeterSharedState(metricProcessor: testProcessor, metricPushInterval: controllerPushIntervalInSec, metricExporter: testExporter, resource: Resource())
4242

4343
let meterInstrumentationLibrary1 = InstrumentationLibraryInfo(name:"meter1")
4444

@@ -60,7 +60,7 @@ final class PushControllerTests: XCTestCase {
6060

6161
let pushInterval = controllerPushIntervalInSec
6262

63-
let controller = PushMetricController(meterProvider: meterProvider, metricProcessor: testProcessor, metricExporter: testExporter, pushInterval: pushInterval)
63+
let controller = PushMetricController(meterProvider: meterProvider, meterSharedState: meterSharedState)
6464

6565
// Validate that collect is called on Meter1, Meter2.
6666
validateMeterCollect(meterCollectCount: &meter1CollectCount, expectedMeterCollectCount: collectionCountExpectedMin, meterName: "meter1", timeout: maxWaitInSec)
@@ -70,7 +70,7 @@ final class PushControllerTests: XCTestCase {
7070
lock.withLockVoid {
7171
XCTAssertTrue(exportCalledCount >= collectionCountExpectedMin)
7272
}
73-
XCTAssertEqual(controller.pushInterval, pushInterval)
73+
XCTAssertEqual(controller.meterSharedState.metricPushInterval, pushInterval)
7474
}
7575

7676
func validateMeterCollect(meterCollectCount: inout Int, expectedMeterCollectCount: Int, meterName: String, timeout: TimeInterval) {

0 commit comments

Comments
 (0)