Skip to content

Commit

Permalink
Merge branch 'main' into mdalmamun/add-batch-span-processor-metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
mamunto authored Nov 22, 2024
2 parents d968d8a + e9c6ae6 commit be3aff9
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 41 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/BuildAndTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: [push, pull_request, workflow_dispatch]

jobs:
macOS:
runs-on: macos-14
runs-on: macos-15
steps:
- uses: actions/checkout@v2
- name: Build and Test for macOS
Expand All @@ -16,7 +16,7 @@ jobs:
xcrun llvm-cov export -ignore-filename-regex="pb\.swift|grpc\.swift" -format="lcov" .build/debug/opentelemetry-swiftPackageTests.xctest/Contents/MacOS/opentelemetry-swiftPackageTests -instr-profile .build/debug/codecov/default.profdata > .build/debug/codecov/coverage_report.lcov
./codecov -f .build/debug/codecov/coverage_report.lcov
iOS:
runs-on: macos-14
runs-on: macos-15
steps:
- uses: actions/checkout@v2
- name: Install Homebrew kegs
Expand All @@ -26,7 +26,7 @@ jobs:
- name: Test for iOS
run: make test-without-building-ios
tvOS:
runs-on: macos-14
runs-on: macos-15
steps:
- uses: actions/checkout@v2
- name: Install Homebrew kegs
Expand All @@ -36,7 +36,7 @@ jobs:
- name: Test for tvOS
run: make test-without-building-tvos
watchOS:
runs-on: macos-14
runs-on: macos-15
steps:
- uses: actions/checkout@v2
- name: Install Homebrew kegs
Expand All @@ -53,4 +53,4 @@ jobs:
- name: Build tests for Linux
run: swift build --build-tests
- name: Run tests for Linux
run: swift test
run: swift test
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ PROJECT_NAME="opentelemetry-swift-Package"

XCODEBUILD_OPTIONS_IOS=\
-configuration Debug \
-destination platform='iOS Simulator,name=iPhone 15,OS=latest' \
-destination platform='iOS Simulator,name=iPhone 16,OS=18.2' \
-scheme $(PROJECT_NAME) \
-test-iterations 5 \
-retry-tests-on-failure \
Expand All @@ -18,7 +18,7 @@ XCODEBUILD_OPTIONS_TVOS=\

XCODEBUILD_OPTIONS_WATCHOS=\
-configuration Debug \
-destination platform='watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest' \
-destination platform='watchOS Simulator,name=Apple Watch Series 10 (46mm),OS=11.2' \
-scheme $(PROJECT_NAME) \
-test-iterations 5 \
-retry-tests-on-failure \
Expand Down Expand Up @@ -50,7 +50,7 @@ build-for-testing-tvos:

.PHONY: build-for-testing-watchos
build-for-testing-watchos:
set -o pipefail && xcodebuild $(XCODEBUILD_OPTIONS_WATCHOS) build-for-testing | xcbeautify
set -o pipefail && xcodebuild OTHER_LDFLAGS="$(OTHER_LDFLAGS) -fprofile-instr-generate" $(XCODEBUILD_OPTIONS_WATCHOS) build-for-testing | xcbeautify

.PHONY: test-ios
test-ios:
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ let package = Package(
path: "Sources/Exporters/OpenTelemetryProtocolCommon"),
.target(name: "OpenTelemetryProtocolExporterHttp",
dependencies: ["OpenTelemetrySdk",
"OpenTelemetryProtocolExporterCommon"],
"OpenTelemetryProtocolExporterCommon",
"DataCompression"],
path: "Sources/Exporters/OpenTelemetryProtocolHttp"),
.target(name: "OpenTelemetryProtocolExporterGrpc",
dependencies: ["OpenTelemetrySdk",
Expand Down
3 changes: 2 additions & 1 deletion Package@swift-5.6.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ let package = Package(
path: "Sources/Exporters/OpenTelemetryProtocolCommon"),
.target(name: "OpenTelemetryProtocolExporterHttp",
dependencies: ["OpenTelemetrySdk",
"OpenTelemetryProtocolExporterCommon"],
"OpenTelemetryProtocolExporterCommon",
"DataCompression"],
path: "Sources/Exporters/OpenTelemetryProtocolHttp"),
.target(name: "OpenTelemetryProtocolExporterGrpc",
dependencies: ["OpenTelemetrySdk",
Expand Down
3 changes: 2 additions & 1 deletion Package@swift-5.9.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ let package = Package(
path: "Sources/Exporters/OpenTelemetryProtocolCommon"),
.target(name: "OpenTelemetryProtocolExporterHttp",
dependencies: ["OpenTelemetrySdk",
"OpenTelemetryProtocolExporterCommon"],
"OpenTelemetryProtocolExporterCommon",
"DataCompression"],
path: "Sources/Exporters/OpenTelemetryProtocolHttp"),
.target(name: "OpenTelemetryProtocolExporterGrpc",
dependencies: ["OpenTelemetrySdk",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import Foundation
import OpenTelemetrySdk
import OpenTelemetryApi
import OpenTelemetryProtocolExporterCommon
#if canImport(FoundationNetworking)
import FoundationNetworking
Expand All @@ -15,11 +16,12 @@ public func defaultStableOtlpHTTPMetricsEndpoint() -> URL {
}

public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMetricExporter {
var aggregationTemporalitySelector: AggregationTemporalitySelector
var defaultAggregationSelector: DefaultAggregationSelector
var aggregationTemporalitySelector: AggregationTemporalitySelector
var defaultAggregationSelector: DefaultAggregationSelector

var pendingMetrics: [StableMetricData] = []
private let exporterLock = Lock()
var pendingMetrics: [StableMetricData] = []
private let exporterLock = Lock()
private var exporterMetrics: ExporterMetrics?

// MARK: - Init

Expand All @@ -31,6 +33,40 @@ public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMet
super.init(endpoint: endpoint, config: config, useSession: useSession, envVarHeaders: envVarHeaders)
}

/// A `convenience` constructor to provide support for exporter metric using`StableMeterProvider` type
/// - Parameters:
/// - endpoint: Exporter endpoint injected as dependency
/// - config: Exporter configuration including type of exporter
/// - meterProvider: Injected `StableMeterProvider` for metric
/// - aggregationTemporalitySelector: aggregator
/// - defaultAggregationSelector: default aggregator
/// - useSession: Overridden `URLSession` if any
/// - envVarHeaders: Extra header key-values
convenience public init(
endpoint: URL,
config: OtlpConfiguration = OtlpConfiguration(),
meterProvider: StableMeterProvider,
aggregationTemporalitySelector: AggregationTemporalitySelector = AggregationTemporality.alwaysCumulative(),
defaultAggregationSelector: DefaultAggregationSelector = AggregationSelector.instance,
useSession: URLSession? = nil,
envVarHeaders: [(String, String)]? = EnvVarHeaders.attributes) {
self.init(
endpoint: endpoint,
config: config,
aggregationTemporalitySelector: aggregationTemporalitySelector,
defaultAggregationSelector: defaultAggregationSelector,
useSession: useSession,
envVarHeaders: envVarHeaders
)
self.exporterMetrics = ExporterMetrics(
type: "otlp",
meterProvider: meterProvider,
exporterName: "metric",
transportName: config.exportAsJson ?
ExporterMetrics.TransporterType.httpJson :
ExporterMetrics.TransporterType.grpc
)
}

// MARK: - StableMetricsExporter

Expand All @@ -44,14 +80,16 @@ public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMet
let body = Opentelemetry_Proto_Collector_Metrics_V1_ExportMetricsServiceRequest.with {
$0.resourceMetrics = MetricsAdapter.toProtoResourceMetrics(stableMetricData: sendingMetrics)
}

self.exporterMetrics?.addSeen(value: sendingMetrics.count)
var request = createRequest(body: body, endpoint: endpoint)
request.timeoutInterval = min(TimeInterval.greatestFiniteMagnitude, config.timeout)
httpClient.send(request: request) { [weak self] result in
switch result {
case .success(_):
self?.exporterMetrics?.addSuccess(value: sendingMetrics.count)
break
case .failure(let error):
self?.exporterMetrics?.addFailed(value: sendingMetrics.count)
self?.exporterLock.withLockVoid {
self?.pendingMetrics.append(contentsOf: sendingMetrics)
}
Expand All @@ -75,11 +113,13 @@ public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMet
let semaphore = DispatchSemaphore(value: 0)
var request = createRequest(body: body, endpoint: endpoint)
request.timeoutInterval = min(TimeInterval.greatestFiniteMagnitude, config.timeout)
httpClient.send(request: request) { result in
httpClient.send(request: request) { [weak self] result in
switch result {
case .success(_):
self?.exporterMetrics?.addSuccess(value: pendingMetrics.count)
break
case .failure(let error):
self?.exporterMetrics?.addFailed(value: pendingMetrics.count)
print(error)
exporterResult = .failure
}
Expand Down
30 changes: 16 additions & 14 deletions Sources/Instrumentation/URLSession/URLSessionInstrumentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@ struct NetworkRequestState {
private var idKey: Void?

public class URLSessionInstrumentation {
private var requestMap = [String: NetworkRequestState]()

var configuration: URLSessionInstrumentationConfiguration

private let queue = DispatchQueue(label: "io.opentelemetry.ddnetworkinstrumentation")

static var instrumentedKey = "io.opentelemetry.instrumentedCall"

static let avAssetDownloadTask: AnyClass? = NSClassFromString("__NSCFBackgroundAVAssetDownloadTask")

private var requestMap = [String: NetworkRequestState]()

var configuration: URLSessionInstrumentationConfiguration

private let queue = DispatchQueue(label: "io.opentelemetry.ddnetworkinstrumentation")

static var instrumentedKey = "io.opentelemetry.instrumentedCall"

static let AVTaskClassList : [AnyClass] = {
["__NSCFBackgroundAVAggregateAssetDownloadTask",
"__NSCFBackgroundAVAssetDownloadTask",
"__NSCFBackgroundAVAggregateAssetDownloadTaskNoChildTask" ]
.compactMap { NSClassFromString($0) }
}()

public private(set) var tracer: Tracer

public var startedRequestSpans: [Span] {
Expand Down Expand Up @@ -592,10 +597,7 @@ public class URLSessionInstrumentation {

private func urlSessionTaskWillResume(_ task: URLSessionTask) {
// AV Asset Tasks cannot be auto instrumented, they dont include request attributes, skip them
if let avAssetTaskClass = Self.avAssetDownloadTask,
task.isKind(of: avAssetTaskClass) {
return
}
guard !Self.AVTaskClassList.contains(where: {task.isKind(of:$0)}) else { return }

// We cannot instrument async background tasks because they crash if you assign a delegate
if #available(OSX 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public struct URLSessionInstrumentationConfiguration {
createdRequest: ((URLRequest, Span) -> Void)? = nil,
receivedResponse: ((URLResponse, DataOrFile?, Span) -> Void)? = nil,
receivedError: ((Error, DataOrFile?, HTTPStatus, Span) -> Void)? = nil,
delegateClassesToInstrument: [AnyClass]? = nil)
delegateClassesToInstrument: [AnyClass]? = nil,
defaultBaggageProvider: (() -> (Baggage)?)? = nil)
{
self.shouldRecordPayload = shouldRecordPayload
self.shouldInstrument = shouldInstrument
Expand All @@ -36,6 +37,7 @@ public struct URLSessionInstrumentationConfiguration {
self.receivedResponse = receivedResponse
self.receivedError = receivedError
self.delegateClassesToInstrument = delegateClassesToInstrument
self.defaultBaggageProvider = defaultBaggageProvider
}

// Instrumentation Callbacks
Expand Down Expand Up @@ -73,4 +75,14 @@ public struct URLSessionInstrumentationConfiguration {

/// The array of URLSession delegate classes that will be instrumented by the library, will autodetect if nil is passed.
public var delegateClassesToInstrument: [AnyClass]?

/// Implement this callback to provide a default baggage instance for all instrumented requests.
/// The provided baggage is merged with active baggage (if any) to create a combined baggage.
/// The combined baggage is then injected into request headers using the configured `TextMapBaggagePropagator`.
/// This allows consistent propagation across requests, regardless of the active context.
///
/// Note: The injected baggage depends on the propagator in use (e.g., W3C or custom).
/// Returns: A `Baggage` instance or `nil` if no default baggage is needed.
public let defaultBaggageProvider: (() -> (Baggage)?)?

}
25 changes: 21 additions & 4 deletions Sources/Instrumentation/URLSession/URLSessionLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,22 @@ class URLSessionLogger {
var instrumentedRequest = request
objc_setAssociatedObject(instrumentedRequest, URLSessionInstrumentation.instrumentedKey, true, .OBJC_ASSOCIATION_COPY_NONATOMIC)
let propagators = OpenTelemetry.instance.propagators
var traceHeaders = tracePropagationHTTPHeaders(span: span, textMapPropagator: propagators.textMapPropagator, textMapBaggagePropagator: propagators.textMapBaggagePropagator)

let defaultBaggage = instrumentation.configuration.defaultBaggageProvider?()

var traceHeaders = tracePropagationHTTPHeaders(span: span,
defaultBaggage: defaultBaggage,
textMapPropagator: propagators.textMapPropagator,
textMapBaggagePropagator: propagators.textMapBaggagePropagator)

if let originalHeaders = request.allHTTPHeaderFields {
traceHeaders.merge(originalHeaders) { _, new in new }
}
instrumentedRequest.allHTTPHeaderFields = traceHeaders
return instrumentedRequest
}

private static func tracePropagationHTTPHeaders(span: Span?, textMapPropagator: TextMapPropagator, textMapBaggagePropagator: TextMapBaggagePropagator) -> [String: String] {
private static func tracePropagationHTTPHeaders(span: Span?, defaultBaggage: Baggage?, textMapPropagator: TextMapPropagator, textMapBaggagePropagator: TextMapBaggagePropagator) -> [String: String] {
var headers = [String: String]()

struct HeaderSetter: Setter {
Expand All @@ -182,9 +189,19 @@ class URLSessionLogger {
}
textMapPropagator.inject(spanContext: currentSpan.context, carrier: &headers, setter: HeaderSetter())

if let baggage = OpenTelemetry.instance.contextProvider.activeBaggage {
textMapBaggagePropagator.inject(baggage: baggage, carrier: &headers, setter: HeaderSetter())
let baggageBuilder = OpenTelemetry.instance.baggageManager.baggageBuilder()

if let activeBaggage = OpenTelemetry.instance.contextProvider.activeBaggage {
activeBaggage.getEntries().forEach { baggageBuilder.put(key: $0.key, value: $0.value, metadata: $0.metadata) }
}

if let defaultBaggage {
defaultBaggage.getEntries().forEach { baggageBuilder.put(key: $0.key, value: $0.value, metadata: $0.metadata) }
}

let combinedBaggage = baggageBuilder.build()
textMapBaggagePropagator.inject(baggage: combinedBaggage, carrier: &headers, setter: HeaderSetter())

return headers
}
}
Loading

0 comments on commit be3aff9

Please sign in to comment.