Metrics.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Metrics API open source project
  4. //
  5. // Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
  6. // Licensed under Apache License v2.0
  7. //
  8. // See LICENSE.txt for license information
  9. // See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
  10. //
  11. // SPDX-License-Identifier: Apache-2.0
  12. //
  13. //===----------------------------------------------------------------------===//
  14. // swift-format-ignore-file
  15. // Note: Whitespace changes are used to workaround compiler bug
  16. // https://github.com/swiftlang/swift/issues/79285
  17. // @_exported import CoreMetrics
  18. // @_exported import class CoreMetrics.Timer
  19. #if canImport(FoundationEssentials)
  20. import FoundationEssentials
  21. #else
  22. import Foundation
  23. #endif
  24. #if canImport(Dispatch)
  25. import Dispatch
  26. extension Timer {
  27. /// Convenience for measuring duration of a closure.
  28. ///
  29. /// - parameters:
  30. /// - label: The label for the Timer.
  31. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  32. /// - body: Closure to run & record.
  33. @inlinable
  34. public static func measure<T>(
  35. label: String,
  36. dimensions: [(String, String)] = [],
  37. body: @escaping () throws -> T
  38. ) rethrows -> T {
  39. let timer = Timer(label: label, dimensions: dimensions)
  40. return try measure(with: timer, body: body)
  41. }
  42. /// Convenience for measuring duration of a closure.
  43. ///
  44. /// - parameters:
  45. /// - label: The label for the Timer.
  46. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  47. /// - factory: The custom metrics factory
  48. /// - body: Closure to run & record.
  49. @inlinable
  50. public static func measure<T>(
  51. label: String,
  52. dimensions: [(String, String)] = [],
  53. factory: MetricsFactory,
  54. body: @escaping () throws -> T
  55. ) rethrows -> T {
  56. let timer = Timer(label: label, dimensions: dimensions, factory: factory)
  57. return try measure(with: timer, body: body)
  58. }
  59. @inlinable
  60. internal static func measure<T>(
  61. with timer: Timer,
  62. body: @escaping () throws -> T
  63. ) rethrows -> T {
  64. let start = DispatchTime.now().uptimeNanoseconds
  65. defer {
  66. let delta = DispatchTime.now().uptimeNanoseconds - start
  67. timer.recordNanoseconds(delta)
  68. }
  69. return try body()
  70. }
  71. /// Record the time interval (with nanosecond precision) between the passed `since` dispatch time and `end` dispatch time.
  72. ///
  73. /// - parameters:
  74. /// - since: Start of the interval as `DispatchTime`.
  75. /// - end: End of the interval, defaulting to `.now()`.
  76. public func recordInterval(since: DispatchTime, end: DispatchTime = .now()) {
  77. self.recordNanoseconds(end.uptimeNanoseconds - since.uptimeNanoseconds)
  78. }
  79. /// Convenience for recording a duration based on DispatchTimeInterval.
  80. ///
  81. /// - parameters:
  82. /// - duration: The duration to record.
  83. @inlinable
  84. public func record(_ duration: DispatchTimeInterval) {
  85. // This wrapping in a optional is a workaround because DispatchTimeInterval
  86. // is a non-frozen public enum and Dispatch is built with library evolution
  87. // mode turned on.
  88. // This means we should have an `@unknown default` case, but this breaks
  89. // on non-Darwin platforms.
  90. // Switching over an optional means that the `.none` case will map to
  91. // `default` (which means we'll always have a valid case to go into
  92. // the default case), but in reality this case will never exist as this
  93. // optional will never be nil.
  94. let duration = Optional(duration)
  95. switch duration {
  96. case .nanoseconds(let value):
  97. self.recordNanoseconds(value)
  98. case .microseconds(let value):
  99. self.recordMicroseconds(value)
  100. case .milliseconds(let value):
  101. self.recordMilliseconds(value)
  102. case .seconds(let value):
  103. self.recordSeconds(value)
  104. case .never:
  105. self.record(0)
  106. default:
  107. self.record(0)
  108. }
  109. }
  110. }
  111. #endif
  112. extension Timer {
  113. /// Convenience for recording a duration based on TimeInterval.
  114. ///
  115. /// - parameters:
  116. /// - duration: The duration to record.
  117. @inlinable
  118. public func record(_ duration: TimeInterval) {
  119. self.recordSeconds(duration)
  120. }
  121. }
  122. extension Timer {
  123. /// Convenience for recording a duration based on `Duration`.
  124. ///
  125. /// `Duration` will be converted to an `Int64` number of nanoseconds, and then recorded with nanosecond precision.
  126. ///
  127. /// - Parameters:
  128. /// - duration: The `Duration` to record.
  129. @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
  130. @inlinable
  131. public func record(duration: Duration) {
  132. // `Duration` doesn't have a nice way to convert it nanoseconds or seconds,
  133. // and manual conversion can overflow.
  134. let seconds = duration.components.seconds.multipliedReportingOverflow(by: 1_000_000_000)
  135. guard !seconds.overflow else { return self.recordNanoseconds(Int64.max) }
  136. let nanoseconds = seconds.partialValue.addingReportingOverflow(duration.components.attoseconds / 1_000_000_000)
  137. guard !nanoseconds.overflow else { return self.recordNanoseconds(Int64.max) }
  138. self.recordNanoseconds(nanoseconds.partialValue)
  139. }
  140. /// Convenience for measuring duration of a closure.
  141. ///
  142. /// - Parameters:
  143. /// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
  144. /// - body: The closure to record the duration of.
  145. @inlinable
  146. @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
  147. public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
  148. clock: Clock = .continuous,
  149. body: () throws(Failure) -> Result
  150. ) throws(Failure) -> Result where Clock.Duration == Duration {
  151. let start = clock.now
  152. defer {
  153. self.record(duration: start.duration(to: clock.now))
  154. }
  155. return try body()
  156. }
  157. /// Convenience for measuring duration of a closure.
  158. ///
  159. /// - Parameters:
  160. /// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
  161. /// - isolation: The isolation of the method. Defaults to the isolation of the caller.
  162. /// - body: The closure to record the duration of.
  163. @inlinable
  164. @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
  165. public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
  166. clock: Clock = .continuous,
  167. isolation: isolated (any Actor)? = #isolation,
  168. body: () async throws(Failure) -> sending Result
  169. ) async throws(Failure) -> sending Result where Clock.Duration == Duration {
  170. let start = clock.now
  171. defer {
  172. self.record(duration: start.duration(to: clock.now))
  173. }
  174. return try await body()
  175. }
  176. }