Skip to content

Commit 36a6bb6

Browse files
authored
Use mach_continuous_time() for calculating uptime (#708)
1 parent 147a65a commit 36a6bb6

File tree

3 files changed

+36
-41
lines changed

3 files changed

+36
-41
lines changed

platform/swift/source/shared/Uptime.swift

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,22 @@
77

88
import Foundation
99

10+
/// High resolution wall clock that continues ticking through device sleep and is unaffected by clock adjustments.
1011
struct Uptime {
11-
private let uptime: TimeInterval
12+
private static let timebase: mach_timebase_info_data_t = getTimebase()
1213

13-
/// Returns a high-resolution measurement of system uptime, that continues ticking through device sleep
14-
/// *and* user- or system-generated clock adjustments. This allows for stable differences to be calculated
15-
/// between timestamps.
16-
///
17-
/// - returns: Expressed in seconds measurement of system uptime.
18-
static func get() -> TimeInterval {
19-
var mib: [Int32] = [CTL_KERN, KERN_BOOTTIME]
20-
var size: size_t = MemoryLayout<timeval>.stride
21-
22-
var now = timeval()
23-
let systemTimeError = gettimeofday(&now, nil) != 0
24-
assert(!systemTimeError, "timing: system time unavailable")
25-
26-
var bootTime = timeval()
27-
let bootTimeError = sysctl(&mib, 2, &bootTime, &size, nil, 0) != 0 || bootTime.tv_sec == 0
28-
assert(!bootTimeError, "timing: kernel boot time unavailable")
29-
30-
let seconds = Double(now.tv_sec - bootTime.tv_sec)
31-
assert(seconds >= 0, "timing: system time precedes boot time")
32-
33-
let microseconds = Double(now.tv_usec - bootTime.tv_usec)
34-
35-
return seconds + microseconds / 1_000_000
14+
private static func getTimebase() -> mach_timebase_info_data_t {
15+
var timebase: mach_timebase_info_data_t = mach_timebase_info_data_t()
16+
let timebaseError = mach_timebase_info(&timebase) != KERN_SUCCESS
17+
assert(!timebaseError, "Uptime: could not get timebase")
18+
return timebase
3619
}
3720

38-
/// Captures the current uptime.
39-
///
40-
/// - parameter uptime: The uptime.
41-
init(uptime: TimeInterval = Uptime.get()) {
42-
self.uptime = uptime
21+
private let startTime: UInt64 = mach_continuous_time()
22+
23+
private func ticksToSeconds(_ ticks: UInt64) -> TimeInterval {
24+
let elapsedNanos = ticks * UInt64(Uptime.timebase.numer) / UInt64(Uptime.timebase.denom)
25+
return TimeInterval(elapsedNanos) / 1_000_000_000.0
4326
}
4427

4528
/// Calculates the duration of time that passed between now and the provided timing.
@@ -48,6 +31,6 @@ struct Uptime {
4831
///
4932
/// - returns: The duration of time.
5033
func timeIntervalSince(_ uptime: Uptime) -> TimeInterval {
51-
return self.uptime - uptime.uptime
34+
return ticksToSeconds(mach_continuous_time() - uptime.startTime)
5235
}
5336
}

test/platform/swift/unit_integration/core/logging/SpanTests.swift

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import Foundation
1111
import XCTest
1212

1313
final class SpanTests: XCTestCase {
14+
// We need to allow a fair bit of variance due to potential CI load affecting the simulator
15+
private let allowedTimeVarianceSeconds: TimeInterval = 0.200
16+
1417
private func createSpan(logger: MockCoreLogging, timeProvider: TimeProvider = MockTimeProvider(),
1518
start: TimeInterval? = nil, parent: UUID? = nil) -> Span
1619
{
@@ -31,6 +34,14 @@ final class SpanTests: XCTestCase {
3134
)
3235
}
3336

37+
private func assertTimeWithinRange(timeStringMS: String, intervalInSeconds: TimeInterval) {
38+
let time = Double(timeStringMS)! / 1000.0
39+
let minValue = (intervalInSeconds - allowedTimeVarianceSeconds) < 0 ? 0 : intervalInSeconds - allowedTimeVarianceSeconds
40+
let maxValue = intervalInSeconds + allowedTimeVarianceSeconds
41+
XCTAssertGreaterThanOrEqual(time, minValue)
42+
XCTAssertLessThanOrEqual(time, maxValue)
43+
}
44+
3445
func testStartEndSpan() throws {
3546
let timeProvider = MockTimeProvider()
3647
let logger = MockCoreLogging()
@@ -50,7 +61,7 @@ final class SpanTests: XCTestCase {
5061
"test_key": "test_value",
5162
], emittedStartFields as? [String: String])
5263

53-
timeProvider.advanceBy(timeInterval: 1.0)
64+
Thread.sleep(forTimeInterval: 1.0)
5465

5566
span.end(
5667
.success,
@@ -62,9 +73,10 @@ final class SpanTests: XCTestCase {
6273
XCTAssertEqual(2, logger.logs.count)
6374
let endLog = logger.logs[1]
6475

65-
let emittedEndFields = endLog.fields
76+
var emittedEndFields = endLog.fields
77+
assertTimeWithinRange(timeStringMS: emittedEndFields!["_duration_ms"] as! String, intervalInSeconds: 1.0)
78+
emittedEndFields?.removeValue(forKey: "_duration_ms")
6679
XCTAssertEqual([
67-
"_duration_ms": "1000.0",
6880
"_span_id": span.id.uuidString,
6981
"_span_type": "end",
7082
"_span_name": "test",
@@ -84,16 +96,16 @@ final class SpanTests: XCTestCase {
8496
// ended manually and is deinited.
8597
let spanID: String = {
8698
let span = self.createSpan(logger: logger, timeProvider: timeProvider)
87-
timeProvider.advanceBy(timeInterval: 1.0)
8899
return span.id.uuidString
89100
}()
90101

91102
XCTAssertEqual(2, logger.logs.count)
92103
let endLog = logger.logs[1]
93104

94-
let emittedEndFields = endLog.fields
105+
var emittedEndFields = endLog.fields
106+
assertTimeWithinRange(timeStringMS: emittedEndFields!["_duration_ms"] as! String, intervalInSeconds: 0.0)
107+
emittedEndFields?.removeValue(forKey: "_duration_ms")
95108
XCTAssertEqual([
96-
"_duration_ms": "1000.0",
97109
"_span_id": spanID,
98110
"_span_type": "end",
99111
"_span_name": "test",
@@ -135,21 +147,21 @@ final class SpanTests: XCTestCase {
135147
// Test start with no end
136148
let timeProvider = MockTimeProvider()
137149
let spanOnlyStart = self.createSpan(logger: logger, timeProvider: timeProvider, start: 0)
138-
timeProvider.advanceBy(timeInterval: 1337)
150+
Thread.sleep(forTimeInterval: 1.0)
139151
spanOnlyStart.end(.success)
140152

141153
XCTAssertEqual(4, logger.logs.count)
142-
XCTAssertEqual("1337000.0", try XCTUnwrap(logger.logs[3].fields?["_duration_ms"] as? String))
154+
assertTimeWithinRange(timeStringMS: (logger.logs[3].fields?["_duration_ms"] as? String)!, intervalInSeconds: 1.0)
143155
XCTAssertEqual(Date(timeIntervalSince1970: 0), logger.logs[2].occurredAtOverride)
144156
XCTAssertEqual(nil, logger.logs[3].occurredAtOverride)
145157

146158
// Test end with no start
147159
let spanOnlyEnd = self.createSpan(logger: logger, timeProvider: timeProvider)
148-
timeProvider.advanceBy(timeInterval: 1338)
160+
Thread.sleep(forTimeInterval: 1.0)
149161
spanOnlyEnd.end(.success, endTimeInterval: 666)
150162

151163
XCTAssertEqual(6, logger.logs.count)
152-
XCTAssertEqual("1338000.0", try XCTUnwrap(logger.logs[5].fields?["_duration_ms"] as? String))
164+
assertTimeWithinRange(timeStringMS: (logger.logs[5].fields?["_duration_ms"] as? String)!, intervalInSeconds: 1.0)
153165
XCTAssertEqual(nil, logger.logs[4].occurredAtOverride)
154166
XCTAssertEqual(Date(timeIntervalSince1970: 666), logger.logs[5].occurredAtOverride)
155167
}

test/platform/swift/unit_integration/mocks/MockTimingProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public final class MockTimeProvider {
2121
}
2222

2323
extension MockTimeProvider: TimeProvider {
24-
public func uptime() -> Uptime { Uptime(uptime: self.timeIntervalSince1970) }
24+
public func uptime() -> Uptime { Uptime() }
2525

2626
public func now() -> Date { Date(timeIntervalSince1970: self.timeIntervalSince1970) }
2727
}

0 commit comments

Comments
 (0)