Skip to content

Commit

Permalink
TimingUtil: added synchronous API (#2716)
Browse files Browse the repository at this point in the history
  • Loading branch information
NachoSoto authored Jun 27, 2023
1 parent e6d9259 commit d72927d
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
88 changes: 88 additions & 0 deletions Sources/Misc/DateAndTime/TimingUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal enum TimingUtil {

}

// MARK: - async API

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
extension TimingUtil {

Expand Down Expand Up @@ -105,6 +107,92 @@ extension TimingUtil {

}

// MARK: - Synchronous API

extension TimingUtil {

/// Measures the time to execute `work` and returns the result and the duration.
/// Example:
/// ```swift
/// let (result, duration) = try TimingUtil.measure {
/// return try method()
/// }
/// ```
static func measureSync<Value>(
_ work: () throws -> Value
) rethrows -> (result: Value, duration: Duration) {
let start: DispatchTime = .now()
let result = try work()

return (
result: result,
duration: start.durationUntilNow
)
}

/// Measures the time to execute `work`, returns the result,
/// and logs `message` if duration exceeded `threshold`.
/// Example:
/// ```swift
/// let result = try TimingUtil.measureAndLogIfTooSlow(
/// threshold: .productRequest,
/// message: "Computation too slow",
/// level: .warn,
/// intent: .appleWarning
/// ) {
/// try method()
/// }
/// ```
static func measureSyncAndLogIfTooSlow<Value, Message: CustomStringConvertible & Sendable>(
threshold: Configuration.TimingThreshold,
message: Message,
level: LogLevel = .warn,
intent: LogIntent = .appleWarning,
_ work: () throws -> Value
) rethrows -> Value {
return try self.measureSyncAndLogIfTooSlow(
threshold: threshold.rawValue,
message: message,
level: level,
intent: intent,
work)
}

/// Measures the time to execute `work`, returns the result,
/// and logs `message` if duration exceeded `threshold`.
/// Example:
/// ```swift
/// let result = try TimingUtil.measureAndLogIfTooSlow(
/// threshold: .productRequest,
/// message: "Computation too slow",
/// level: .warn,
/// intent: .appleWarning
/// ) {
/// try asyncMethod()
/// }
/// ```
static func measureSyncAndLogIfTooSlow<Value, Message: CustomStringConvertible & Sendable>(
threshold: Duration,
message: Message,
level: LogLevel = .warn,
intent: LogIntent = .appleWarning,
_ work: () throws -> Value
) rethrows -> Value {
let (result, duration) = try self.measureSync(work)

Self.logIfRequired(duration: duration,
threshold: threshold,
message: message,
level: level,
intent: intent)

return result
}

}

// MARK: - completion-block API

extension TimingUtil {

/// Measures the time to execute `work` and returns the `Result` and the duration.
Expand Down
66 changes: 66 additions & 0 deletions Tests/UnitTests/Misc/TimingUtilTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,72 @@ class TimingUtilAsyncTests: TestCase {
expect(logger.messages).to(beEmpty())
}

func testMeasureSyncAndLogDoesNotLogIfLowerThanThreshold() {
let logger = TestLogHandler()

let expectedResult = Int.random(in: 0..<1000)
let threshold: DispatchTimeInterval = .milliseconds(10)
let sleepDuration = threshold + .milliseconds(-5)

let result: Int = TimingUtil.measureSyncAndLogIfTooSlow(threshold: threshold.seconds,
message: "Too slow") {
Thread.sleep(forTimeInterval: sleepDuration.seconds)

return expectedResult
}

expect(result) == expectedResult
expect(logger.messages).to(beEmpty())
}

func testMeasureSyncAndLogThrowsError() {
let logger = TestLogHandler()

let expectedError: ErrorCode = .storeProblemError

do {
_ = try TimingUtil.measureSyncAndLogIfTooSlow(threshold: 0.001,
message: "Too slow") {
throw expectedError
}

fail("Expected error")
} catch {
expect(error).to(matchError(expectedError))
}

expect(logger.messages).to(beEmpty())
}

func testMeasureSyncAndLogWithResult() {
let logger = TestLogHandler()

let expectedResult = Int.random(in: 0..<1000)
let threshold: DispatchTimeInterval = .milliseconds(10)
let sleepDuration = threshold + .milliseconds(10)

let message = "Computation took too long"
let level: LogLevel = .info

let result = TimingUtil.measureSyncAndLogIfTooSlow(threshold: threshold.seconds,
message: message,
level: level) { () -> Int in
Thread.sleep(forTimeInterval: sleepDuration.seconds)

return expectedResult
}

expect(result) == expectedResult

// Expected: 🍎⚠️ Computation took too long (0.02 seconds)
logger.verifyMessageWasLogged(
String(format: "%@ %@ (%.2f seconds)",
LogIntent.appleWarning.prefix,
message,
sleepDuration.seconds),
level: level
)
}
}

class TimingUtilCompletionBlockTests: TestCase {
Expand Down

0 comments on commit d72927d

Please sign in to comment.