Skip to content

Commit 0737ab2

Browse files
author
Ignacio Bonafonte
authored
Merge pull request #171 from open-telemetry/network-instrumentation
Add automatic Network instrumentation for URLSession, just by initializing a class. Extra behaviour can be added by setting the callbacks in URLSessionInstrumentationConfiguration. It includes a simple example with the minimum code that can also be used for testing.
2 parents 48b3f8a + ab96e89 commit 0737ab2

File tree

16 files changed

+1476
-94
lines changed

16 files changed

+1476
-94
lines changed

Examples/Network Sample/main.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2021, 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+
import OpenTelemetrySdk
18+
import StdoutExporter
19+
import URLSessionInstrumentation
20+
21+
22+
func simpleNetworkCall() {
23+
let url = URL(string: "http://httpbin.org/get")!
24+
let request = URLRequest(url: url)
25+
let semaphore = DispatchSemaphore(value: 0)
26+
27+
let task = URLSession.shared.dataTask(with: request) { data, _, _ in
28+
if let data = data {
29+
let string = String(decoding: data, as: UTF8.self)
30+
print(string)
31+
}
32+
semaphore.signal()
33+
}
34+
task.resume()
35+
36+
semaphore.wait()
37+
}
38+
39+
40+
class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionTaskDelegate {
41+
let semaphore = DispatchSemaphore(value: 0)
42+
43+
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
44+
semaphore.signal()
45+
}
46+
}
47+
let delegate = SessionDelegate()
48+
49+
func simpleNetworkCallWithDelegate() {
50+
51+
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue:nil)
52+
53+
let url = URL(string: "http://httpbin.org/get")!
54+
let request = URLRequest(url: url)
55+
56+
let task = session.dataTask(with: request)
57+
task.resume()
58+
59+
delegate.semaphore.wait()
60+
}
61+
62+
63+
let spanProcessor = SimpleSpanProcessor(spanExporter: StdoutExporter(isDebug: true))
64+
OpenTelemetrySDK.instance.tracerProvider.addSpanProcessor(spanProcessor)
65+
66+
let networkInstrumentation = URLSessionInstrumentation(configuration: URLSessionInstrumentationConfiguration())
67+
68+
simpleNetworkCall()
69+
simpleNetworkCallWithDelegate()
70+
sleep(1)

Package.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ let package = Package(
1616
.library(name: "libOpenTelemetrySdk", type: .static, targets: ["OpenTelemetrySdk"]),
1717
.library(name: "ResourceExtension", type: .dynamic, targets: ["ResourceExtension"]),
1818
.library(name: "libResourceExtension", type: .static, targets: ["ResourceExtension"]),
19+
.library(name: "URLSessionInstrumentation", type: .dynamic, targets: ["URLSessionInstrumentation"]),
20+
.library(name: "libURLSessionInstrumentation", type: .static, targets: ["URLSessionInstrumentation"]),
1921
.library(name: "OpenTracingShim-experimental", type: .dynamic, targets: ["OpenTracingShim"]),
2022
.library(name: "libOpenTracingShim-experimental", type: .static, targets: ["OpenTracingShim"]),
2123
.library(name: "SwiftMetricsShim-experimental", type: .dynamic, targets: ["SwiftMetricsShim"]),
@@ -48,26 +50,25 @@ let package = Package(
4850
targets: [
4951
.target(name: "OpenTelemetryApi",
5052
dependencies: []),
51-
5253
.target(name: "OpenTelemetrySdk",
5354
dependencies: ["OpenTelemetryApi",
5455
.product(name: "Atomics", package: "swift-atomics")]),
5556
.target(name: "ResourceExtension",
5657
dependencies: ["OpenTelemetrySdk"],
5758
path: "Sources/Instrumentation/SDKResourceExtension",
5859
exclude: ["README.md"]),
60+
.target(name: "URLSessionInstrumentation",
61+
dependencies: ["OpenTelemetrySdk"],
62+
path: "Sources/Instrumentation/URLSession"),
5963
.target(name: "OpenTracingShim",
6064
dependencies: ["OpenTelemetrySdk",
6165
"Opentracing"],
62-
path: "Sources/Importers/OpenTracingShim"
63-
),
66+
path: "Sources/Importers/OpenTracingShim"),
6467
.target(name: "SwiftMetricsShim",
6568
dependencies: ["OpenTelemetrySdk",
6669
.product(name: "NIO", package: "swift-nio"),
6770
.product(name: "CoreMetrics", package: "swift-metrics")],
68-
path: "Sources/Importers/SwiftMetricsShim"
69-
70-
),
71+
path: "Sources/Importers/SwiftMetricsShim"),
7172
.target(name: "JaegerExporter",
7273
dependencies: ["OpenTelemetrySdk",
7374
.product(name: "Thrift", package: "Thrift")],
@@ -97,23 +98,26 @@ let package = Package(
9798
.testTarget(name: "OpenTelemetryApiTests",
9899
dependencies: ["OpenTelemetryApi"],
99100
path: "Tests/OpenTelemetryApiTests"),
101+
.testTarget(name: "OpenTelemetrySdkTests",
102+
dependencies: ["OpenTelemetryApi",
103+
"OpenTelemetrySdk"],
104+
path: "Tests/OpenTelemetrySdkTests"),
100105
.testTarget(name: "ResourceExtensionTests",
101106
dependencies: ["ResourceExtension", "OpenTelemetrySdk"],
102107
path: "Tests/InstrumentationTests/SDKResourceExtensionTests"),
108+
.testTarget(name: "URLSessionInstrumentationTests",
109+
dependencies: ["URLSessionInstrumentation",
110+
.product(name: "NIO", package: "swift-nio"),
111+
.product(name: "NIOHTTP1", package: "swift-nio")],
112+
path: "Tests/InstrumentationTests/URLSessionTests"),
103113
.testTarget(name: "OpenTracingShimTests",
104114
dependencies: ["OpenTracingShim",
105115
"OpenTelemetrySdk"],
106-
path: "Tests/ImportersTests/OpenTracingShim"
107-
),
116+
path: "Tests/ImportersTests/OpenTracingShim"),
108117
.testTarget(name: "SwiftMetricsShimTests",
109118
dependencies: ["SwiftMetricsShim",
110119
"OpenTelemetrySdk"],
111-
path: "Tests/ImportersTests/SwiftMetricsShim"
112-
),
113-
.testTarget(name: "OpenTelemetrySdkTests",
114-
dependencies: ["OpenTelemetryApi",
115-
"OpenTelemetrySdk"],
116-
path: "Tests/OpenTelemetrySdkTests"),
120+
path: "Tests/ImportersTests/SwiftMetricsShim"),
117121
.testTarget(name: "JaegerExporterTests",
118122
dependencies: ["JaegerExporter"],
119123
path: "Tests/ExportersTests/Jaeger"),
@@ -149,5 +153,9 @@ let package = Package(
149153
dependencies: ["DatadogExporter"],
150154
path: "Examples/Datadog Sample",
151155
exclude: ["README.md"]),
156+
.target(name: "NetworkSample",
157+
dependencies: ["URLSessionInstrumentation", "StdoutExporter"],
158+
path: "Examples/Network Sample",
159+
exclude: ["README.md"]),
152160
]
153161
)

Sources/Exporters/OpenTelemetryProtocol/metric/MetricsAdapter.swift

Lines changed: 72 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct MetricsAdapter {
2020
static func toProtoResourceMetrics(metricDataList: [Metric]) -> [Opentelemetry_Proto_Metrics_V1_ResourceMetrics] {
2121
let resourceAndLibraryMap = groupByResouceAndLibrary(metricDataList: metricDataList)
2222
var resourceMetrics = [Opentelemetry_Proto_Metrics_V1_ResourceMetrics]()
23-
23+
2424
resourceAndLibraryMap.forEach { resMap in
2525
var instrumentationLibraryMetrics = [Opentelemetry_Proto_Metrics_V1_InstrumentationLibraryMetrics]()
2626
resMap.value.forEach { instLibrary in
@@ -38,98 +38,95 @@ struct MetricsAdapter {
3838
resourceMetric.instrumentationLibraryMetrics.append(contentsOf: instrumentationLibraryMetrics)
3939
resourceMetrics.append(resourceMetric)
4040
}
41-
41+
4242
return resourceMetrics
4343
}
44-
44+
4545
private static func groupByResouceAndLibrary(metricDataList: [Metric]) -> [Resource: [InstrumentationLibraryInfo: [Opentelemetry_Proto_Metrics_V1_Metric]]] {
4646
var results = [Resource: [InstrumentationLibraryInfo: [Opentelemetry_Proto_Metrics_V1_Metric]]]()
4747

4848
metricDataList.forEach {
4949
results[$0.resource, default: [InstrumentationLibraryInfo: [Opentelemetry_Proto_Metrics_V1_Metric]]()][$0.instrumentationLibraryInfo, default: [Opentelemetry_Proto_Metrics_V1_Metric]()]
5050
.append(toProtoMetric(metric: $0))
5151
}
52-
52+
5353
return results
5454
}
55-
55+
5656
static func toProtoMetric(metric: Metric) -> Opentelemetry_Proto_Metrics_V1_Metric {
5757
var protoMetric = Opentelemetry_Proto_Metrics_V1_Metric()
5858
protoMetric.name = metric.name
5959
protoMetric.description_p = metric.description
6060

6161
metric.data.forEach {
6262
switch metric.aggregationType {
63-
case .doubleSum:
64-
guard let sumData = $0 as? SumData<Double> else {
65-
break
66-
}
67-
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_DoubleDataPoint()
68-
protoDataPoint.value = sumData.sum
69-
sumData.labels.forEach {
70-
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
71-
kvp.key = $0.key
72-
kvp.value = $0.value
73-
protoDataPoint.labels.append(kvp)
74-
}
75-
76-
protoMetric.doubleSum.dataPoints.append(protoDataPoint)
77-
case .doubleSummary:
78-
79-
guard let summaryData = $0 as? SummaryData<Double> else {
80-
break
81-
}
82-
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_DoubleHistogramDataPoint()
83-
protoDataPoint.sum = summaryData.sum
84-
protoDataPoint.count = UInt64(summaryData.count)
85-
protoDataPoint.explicitBounds = [summaryData.min, summaryData.max]
86-
87-
protoDataPoint.startTimeUnixNano = summaryData.startTimestamp.timeIntervalSince1970.toNanoseconds
88-
protoDataPoint.timeUnixNano = summaryData.timestamp.timeIntervalSince1970.toNanoseconds
89-
90-
summaryData.labels.forEach {
91-
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
92-
kvp.key = $0.key
93-
kvp.value = $0.value
94-
protoDataPoint.labels.append(kvp)
95-
}
96-
97-
protoMetric.doubleHistogram.dataPoints.append(protoDataPoint)
98-
99-
case .intSum:
100-
guard let sumData = $0 as? SumData<Int> else {
101-
break
102-
}
103-
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_IntDataPoint()
104-
protoDataPoint.value = Int64(sumData.sum)
105-
sumData.labels.forEach {
106-
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
107-
kvp.key = $0.key
108-
kvp.value = $0.value
109-
protoDataPoint.labels.append(kvp)
110-
}
111-
112-
protoMetric.intSum.dataPoints.append(protoDataPoint)
113-
114-
case .intSummary:
115-
guard let summaryData = $0 as? SummaryData<Int> else {
116-
break
117-
}
118-
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_IntHistogramDataPoint()
119-
protoDataPoint.sum = Int64(summaryData.sum)
120-
protoDataPoint.count = UInt64(summaryData.count)
121-
protoDataPoint.bucketCounts = [UInt64(summaryData.min), UInt64(summaryData.max)]
122-
protoDataPoint.startTimeUnixNano = summaryData.startTimestamp.timeIntervalSince1970.toNanoseconds
123-
protoDataPoint.timeUnixNano = summaryData.timestamp.timeIntervalSince1970.toNanoseconds
124-
125-
summaryData.labels.forEach {
126-
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
127-
kvp.key = $0.key
128-
kvp.value = $0.value
129-
protoDataPoint.labels.append(kvp)
130-
}
131-
132-
protoMetric.intHistogram.dataPoints.append(protoDataPoint)
63+
case .doubleSum:
64+
guard let sumData = $0 as? SumData<Double> else {
65+
break
66+
}
67+
68+
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_DoubleDataPoint()
69+
protoDataPoint.value = sumData.sum
70+
sumData.labels.forEach {
71+
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
72+
kvp.key = $0.key
73+
kvp.value = $0.value
74+
protoDataPoint.labels.append(kvp)
75+
}
76+
77+
protoMetric.doubleSum.dataPoints.append(protoDataPoint)
78+
case .doubleSummary:
79+
80+
guard let summaryData = $0 as? SummaryData<Double> else {
81+
break
82+
}
83+
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_DoubleSummaryDataPoint()
84+
protoDataPoint.sum = summaryData.sum
85+
protoDataPoint.count = UInt64(summaryData.count)
86+
87+
protoDataPoint.startTimeUnixNano = summaryData.startTimestamp.timeIntervalSince1970.toNanoseconds
88+
protoDataPoint.timeUnixNano = summaryData.timestamp.timeIntervalSince1970.toNanoseconds
89+
90+
summaryData.labels.forEach {
91+
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
92+
kvp.key = $0.key
93+
kvp.value = $0.value
94+
protoDataPoint.labels.append(kvp)
95+
}
96+
97+
protoMetric.doubleSummary.dataPoints.append(protoDataPoint)
98+
case .intSum:
99+
guard let sumData = $0 as? SumData<Int> else {
100+
break
101+
}
102+
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_IntDataPoint()
103+
protoDataPoint.value = Int64(sumData.sum)
104+
sumData.labels.forEach {
105+
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
106+
kvp.key = $0.key
107+
kvp.value = $0.value
108+
protoDataPoint.labels.append(kvp)
109+
}
110+
111+
protoMetric.intSum.dataPoints.append(protoDataPoint)
112+
case .intSummary:
113+
guard let summaryData = $0 as? SummaryData<Int> else {
114+
break
115+
}
116+
var protoDataPoint = Opentelemetry_Proto_Metrics_V1_DoubleSummaryDataPoint()
117+
protoDataPoint.sum = Double(summaryData.sum)
118+
protoDataPoint.count = UInt64(summaryData.count)
119+
protoDataPoint.startTimeUnixNano = summaryData.startTimestamp.timeIntervalSince1970.toNanoseconds
120+
protoDataPoint.timeUnixNano = summaryData.timestamp.timeIntervalSince1970.toNanoseconds
121+
122+
summaryData.labels.forEach {
123+
var kvp = Opentelemetry_Proto_Common_V1_StringKeyValue()
124+
kvp.key = $0.key
125+
kvp.value = $0.value
126+
protoDataPoint.labels.append(kvp)
127+
}
128+
129+
protoMetric.doubleSummary.dataPoints.append(protoDataPoint)
133130
}
134131
}
135132
return protoMetric
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
enum InstrumentationUtils {
19+
static func objc_getClassList() -> [AnyClass] {
20+
let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
21+
let allClasses = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(expectedClassCount))
22+
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses)
23+
let actualClassCount: Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
24+
25+
var classes = [AnyClass]()
26+
for i in 0 ..< actualClassCount {
27+
classes.append(allClasses[Int(i)])
28+
}
29+
allClasses.deallocate()
30+
return classes
31+
}
32+
33+
static func instanceRespondsAndImplements(cls: AnyClass, selector: Selector) -> Bool {
34+
var implements = false
35+
if cls.instancesRespond(to: selector) {
36+
var methodCount: UInt32 = 0
37+
let methodList = class_copyMethodList(cls, &methodCount)
38+
defer {
39+
free(methodList)
40+
}
41+
if let methodList = methodList, methodCount > 0 {
42+
enumerateCArray(array: methodList, count: methodCount) { _, m in
43+
let sel = method_getName(m)
44+
if sel == selector {
45+
implements = true
46+
return
47+
}
48+
}
49+
}
50+
}
51+
return implements
52+
}
53+
54+
private static func enumerateCArray<T>(array: UnsafePointer<T>, count: UInt32, f: (UInt32, T) -> Void) {
55+
var ptr = array
56+
for i in 0 ..< count {
57+
f(i, ptr.pointee)
58+
ptr = ptr.successor()
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)