CoreMetrics.swift 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733
  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. // MARK: - User API
  15. // MARK: - Counter
  16. /// A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero.
  17. ///
  18. /// For example, you can use a counter to represent the number of requests served, tasks completed, or errors.
  19. ///
  20. /// This is the user-facing counter API.
  21. /// Its behavior is defined by the ``CounterHandler`` implementation.
  22. ///
  23. /// Increment a counter:
  24. ///
  25. /// ```swift
  26. /// counter.increment(by: 5)
  27. /// counter.increment() // increments counter by 1
  28. /// ```
  29. ///
  30. /// Reset a counter:
  31. ///
  32. /// ```swift
  33. /// counter.reset()
  34. /// ````
  35. public final class Counter {
  36. /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
  37. /// Do not consider them part of the public API.
  38. @_documentation(visibility: internal)
  39. public let _handler: CounterHandler
  40. @_documentation(visibility: internal)
  41. @usableFromInline
  42. package let _factory: MetricsFactory
  43. /// The label for the counter.
  44. public let label: String
  45. /// The dimensions for the counter, as `(name, value)` tuples.
  46. public let dimensions: [(String, String)]
  47. /// Alternative way to create a new counter with an explicit counter handler.
  48. ///
  49. /// - SeeAlso: Use `init(label:dimensions:)` to create `Counter` instances using the configured metrics backend.
  50. ///
  51. /// - parameters:
  52. /// - label: The label for the `Counter`.
  53. /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
  54. /// - handler: The custom backend.
  55. /// - factory: The custom metrics factory.
  56. public init(label: String, dimensions: [(String, String)], handler: CounterHandler, factory: MetricsFactory) {
  57. self.label = label
  58. self.dimensions = dimensions
  59. self._handler = handler
  60. self._factory = factory
  61. }
  62. /// Alternative way to create a new counter, with an explicit counter handler.
  63. ///
  64. /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Counter`` using the configured metrics backend.
  65. ///
  66. /// - parameters:
  67. /// - label: The label for the `Counter`.
  68. /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
  69. /// - handler: The custom backend, created by the global metrics factory.
  70. public convenience init(label: String, dimensions: [(String, String)], handler: CounterHandler) {
  71. self.init(
  72. label: label,
  73. dimensions: dimensions,
  74. handler: handler,
  75. factory: MetricsSystem.factory
  76. )
  77. }
  78. /// Increment the counter.
  79. ///
  80. /// - parameters:
  81. /// - amount: Amount to increment by.
  82. @inlinable
  83. public func increment<DataType: BinaryInteger>(by amount: DataType) {
  84. self._handler.increment(by: Int64(amount))
  85. }
  86. /// Increment the counter by one.
  87. @inlinable
  88. public func increment() {
  89. self.increment(by: 1)
  90. }
  91. /// Reset the counter back to zero.
  92. @inlinable
  93. public func reset() {
  94. self._handler.reset()
  95. }
  96. }
  97. extension Counter {
  98. /// Create a new counter.
  99. ///
  100. /// - parameters:
  101. /// - label: The label for the `Counter`.
  102. /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
  103. public convenience init(label: String, dimensions: [(String, String)] = []) {
  104. self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
  105. }
  106. /// Create a new counter using a custom metrics factory that you provide.
  107. ///
  108. /// - parameters:
  109. /// - label: The label for the `Counter`.
  110. /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
  111. /// - factory: The custom metrics factory.
  112. public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
  113. let handler = factory.makeCounter(label: label, dimensions: dimensions)
  114. self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
  115. }
  116. /// Signal the underlying metrics library that this counter will never be updated again.
  117. ///
  118. /// In response the library MAY decide to eagerly release any resources held by this counter.
  119. @inlinable
  120. public func destroy() {
  121. self._factory.destroyCounter(self._handler)
  122. }
  123. }
  124. extension Counter: CustomStringConvertible {
  125. public var description: String {
  126. "Counter(\(self.label), dimensions: \(self.dimensions))"
  127. }
  128. }
  129. // MARK: - FloatingPointCounter
  130. /// A floating-point counter is a cumulative metric that represents a single monotonically increasing floating-point counter whose value can only increase or be reset to zero.
  131. ///
  132. /// For example, you can use a floating-point counter to represent the number of requests served, tasks completed, or errors.
  133. /// floating-point counter is not supported by all metrics backends, however a default implementation is provided which accumulates floating point values and records increments to a standard counter after crossing integer boundaries.
  134. ///
  135. /// This is the user-facing floating-point counter API.
  136. /// Its behavior depends on the ``FloatingPointCounterHandler`` implementation.
  137. ///
  138. /// Increment a counter:
  139. ///
  140. /// ```swift
  141. /// counter.increment(by: 5.1)
  142. /// counter.increment() // increments counter by 1
  143. /// ```
  144. ///
  145. /// Reset a counter:
  146. ///
  147. /// ```swift
  148. /// counter.reset()
  149. /// ````
  150. public final class FloatingPointCounter {
  151. /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
  152. /// Do not consider them part of the public API.
  153. @_documentation(visibility: internal)
  154. public let _handler: FloatingPointCounterHandler
  155. @_documentation(visibility: internal)
  156. @usableFromInline
  157. package let _factory: MetricsFactory
  158. /// The label for the floating point counter.
  159. public let label: String
  160. /// The dimensions for the floating point counter, as `(name, value)` tuples.
  161. public let dimensions: [(String, String)]
  162. /// Alternative way to create a new floating-point counter, while providing an explicit floating-point counter handler.
  163. ///
  164. /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``FloatingPointCounter``using the configured metrics backend.
  165. ///
  166. /// - parameters:
  167. /// - label: The label for the `FloatingPointCounter`.
  168. /// - dimensions: The dimensions for the `FloatingPointCounter`.
  169. /// - handler: The custom backend.
  170. /// - factory: The custom metrics factory.
  171. public init(
  172. label: String,
  173. dimensions: [(String, String)],
  174. handler: FloatingPointCounterHandler,
  175. factory: MetricsFactory
  176. ) {
  177. self.label = label
  178. self.dimensions = dimensions
  179. self._handler = handler
  180. self._factory = factory
  181. }
  182. /// Alternative way to create a new floating-point counter, while providing an explicit floating-point counter handler..
  183. ///
  184. /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``FloatingPointCounter`` using the configured metrics backend.
  185. ///
  186. /// - parameters:
  187. /// - label: The label for the `FloatingPointCounter`.
  188. /// - dimensions: The dimensions for the `FloatingPointCounter`, as `(name, value)` tuples.
  189. /// - handler: The custom backend.
  190. public convenience init(label: String, dimensions: [(String, String)], handler: FloatingPointCounterHandler) {
  191. self.init(
  192. label: label,
  193. dimensions: dimensions,
  194. handler: handler,
  195. factory: MetricsSystem.factory
  196. )
  197. }
  198. /// Increment the floating-point counter.
  199. ///
  200. /// - parameters:
  201. /// - amount: Amount to increment by.
  202. @inlinable
  203. public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
  204. self._handler.increment(by: Double(amount))
  205. }
  206. /// Increment the floating-point counter by one.
  207. @inlinable
  208. public func increment() {
  209. self.increment(by: 1.0)
  210. }
  211. /// Reset the floating-point counter back to zero.
  212. @inlinable
  213. public func reset() {
  214. self._handler.reset()
  215. }
  216. }
  217. extension FloatingPointCounter {
  218. /// Create a new floating-point counter.
  219. ///
  220. /// - parameters:
  221. /// - label: The label for the `FloatingPointCounter`.
  222. /// - dimensions: The dimensions for the `FloatingPointCounter`, as `(name, value)` tuples.
  223. public convenience init(label: String, dimensions: [(String, String)] = []) {
  224. self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
  225. }
  226. /// Create a new floating-point counter using a custom metrics factory that you provide.
  227. ///
  228. /// - parameters:
  229. /// - label: The label for the `FloatingPointCounter`.
  230. /// - dimensions: The dimensions for the `FloatingPointCounter`, as `(name, value)` tuples.
  231. /// - factory: The custom metrics factory.
  232. public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
  233. let handler = factory.makeFloatingPointCounter(label: label, dimensions: dimensions)
  234. self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
  235. }
  236. /// Signal the underlying metrics library that this floating point counter will never be updated again.
  237. ///
  238. /// In response the library MAY decide to eagerly release any resources held by this `FloatingPointCounter`.
  239. @inlinable
  240. public func destroy() {
  241. self._factory.destroyFloatingPointCounter(self._handler)
  242. }
  243. }
  244. extension FloatingPointCounter: CustomStringConvertible {
  245. public var description: String {
  246. "FloatingPointCounter(\(self.label), dimensions: \(self.dimensions))"
  247. }
  248. }
  249. // MARK: - Gauge
  250. /// A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
  251. ///
  252. /// Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, such as the number of active threads.
  253. /// Gauges are modeled as `Recorder` with a sample size of 1 and that does not perform any aggregation.
  254. ///
  255. /// Recording a value with a gauge:
  256. ///
  257. /// ```swift
  258. /// guage.record(100)
  259. /// ```
  260. public final class Gauge: Recorder, @unchecked Sendable {
  261. /// Create a new gauge.
  262. ///
  263. /// - parameters:
  264. /// - label: The label for the `Gauge`.
  265. /// - dimensions: The dimensions for the `Gauge`, as `(name, value)` tuples.
  266. public convenience init(label: String, dimensions: [(String, String)] = []) {
  267. self.init(label: label, dimensions: dimensions, aggregate: false)
  268. }
  269. /// Create a new gauge using a custom metrics factory that you provide.
  270. ///
  271. /// - parameters:
  272. /// - label: The label for the `Gauge`.
  273. /// - dimensions: The dimensions for the `Gauge`, as `(name, value)` tuples.
  274. /// - factory: The custom metrics factory.
  275. public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
  276. self.init(label: label, dimensions: dimensions, aggregate: false, factory: factory)
  277. }
  278. }
  279. // MARK: - Meter
  280. /// A meter is similar to a gauge, it is a metric that represents a single numerical value that can arbitrarily go up and down.
  281. ///
  282. /// Meters are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, such as the number of active threads.
  283. ///
  284. /// Recording a value with a meter:
  285. /// ```swift
  286. /// meter.record(100)
  287. /// ```
  288. public final class Meter {
  289. /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
  290. /// Do not consider them part of the public API.
  291. @_documentation(visibility: internal)
  292. public let _handler: MeterHandler
  293. @usableFromInline
  294. @_documentation(visibility: internal)
  295. package let _factory: MetricsFactory
  296. /// The label for the meter.
  297. public let label: String
  298. /// The dimensions for the meter, as `(name, value)` tuples.
  299. public let dimensions: [(String, String)]
  300. /// Alternative way to create a new meter, while providing an explicit meter handler.
  301. ///
  302. /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Meter`` using the configured metrics backend.
  303. ///
  304. /// - parameters:
  305. /// - label: The label for the `Recorder`.
  306. /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
  307. /// - handler: The custom backend.
  308. /// - factory: The custom metrics factory.
  309. public init(label: String, dimensions: [(String, String)], handler: MeterHandler, factory: MetricsFactory) {
  310. self.label = label
  311. self.dimensions = dimensions
  312. self._handler = handler
  313. self._factory = factory
  314. }
  315. /// Alternative way to create a new meter, while providing an explicit meter handler.
  316. ///
  317. /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Meter`` using the configured metrics backend.
  318. ///
  319. /// - parameters:
  320. /// - label: The label for the `Recorder`.
  321. /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
  322. /// - handler: The custom backend.
  323. public convenience init(label: String, dimensions: [(String, String)], handler: MeterHandler) {
  324. self.init(label: label, dimensions: dimensions, handler: handler, factory: MetricsSystem.factory)
  325. }
  326. /// Set an integer value.
  327. ///
  328. /// - parameters:
  329. /// - value: Value to set.
  330. @inlinable
  331. public func set<DataType: BinaryInteger>(_ value: DataType) {
  332. self._handler.set(Int64(value))
  333. }
  334. /// Set a floating-point value.
  335. ///
  336. /// - parameters:
  337. /// - value: Value to est.
  338. @inlinable
  339. public func set<DataType: BinaryFloatingPoint>(_ value: DataType) {
  340. self._handler.set(Double(value))
  341. }
  342. /// Increment the meter.
  343. ///
  344. /// - parameters:
  345. /// - amount: Amount to increment by.
  346. @inlinable
  347. public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
  348. self._handler.increment(by: Double(amount))
  349. }
  350. /// Increment the meter by one.
  351. @inlinable
  352. public func increment() {
  353. self.increment(by: 1.0)
  354. }
  355. /// Decrement the meter.
  356. ///
  357. /// - parameters:
  358. /// - amount: Amount to decrement by.
  359. @inlinable
  360. public func decrement<DataType: BinaryFloatingPoint>(by amount: DataType) {
  361. self._handler.decrement(by: Double(amount))
  362. }
  363. /// Decrement the meter by one.
  364. @inlinable
  365. public func decrement() {
  366. self.decrement(by: 1.0)
  367. }
  368. }
  369. extension Meter {
  370. /// Create a new meter.
  371. ///
  372. /// - parameters:
  373. /// - label: The label for the `Meter`.
  374. /// - dimensions: The dimensions for the `Meter`, as `(name, value)` tuples.
  375. /// - factory: The custom metrics factory.
  376. public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
  377. let handler = factory.makeMeter(label: label, dimensions: dimensions)
  378. self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
  379. }
  380. /// Create a new meter.
  381. ///
  382. /// - parameters:
  383. /// - label: The label for the `Meter`.
  384. /// - dimensions: The dimensions for the `Meter`, as `(name, value)` tuples.
  385. public convenience init(label: String, dimensions: [(String, String)] = []) {
  386. self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
  387. }
  388. /// Signal the underlying metrics library that this recorder will never be updated again.
  389. ///
  390. /// In response the library MAY decide to eagerly release any resources held by this `Recorder`.
  391. @inlinable
  392. public func destroy() {
  393. self._factory.destroyMeter(self._handler)
  394. }
  395. }
  396. extension Meter: CustomStringConvertible {
  397. public var description: String {
  398. "\(type(of: self))(\(self.label), dimensions: \(self.dimensions))"
  399. }
  400. }
  401. // MARK: - Recorder
  402. /// A recorder collects observations within a time window.
  403. ///
  404. /// An example is using a recorder to capture response sizes.
  405. /// A recorder *can* provide aggregated information about the data sample such as count, sum, min, max, and various quantiles.
  406. ///
  407. /// This is the user-facing Recorder API.
  408. /// Its behavior depends on the ``RecorderHandler`` implementation.
  409. ///
  410. /// Recording a value:
  411. ///
  412. /// ```swift
  413. /// recorder.record(101)
  414. /// ```
  415. public class Recorder {
  416. /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
  417. /// Do not consider them part of the public API.
  418. @_documentation(visibility: internal)
  419. public let _handler: RecorderHandler
  420. @_documentation(visibility: internal)
  421. @usableFromInline
  422. package let _factory: MetricsFactory
  423. /// The label for the recorder.
  424. public let label: String
  425. /// The dimensions for the recorder, as `(name, value)` tuples.
  426. public let dimensions: [(String, String)]
  427. /// Whether the backend summarizes recorded values as a distribution.
  428. public let aggregate: Bool
  429. /// Alternative way to create a new recorder, while providing an explicit recorder handler.
  430. ///
  431. /// - SeeAlso: Use `init(label:dimensions:)` to create `Recorder` instances using the configured metrics backend.
  432. ///
  433. /// - parameters:
  434. /// - label: The label for the `Recorder`.
  435. /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
  436. /// - aggregate: A Boolean value that indicates whether to aggregate values.
  437. /// - handler: The custom backend.
  438. /// - factory: The custom metrics factory.
  439. public init(
  440. label: String,
  441. dimensions: [(String, String)],
  442. aggregate: Bool,
  443. handler: RecorderHandler,
  444. factory: MetricsFactory
  445. ) {
  446. self.label = label
  447. self.dimensions = dimensions
  448. self.aggregate = aggregate
  449. self._handler = handler
  450. self._factory = factory
  451. }
  452. /// Alternative way to create a new recorder, while providing an explicit recorder handler.
  453. ///
  454. /// - SeeAlso: Use `init(label:dimensions:)` to create `Recorder` instances using the configured metrics backend.
  455. ///
  456. /// - parameters:
  457. /// - label: The label for the `Recorder`.
  458. /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
  459. /// - aggregate: A Boolean value that indicates whether to aggregate values.
  460. /// - handler: The custom backend.
  461. public convenience init(label: String, dimensions: [(String, String)], aggregate: Bool, handler: RecorderHandler) {
  462. self.init(
  463. label: label,
  464. dimensions: dimensions,
  465. aggregate: aggregate,
  466. handler: handler,
  467. factory: MetricsSystem.factory
  468. )
  469. }
  470. /// Record a value.
  471. ///
  472. /// Recording a value is meant to have "set" semantics, rather than "add" semantics.
  473. /// This means that the value of this `Recorder` will match the passed in value, rather than accumulate and sum the values up.
  474. ///
  475. /// - parameters:
  476. /// - value: Value to record.
  477. @inlinable
  478. public func record<DataType: BinaryInteger>(_ value: DataType) {
  479. self._handler.record(Int64(value))
  480. }
  481. /// Record a value.
  482. ///
  483. /// Recording a value is meant to have "set" semantics, rather than "add" semantics.
  484. /// This means that the value of this `Recorder` will match the passed in value, rather than accumulate and sum the values up.
  485. ///
  486. /// - parameters:
  487. /// - value: Value to record.
  488. @inlinable
  489. public func record<DataType: BinaryFloatingPoint>(_ value: DataType) {
  490. self._handler.record(Double(value))
  491. }
  492. }
  493. extension Recorder {
  494. /// Create a new recorder.
  495. ///
  496. /// - parameters:
  497. /// - label: The label for the `Recorder`.
  498. /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
  499. /// - aggregate: A Boolean value that indicates whether to aggregate values.
  500. public convenience init(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true) {
  501. self.init(label: label, dimensions: dimensions, aggregate: aggregate, factory: MetricsSystem.factory)
  502. }
  503. /// Create a new recorder using a custom metrics factory that you provide..
  504. ///
  505. /// - parameters:
  506. /// - label: The label for the `Recorder`.
  507. /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
  508. /// - aggregate: A Boolean value that indicates whether to aggregate values.
  509. /// - factory: The custom metrics factory.
  510. public convenience init(
  511. label: String,
  512. dimensions: [(String, String)] = [],
  513. aggregate: Bool = true,
  514. factory: MetricsFactory
  515. ) {
  516. let handler = factory.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
  517. self.init(label: label, dimensions: dimensions, aggregate: aggregate, handler: handler, factory: factory)
  518. }
  519. /// Signal the underlying metrics library that this recorder will never be updated again.
  520. ///
  521. /// In response the library MAY decide to eagerly release any resources held by this `Recorder`.
  522. @inlinable
  523. public func destroy() {
  524. self._factory.destroyRecorder(self._handler)
  525. }
  526. }
  527. extension Recorder: CustomStringConvertible {
  528. public var description: String {
  529. "\(type(of: self))(\(self.label), dimensions: \(self.dimensions), aggregate: \(self.aggregate))"
  530. }
  531. }
  532. // MARK: - Timer
  533. /// A unit of time.
  534. public struct TimeUnit: Equatable, Sendable {
  535. private enum Code: Equatable, Sendable {
  536. case nanoseconds
  537. case microseconds
  538. case milliseconds
  539. case seconds
  540. case minutes
  541. case hours
  542. case days
  543. }
  544. private let code: Code
  545. /// The number of nanoseconds in this time unit.
  546. public let scaleFromNanoseconds: UInt64
  547. private init(code: Code, scaleFromNanoseconds: UInt64) {
  548. assert(scaleFromNanoseconds > 0, "invalid scale from nanoseconds")
  549. self.code = code
  550. self.scaleFromNanoseconds = scaleFromNanoseconds
  551. }
  552. /// A nanosecond.
  553. public static let nanoseconds = TimeUnit(code: .nanoseconds, scaleFromNanoseconds: 1)
  554. /// A microsecond.
  555. public static let microseconds = TimeUnit(code: .microseconds, scaleFromNanoseconds: 1000)
  556. /// A millisecond.
  557. public static let milliseconds = TimeUnit(
  558. code: .milliseconds,
  559. scaleFromNanoseconds: 1000 * TimeUnit.microseconds.scaleFromNanoseconds
  560. )
  561. /// A second.
  562. public static let seconds = TimeUnit(
  563. code: .seconds,
  564. scaleFromNanoseconds: 1000 * TimeUnit.milliseconds.scaleFromNanoseconds
  565. )
  566. /// A minute.
  567. public static let minutes = TimeUnit(
  568. code: .minutes,
  569. scaleFromNanoseconds: 60 * TimeUnit.seconds.scaleFromNanoseconds
  570. )
  571. /// An hour.
  572. public static let hours = TimeUnit(code: .hours, scaleFromNanoseconds: 60 * TimeUnit.minutes.scaleFromNanoseconds)
  573. /// A day.
  574. public static let days = TimeUnit(code: .days, scaleFromNanoseconds: 24 * TimeUnit.hours.scaleFromNanoseconds)
  575. }
  576. /// A timer collects observations that represents durations within a time window.
  577. ///
  578. /// It is similar to a `Recorder` but specialized for values that represent durations, such as request durations.
  579. /// A timer provides aggregated information about the data sample, such as min, max, and various quantiles.
  580. ///
  581. /// This is the user-facing Timer API.
  582. /// Its behavior depends on the ``TimerHandler`` implementation.
  583. ///
  584. /// Recording a value:
  585. ///
  586. /// ```swift
  587. /// timer.recordMilliseconds(52)
  588. /// ```
  589. public final class Timer {
  590. /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
  591. /// Do not consider them part of the public API.
  592. @_documentation(visibility: internal)
  593. public let _handler: TimerHandler
  594. @_documentation(visibility: internal)
  595. @usableFromInline
  596. package let _factory: MetricsFactory
  597. /// The label for the timer.
  598. public let label: String
  599. /// The dimensions for the timer, as `(name, value)` tuples.
  600. public let dimensions: [(String, String)]
  601. /// Alternative way to create a new timer, while providing an explicit timer handler.
  602. ///
  603. /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Timer`` using the configured metrics backend.
  604. ///
  605. /// - parameters:
  606. /// - label: The label for the `Timer`.
  607. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  608. /// - handler: The custom backend.
  609. /// - factory: The custom factory.
  610. public init(label: String, dimensions: [(String, String)], handler: TimerHandler, factory: MetricsFactory) {
  611. self.label = label
  612. self.dimensions = dimensions
  613. self._handler = handler
  614. self._factory = factory
  615. }
  616. /// Alternative way to create a new timer, while providing an explicit timer handler.
  617. ///
  618. /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Timer`` using the configured metrics backend.
  619. ///
  620. /// - parameters:
  621. /// - label: The label for the `Timer`.
  622. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  623. /// - handler: The custom backend.
  624. public convenience init(label: String, dimensions: [(String, String)], handler: TimerHandler) {
  625. self.init(label: label, dimensions: dimensions, handler: handler, factory: MetricsSystem.factory)
  626. }
  627. /// Record a duration in nanoseconds.
  628. ///
  629. /// - parameters:
  630. /// - duration: Duration to record.
  631. @inlinable
  632. public func recordNanoseconds(_ duration: Int64) {
  633. self._handler.recordNanoseconds(duration)
  634. }
  635. /// Record a duration in nanoseconds.
  636. ///
  637. /// - parameters:
  638. /// - duration: Duration to record.
  639. @inlinable
  640. public func recordNanoseconds<DataType: BinaryInteger>(_ duration: DataType) {
  641. self.recordNanoseconds(duration >= Int64.max ? Int64.max : Int64(duration))
  642. }
  643. /// Record a duration in microseconds.
  644. ///
  645. /// - parameters:
  646. /// - duration: Duration to record.
  647. @inlinable
  648. public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
  649. guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
  650. let result = Int64(duration).multipliedReportingOverflow(by: 1000)
  651. if result.overflow {
  652. self.recordNanoseconds(Int64.max)
  653. } else {
  654. self.recordNanoseconds(result.partialValue)
  655. }
  656. }
  657. /// Record a duration in microseconds.
  658. ///
  659. /// - parameters:
  660. /// - duration: Duration to record.
  661. @inlinable
  662. public func recordMicroseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
  663. self.recordNanoseconds(Double(duration * 1000) < Double(Int64.max) ? Int64(duration * 1000) : Int64.max)
  664. }
  665. /// Record a duration in milliseconds.
  666. ///
  667. /// - parameters:
  668. /// - duration: Duration to record.
  669. @inlinable
  670. public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
  671. guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
  672. let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000)
  673. if result.overflow {
  674. self.recordNanoseconds(Int64.max)
  675. } else {
  676. self.recordNanoseconds(result.partialValue)
  677. }
  678. }
  679. /// Record a duration in milliseconds.
  680. ///
  681. /// - parameters:
  682. /// - duration: Duration to record.
  683. @inlinable
  684. public func recordMilliseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
  685. self.recordNanoseconds(
  686. Double(duration * 1_000_000) < Double(Int64.max) ? Int64(duration * 1_000_000) : Int64.max
  687. )
  688. }
  689. /// Record a duration in seconds.
  690. ///
  691. /// - parameters:
  692. /// - duration: Duration to record.
  693. @inlinable
  694. public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
  695. guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
  696. let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000_000)
  697. if result.overflow {
  698. self.recordNanoseconds(Int64.max)
  699. } else {
  700. self.recordNanoseconds(result.partialValue)
  701. }
  702. }
  703. /// Record a duration in seconds.
  704. ///
  705. /// - parameters:
  706. /// - duration: Duration to record.
  707. @inlinable
  708. public func recordSeconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
  709. self.recordNanoseconds(
  710. Double(duration * 1_000_000_000) < Double(Int64.max) ? Int64(duration * 1_000_000_000) : Int64.max
  711. )
  712. }
  713. }
  714. extension Timer {
  715. /// Create a new timer.
  716. ///
  717. /// - parameters:
  718. /// - label: The label for the `Timer`.
  719. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  720. /// - factory: The custom factory.
  721. public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
  722. let handler = factory.makeTimer(label: label, dimensions: dimensions)
  723. self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
  724. }
  725. /// Create a new timer using a custom metrics factory that you provide..
  726. ///
  727. /// - parameters:
  728. /// - label: The label for the `Timer`.
  729. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  730. public convenience init(label: String, dimensions: [(String, String)] = []) {
  731. self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
  732. }
  733. /// Create a new timer.
  734. ///
  735. /// - parameters:
  736. /// - label: The label for the `Timer`.
  737. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  738. /// - displayUnit: A hint to the backend responsible for presenting the data of the preferred display unit. This is not guaranteed to be supported by all backends.
  739. /// - factory: The custom factory.
  740. public convenience init(
  741. label: String,
  742. dimensions: [(String, String)] = [],
  743. preferredDisplayUnit displayUnit: TimeUnit,
  744. factory: MetricsFactory
  745. ) {
  746. let handler = factory.makeTimer(label: label, dimensions: dimensions)
  747. handler.preferDisplayUnit(displayUnit)
  748. self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
  749. }
  750. /// Create a new timer.
  751. ///
  752. /// - parameters:
  753. /// - label: The label for the `Timer`.
  754. /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
  755. /// - displayUnit: A hint to the backend responsible for presenting the data of the preferred display unit. This is not guaranteed to be supported by all backends.
  756. public convenience init(
  757. label: String,
  758. dimensions: [(String, String)] = [],
  759. preferredDisplayUnit displayUnit: TimeUnit
  760. ) {
  761. self.init(
  762. label: label,
  763. dimensions: dimensions,
  764. preferredDisplayUnit: displayUnit,
  765. factory: MetricsSystem.factory
  766. )
  767. }
  768. /// Signal the underlying metrics library that this timer will never be updated again.
  769. ///
  770. /// In response the library MAY decide to eagerly release any resources held by this `Timer`.
  771. @inlinable
  772. public func destroy() {
  773. self._factory.destroyTimer(self._handler)
  774. }
  775. }
  776. extension Timer: CustomStringConvertible {
  777. public var description: String {
  778. "Timer(\(self.label), dimensions: \(self.dimensions))"
  779. }
  780. }
  781. // MARK: - MetricsSystem
  782. /// A global facility where the default metrics backend implementation is configured.
  783. ///
  784. /// `MetricsSystem` is set up just once in a given program to create the desired metrics backend
  785. /// implementation using ``MetricsFactory``.
  786. public enum MetricsSystem {
  787. private static let _factory = FactoryBox(NOOPMetricsHandler.instance)
  788. /// A one-time configuration function which globally selects the desired metrics backend
  789. /// implementation.
  790. ///
  791. /// `bootstrap` can be called at maximum once in any given program, calling it more than once will
  792. /// lead to undefined behavior, most likely a crash.
  793. ///
  794. /// - parameters:
  795. /// - factory: A factory that given an identifier produces instances of metrics handlers such as ``CounterHandler``, ``RecorderHandler``, or ``TimerHandler``.
  796. public static func bootstrap(_ factory: MetricsFactory) {
  797. self._factory.replaceFactory(factory, validate: true)
  798. }
  799. // for our testing we want to allow multiple bootstrapping
  800. internal static func bootstrapInternal(_ factory: MetricsFactory) {
  801. self._factory.replaceFactory(factory, validate: false)
  802. }
  803. /// Returns the currently used metrics factory.
  804. ///
  805. /// If a task-local factory was bound by using `withMetricsFactory(_:_:)` this factory is returned, otherwise the globally bootstrapped factory will be returned.
  806. /// This is useful for passing the current factory to APIs that expect an explicit factory parameter.
  807. ///
  808. /// ## Example: Passing current factory to explicit API
  809. ///
  810. /// ```swift
  811. /// // Library API that requires explicit factory
  812. /// func createMetricWithExplicitFactory(label: String, factory: MetricsFactory) -> Counter {
  813. /// Counter(label: label, factory: factory)
  814. /// }
  815. ///
  816. /// // Usage with task-local factory
  817. /// withMetricsFactory(testFactory) {
  818. /// // Pass current factory to API expecting explicit parameter
  819. /// let counter = createMetricWithExplicitFactory(
  820. /// label: "requests",
  821. /// factory: MetricsSystem.factory
  822. /// )
  823. /// }
  824. /// ```
  825. ///
  826. /// - Returns: The task-local factory if bound, otherwise the global factory.
  827. public static var factory: MetricsFactory {
  828. if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
  829. self._taskLocalFactory ?? self._factory.underlying
  830. } else {
  831. self._factory.underlying
  832. }
  833. }
  834. /// Task-local metrics factory override.
  835. ///
  836. /// Used internally by `withMetricsFactory(_:_:)` free functions.
  837. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
  838. @TaskLocal
  839. @usableFromInline
  840. internal static var _taskLocalFactory: MetricsFactory?
  841. /// Returns the currently used metrics factory.
  842. ///
  843. /// If a task-local factory was bound by using `withMetricsFactory(_:_:)` this factory is returned, otherwise the globally bootstrapped factory will be returned.
  844. /// This is useful for passing the current factory to APIs that expect an explicit factory parameter.
  845. ///
  846. /// ## Example: Passing current factory to explicit API
  847. ///
  848. /// ```swift
  849. /// // Library API that requires explicit factory
  850. /// func createMetricWithExplicitFactory(label: String, factory: MetricsFactory) -> Counter {
  851. /// Counter(label: label, factory: factory)
  852. /// }
  853. ///
  854. /// // Usage with task-local factory
  855. /// withMetricsFactory(testFactory) {
  856. /// // Pass current factory to API expecting explicit parameter
  857. /// let counter = createMetricWithExplicitFactory(
  858. /// label: "requests",
  859. /// factory: MetricsSystem.currentFactory
  860. /// )
  861. /// }
  862. /// ```
  863. ///
  864. /// - Returns: The task-local factory if bound, otherwise the global factory.
  865. @available(*, deprecated, renamed: "factory")
  866. @inlinable
  867. public static var currentFactory: MetricsFactory {
  868. factory
  869. }
  870. /// Execute a closure with a factory bound to task-local storage.
  871. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
  872. @usableFromInline
  873. internal static func withTaskLocalFactory<R>(
  874. _ factory: MetricsFactory,
  875. operation: () throws -> R
  876. ) rethrows -> R {
  877. try $_taskLocalFactory.withValue(factory, operation: operation)
  878. }
  879. /// Execute an async closure with a factory bound to task-local storage.
  880. #if compiler(>=6.2)
  881. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
  882. @usableFromInline
  883. nonisolated(nonsending)
  884. internal static func withTaskLocalFactory<R>(
  885. _ factory: MetricsFactory,
  886. operation: nonisolated(nonsending) () async throws -> R
  887. ) async rethrows -> R
  888. {
  889. try await $_taskLocalFactory.withValue(factory, operation: operation)
  890. }
  891. #else
  892. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
  893. @usableFromInline
  894. internal static func withTaskLocalFactory<R>(
  895. _ factory: MetricsFactory,
  896. operation: () async throws -> R
  897. ) async rethrows -> R {
  898. try await $_taskLocalFactory.withValue(factory, operation: operation)
  899. }
  900. #endif
  901. /// Acquire a writer lock for the duration of the given block.
  902. ///
  903. /// - Parameter body: The block to execute while holding the lock.
  904. /// - Returns: The value returned by the block.
  905. public static func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
  906. try self._factory.withWriterLock(body)
  907. }
  908. // This can be `@unchecked Sendable` because we're manually gating access to mutable state with a lock.
  909. private final class FactoryBox: @unchecked Sendable {
  910. private let lock = ReadWriteLock()
  911. fileprivate var _underlying: MetricsFactory
  912. private var initialized = false
  913. init(_ underlying: MetricsFactory) {
  914. self._underlying = underlying
  915. }
  916. func replaceFactory(_ factory: MetricsFactory, validate: Bool) {
  917. self.lock.withWriterLock {
  918. precondition(
  919. !validate || !self.initialized,
  920. "metrics system can only be initialized once per process. currently used factory: \(self._underlying)"
  921. )
  922. self._underlying = factory
  923. self.initialized = true
  924. }
  925. }
  926. var underlying: MetricsFactory {
  927. self.lock.withReaderLock {
  928. self._underlying
  929. }
  930. }
  931. func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
  932. try self.lock.withWriterLock(body)
  933. }
  934. }
  935. }
  936. // MARK: - Library SPI, intended to be implemented by backend libraries
  937. // MARK: - MetricsFactory
  938. /// The `MetricsFactory` is the bridge between the `MetricsSystem` and the metrics backend implementation.
  939. ///
  940. /// The role of `MetricsFactory` is to initialize concrete implementations of the various metric types:
  941. /// * `Counter` -> `CounterHandler`
  942. /// * `FloatingPointCounter` -> `FloatingPointCounterHandler`
  943. /// * `Recorder` -> `RecorderHandler`
  944. /// * `Timer` -> `TimerHandler`
  945. ///
  946. /// To use the SwiftMetrics API, please refer to the documentation of `MetricsSystem`.
  947. ///
  948. /// ### Destroying metrics
  949. ///
  950. /// Since _some_ metrics implementations may need to allocate (potentially "heavy") resources for metrics, destroying
  951. /// metrics offers a signal to libraries when a metric is "known to never be updated again."
  952. ///
  953. /// While many metrics are bound to the entire lifetime of an application and thus never need to be destroyed eagerly,
  954. /// some metrics have well defined unique life-cycles where it may be beneficial to release any resources held by them
  955. /// more eagerly than awaiting the application's termination. In such cases, a library or application should invoke
  956. /// a metric's appropriate `destroy()` method, which in turn results in the corresponding handler that it is backed by
  957. /// to be passed to `destroyCounter(handler:)`, `destroyRecorder(handler:)` or `destroyTimer(handler:)` where the factory
  958. /// can decide to free any corresponding resources.
  959. ///
  960. /// While some libraries may not need to implement this destroying as they may be stateless or similar,
  961. /// libraries using the metrics API should always assume a library WILL make use of this signal, and shall not
  962. /// neglect calling these methods when appropriate.
  963. public protocol MetricsFactory: _SwiftMetricsSendableProtocol {
  964. /// Create a backing counter handler.
  965. ///
  966. /// - parameters:
  967. /// - label: The label for the `CounterHandler`.
  968. /// - dimensions: The dimensions for the `CounterHandler`, as `(name, value)` tuples.
  969. func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler
  970. /// Create a backing floating-point handler.
  971. ///
  972. /// - parameters:
  973. /// - label: The label for the `FloatingPointCounterHandler`.
  974. /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
  975. func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler
  976. /// Create a backing meter handler.
  977. ///
  978. /// - parameters:
  979. /// - label: The label for the `MeterHandler`.
  980. /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
  981. func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler
  982. /// Create a backing recorder handler.
  983. ///
  984. /// - parameters:
  985. /// - label: The label for the `RecorderHandler`.
  986. /// - dimensions: The dimensions for the `RecorderHandler`, as `(name, value)` tuples.
  987. /// - aggregate: Whether the returned handler should summarize recorded values as a distribution.
  988. func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler
  989. /// Create a backing timer handler.
  990. ///
  991. /// - parameters:
  992. /// - label: The label for the `TimerHandler`.
  993. /// - dimensions: The dimensions for the `TimerHandler`, as `(name, value)` tuples.
  994. func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler
  995. /// Invoked when the corresponding counter's `destroy()` function is invoked.
  996. ///
  997. /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
  998. ///
  999. /// - parameters:
  1000. /// - handler: The handler to be destroyed.
  1001. func destroyCounter(_ handler: CounterHandler)
  1002. /// Invoked when the corresponding meter's `destroy()` function is invoked.
  1003. ///
  1004. /// Upon receiving this signal the factory may eagerly release any resources related to this recorder.
  1005. ///
  1006. /// - parameters:
  1007. /// - handler: The handler to be destroyed.
  1008. func destroyMeter(_ handler: MeterHandler)
  1009. /// Invoked when the corresponding floating-point counter's `destroy()` function is invoked.
  1010. ///
  1011. /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
  1012. ///
  1013. /// - parameters:
  1014. /// - handler: The handler to be destroyed.
  1015. func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler)
  1016. /// Invoked when the corresponding recorder's `destroy()` function is invoked.
  1017. ///
  1018. /// Upon receiving this signal the factory may eagerly release any resources related to this recorder.
  1019. ///
  1020. /// - parameters:
  1021. /// - handler: The handler to be destroyed.
  1022. func destroyRecorder(_ handler: RecorderHandler)
  1023. /// Invoked when the corresponding Timer's `destroy()` function is invoked.
  1024. ///
  1025. /// Upon receiving this signal the factory may eagerly release any resources related to this timer.
  1026. ///
  1027. /// - parameters:
  1028. /// - handler: The handler to be destroyed.
  1029. func destroyTimer(_ handler: TimerHandler)
  1030. }
  1031. /// Wraps a CounterHandler, adding support for incrementing by floating point values by storing an accumulated floating point value and recording increments to the underlying CounterHandler after crossing integer boundaries.
  1032. internal final class AccumulatingRoundingFloatingPointCounter: FloatingPointCounterHandler {
  1033. private let lock = Lock()
  1034. private let counterHandler: CounterHandler
  1035. private let factory: MetricsFactory
  1036. internal var fraction: Double = 0
  1037. init(label: String, dimensions: [(String, String)], factory: MetricsFactory) {
  1038. self.counterHandler = factory.makeCounter(label: label, dimensions: dimensions)
  1039. self.factory = factory
  1040. }
  1041. func increment(by amount: Double) {
  1042. // Drop illegal values
  1043. // - cannot increment by NaN
  1044. guard !amount.isNaN else { return }
  1045. // - cannot increment by infinite quantities
  1046. guard !amount.isInfinite else { return }
  1047. // - cannot increment by negative values
  1048. guard amount.sign == .plus else { return }
  1049. // - cannot increment by zero
  1050. guard !amount.isZero else { return }
  1051. if amount.exponent >= 63 {
  1052. // If amount is in Int64.max..<+Inf, ceil to Int64.max
  1053. self.lock.withLockVoid {
  1054. self.counterHandler.increment(by: .max)
  1055. }
  1056. } else {
  1057. // Split amount into integer and fraction components
  1058. var (increment, fraction) = self.integerAndFractionComponents(of: amount)
  1059. self.lock.withLockVoid {
  1060. // Add the fractional component to the accumulated fraction.
  1061. self.fraction += fraction
  1062. // self.fraction may have cross an integer boundary, Split it
  1063. // and add any integer component.
  1064. let (integer, fraction) = integerAndFractionComponents(of: self.fraction)
  1065. increment += integer
  1066. self.fraction = fraction
  1067. // Increment the handler by the total integer component.
  1068. if increment > 0 {
  1069. self.counterHandler.increment(by: increment)
  1070. }
  1071. }
  1072. }
  1073. }
  1074. @inline(__always)
  1075. private func integerAndFractionComponents(of value: Double) -> (Int64, Double) {
  1076. let integer = Int64(value)
  1077. let fraction = value - value.rounded(.towardZero)
  1078. return (integer, fraction)
  1079. }
  1080. func reset() {
  1081. self.lock.withLockVoid {
  1082. self.fraction = 0
  1083. self.counterHandler.reset()
  1084. }
  1085. }
  1086. func destroy() {
  1087. self.factory.destroyCounter(self.counterHandler)
  1088. }
  1089. }
  1090. /// Wraps a RecorderHandler, adding support for incrementing values by storing an accumulated value and recording increments to the underlying CounterHandler after crossing integer boundaries.
  1091. /// - Note: we can annotate this class as `@unchecked Sendable` because we are manually gating access to mutable state (i.e., the `value` property) via a Lock.
  1092. internal final class AccumulatingMeter: MeterHandler, @unchecked Sendable {
  1093. private let recorderHandler: RecorderHandler
  1094. // FIXME: use swift-atomics when floating point support is available
  1095. private var value: Double = 0
  1096. private let lock = Lock()
  1097. private let factory: MetricsFactory
  1098. init(label: String, dimensions: [(String, String)], factory: MetricsFactory) {
  1099. self.recorderHandler = factory.makeRecorder(label: label, dimensions: dimensions, aggregate: true)
  1100. self.factory = factory
  1101. }
  1102. func set(_ value: Int64) {
  1103. self._set(Double(value))
  1104. }
  1105. func set(_ value: Double) {
  1106. self._set(value)
  1107. }
  1108. func increment(by amount: Double) {
  1109. // Drop illegal values
  1110. // - cannot increment by NaN
  1111. guard !amount.isNaN else {
  1112. return
  1113. }
  1114. // - cannot increment by infinite quantities
  1115. guard !amount.isInfinite else {
  1116. return
  1117. }
  1118. // - cannot increment by negative values
  1119. guard amount.sign == .plus else {
  1120. return
  1121. }
  1122. // - cannot increment by zero
  1123. guard !amount.isZero else {
  1124. return
  1125. }
  1126. let newValue: Double = self.lock.withLock {
  1127. self.value += amount
  1128. return self.value
  1129. }
  1130. self.recorderHandler.record(newValue)
  1131. }
  1132. func decrement(by amount: Double) {
  1133. // Drop illegal values
  1134. // - cannot decrement by NaN
  1135. guard !amount.isNaN else {
  1136. return
  1137. }
  1138. // - cannot decrement by infinite quantities
  1139. guard !amount.isInfinite else {
  1140. return
  1141. }
  1142. // - cannot decrement by negative values
  1143. guard amount.sign == .plus else {
  1144. return
  1145. }
  1146. // - cannot decrement by zero
  1147. guard !amount.isZero else {
  1148. return
  1149. }
  1150. let newValue: Double = self.lock.withLock {
  1151. self.value -= amount
  1152. return self.value
  1153. }
  1154. self.recorderHandler.record(newValue)
  1155. }
  1156. private func _set(_ value: Double) {
  1157. self.lock.withLockVoid {
  1158. self.value = value
  1159. }
  1160. self.recorderHandler.record(value)
  1161. }
  1162. func destroy() {
  1163. self.factory.destroyRecorder(self.recorderHandler)
  1164. }
  1165. }
  1166. extension MetricsFactory {
  1167. /// Create a default backing floating-point counter handler for backends which do not naively support floating point counters.
  1168. ///
  1169. /// The created floating-point counter handler is a wrapper around a backend's counter handler which accumulates floating point values and records increments to an underlying counter handler after crossing integer boundaries.
  1170. ///
  1171. /// - parameters:
  1172. /// - label: The label for the `FloatingPointCounterHandler`.
  1173. /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
  1174. public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
  1175. AccumulatingRoundingFloatingPointCounter(label: label, dimensions: dimensions, factory: self)
  1176. }
  1177. /// Invoked when the corresponding floating-point counter's `destroy()` function is invoked.
  1178. ///
  1179. /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
  1180. ///
  1181. /// `destroyFloatingPointCounter` must be implemented if `makeFloatingPointCounter` is implemented.
  1182. ///
  1183. /// - parameters:
  1184. /// - handler: The handler to be destroyed.
  1185. public func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler) {
  1186. (handler as? AccumulatingRoundingFloatingPointCounter)?.destroy()
  1187. }
  1188. }
  1189. extension MetricsFactory {
  1190. /// Create a default backing meter handler for backends which do not naively support meters.
  1191. ///
  1192. /// The created MeterHandler is a wrapper around a backend's RecorderHandler which records current values.
  1193. ///
  1194. /// - parameters:
  1195. /// - label: The label for the `MeterHandler`.
  1196. /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
  1197. public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
  1198. AccumulatingMeter(label: label, dimensions: dimensions, factory: self)
  1199. }
  1200. /// Invoked when the corresponding meter's `destroy()` function is invoked.
  1201. ///
  1202. /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
  1203. ///
  1204. /// `destroyMeter` must be implemented if `makeMeter` is implemented.
  1205. ///
  1206. /// - parameters:
  1207. /// - handler: The handler to be destroyed.
  1208. public func destroyMeter(_ handler: MeterHandler) {
  1209. (handler as? AccumulatingMeter)?.destroy()
  1210. }
  1211. }
  1212. // MARK: - Backend Handlers
  1213. /// A counter handler represents a backend implementation of a counter.
  1214. ///
  1215. /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
  1216. /// To use the SwiftMetrics API, please refer to the documentation of `Counter`.
  1217. ///
  1218. /// ### Implementation requirements
  1219. ///
  1220. /// To implement your own `CounterHandler` you should respect a few requirements that are necessary so applications work
  1221. /// as expected regardless of the selected `CounterHandler` implementation.
  1222. ///
  1223. /// - The `CounterHandler` must be a `class`.
  1224. public protocol CounterHandler: AnyObject, _SwiftMetricsSendableProtocol {
  1225. /// Increment the counter.
  1226. ///
  1227. /// - parameters:
  1228. /// - by: Amount to increment by.
  1229. func increment(by: Int64)
  1230. /// Reset the counter back to zero.
  1231. func reset()
  1232. }
  1233. /// A floating-point counter handler represents a backend implementation of a floating-point counter.
  1234. ///
  1235. /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
  1236. /// To use the SwiftMetrics API, please refer to the documentation of `FloatingPointCounter`.
  1237. ///
  1238. /// ### Implementation requirements
  1239. ///
  1240. /// To implement your own `FloatingPointCounterHandler` you should respect a few requirements that are necessary so applications work
  1241. /// as expected regardless of the selected `FloatingPointCounterHandler` implementation.
  1242. ///
  1243. /// - The `FloatingPointCounterHandler` must be a `class`.
  1244. public protocol FloatingPointCounterHandler: AnyObject, _SwiftMetricsSendableProtocol {
  1245. /// Increment the counter.
  1246. ///
  1247. /// - parameters:
  1248. /// - by: Amount to increment by.
  1249. func increment(by: Double)
  1250. /// Reset the counter back to zero.
  1251. func reset()
  1252. }
  1253. /// A recorder handler represents a backend implementation of a recorder.
  1254. ///
  1255. /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
  1256. /// To use the SwiftMetrics API, please refer to the documentation of `Recorder`.
  1257. ///
  1258. /// The `aggregate` argument to ``MetricsFactory/makeRecorder(label:dimensions:aggregate:)``
  1259. /// selects one of two modes for the handler:
  1260. ///
  1261. /// - **Aggregating** (`aggregate: true`): Each call to `record(_:)` contributes a distribution
  1262. /// that the backend summarizes. This is the default ``Recorder`` behavior.
  1263. /// - **Non-aggregating** (`aggregate: false`): Each call to `record(_:)` replaces the previous value.
  1264. /// This is the behavior ``Gauge`` expects.
  1265. ///
  1266. /// A single handler instance is bound to one mode — the flag is consumed by the factory
  1267. /// when the handler is created, not on each call.
  1268. ///
  1269. /// ### Implementation requirements
  1270. ///
  1271. /// To implement your own `RecorderHandler` you should respect a few requirements that are necessary so applications work
  1272. /// as expected regardless of the selected `RecorderHandler` implementation.
  1273. ///
  1274. /// - The `RecorderHandler` must be a `class`.
  1275. public protocol RecorderHandler: AnyObject, _SwiftMetricsSendableProtocol {
  1276. /// Record an integer value.
  1277. ///
  1278. /// - parameters:
  1279. /// - value: Value to record.
  1280. func record(_ value: Int64)
  1281. /// Record a floating-point value.
  1282. ///
  1283. /// - parameters:
  1284. /// - value: Value to record.
  1285. func record(_ value: Double)
  1286. }
  1287. /// A meter handler represents a backend implementation of a meter.
  1288. ///
  1289. /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
  1290. /// To use the SwiftMetrics API, please refer to the documentation of `Meter`.
  1291. ///
  1292. /// ### Implementation requirements
  1293. ///
  1294. /// To implement your own `MeterHandler` you should respect a few requirements that are necessary so applications work
  1295. /// as expected regardless of the selected `MeterHandler` implementation.
  1296. ///
  1297. /// - The `RecorderHandler` must be a `class`.
  1298. public protocol MeterHandler: AnyObject, _SwiftMetricsSendableProtocol {
  1299. /// Set an integer value.
  1300. ///
  1301. /// - parameters:
  1302. /// - value: Value to set.
  1303. func set(_ value: Int64)
  1304. /// Set a floating-point value.
  1305. ///
  1306. /// - parameters:
  1307. /// - value: Value to set.
  1308. func set(_ value: Double)
  1309. /// Increment the value.
  1310. ///
  1311. /// - parameters:
  1312. /// - by: Amount to increment by.
  1313. func increment(by: Double)
  1314. /// Decrement the value.
  1315. ///
  1316. /// - parameters:
  1317. /// - by: Amount to increment by.
  1318. func decrement(by: Double)
  1319. }
  1320. /// A timer handler represents a backend implementation of a timer.
  1321. ///
  1322. /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
  1323. /// To use the SwiftMetrics API, please refer to the documentation of `Timer`.
  1324. ///
  1325. /// ### Implementation requirements
  1326. ///
  1327. /// To implement your own `TimerHandler` you should respect a few requirements that are necessary so applications work
  1328. /// as expected regardless of the selected `TimerHandler` implementation.
  1329. ///
  1330. /// - The `TimerHandler` must be a `class`.
  1331. public protocol TimerHandler: AnyObject, _SwiftMetricsSendableProtocol {
  1332. /// Record a duration in nanoseconds.
  1333. ///
  1334. /// - parameters:
  1335. /// - duration: Duration to record.
  1336. func recordNanoseconds(_ duration: Int64)
  1337. /// Set the preferred display unit for this timer handler.
  1338. ///
  1339. /// - parameters:
  1340. /// - unit: A hint to the backend responsible for presenting the data of the preferred display unit. This is not guaranteed to be supported by all backends.
  1341. func preferDisplayUnit(_ unit: TimeUnit)
  1342. }
  1343. extension TimerHandler {
  1344. /// Set the preferred display unit for this timer handler.
  1345. /// - Parameter unit: A hint to the backend responsible for presenting the data of the preferred display unit. This is not guaranteed to be supported by all backends.
  1346. public func preferDisplayUnit(_ unit: TimeUnit) {
  1347. // NOOP
  1348. }
  1349. }
  1350. // MARK: - Predefined Metrics Handlers
  1351. /// A pseudo-metrics handler that can be used to send messages to multiple other metrics handlers.
  1352. public final class MultiplexMetricsHandler: MetricsFactory {
  1353. private let factories: [MetricsFactory]
  1354. /// Creates a new multiplex metrics handler from the metric factories you provide.
  1355. /// - Parameter factories: The metric factories to multiplex together.
  1356. public init(factories: [MetricsFactory]) {
  1357. self.factories = factories
  1358. }
  1359. /// Creates a new counter handler.
  1360. /// - Parameters:
  1361. /// - label: The label for the `CounterHandler`.
  1362. /// - dimensions: The dimensions for the `CounterHandler`, as `(name, value)` tuples.
  1363. public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
  1364. MuxCounter(factories: self.factories, label: label, dimensions: dimensions)
  1365. }
  1366. /// Creates a new floating point counter handler.
  1367. /// - Parameters:
  1368. /// - label: The label for the `FloatingPointCounterHandler`.
  1369. /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
  1370. public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
  1371. MuxFloatingPointCounter(factories: self.factories, label: label, dimensions: dimensions)
  1372. }
  1373. /// Creates a new meter handler.
  1374. /// - Parameters:
  1375. /// - label: The label for the `MeterHandler`.
  1376. /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
  1377. public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
  1378. MuxMeter(factories: self.factories, label: label, dimensions: dimensions)
  1379. }
  1380. /// Creates a new recorder handler.
  1381. /// - Parameters:
  1382. /// - label: The label for the `RecorderHandler`.
  1383. /// - dimensions: The dimensions for the `RecorderHandler`, as `(name, value)` tuples.
  1384. /// - aggregate: A Boolean value that indicates whether to aggregate values.
  1385. public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
  1386. MuxRecorder(factories: self.factories, label: label, dimensions: dimensions, aggregate: aggregate)
  1387. }
  1388. /// Creates a new timer handler.
  1389. /// - Parameters:
  1390. /// - label: The label for the `TimerHandler`.
  1391. /// - dimensions: The dimensions for the `TimerHandler`, as `(name, value)` tuples.
  1392. public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
  1393. MuxTimer(factories: self.factories, label: label, dimensions: dimensions)
  1394. }
  1395. /// Signal the underlying metrics library that this counter will never be updated again.
  1396. /// - Parameter handler: The counter handler to signal.
  1397. public func destroyCounter(_ handler: CounterHandler) {
  1398. guard let mux = handler as? MuxCounter else { return }
  1399. for (factory, counter) in zip(self.factories, mux.counters) {
  1400. factory.destroyCounter(counter)
  1401. }
  1402. }
  1403. /// Signal the underlying metrics library that this floating point counter will never be updated again.
  1404. /// - Parameter handler: The floating point counter handler to signal.
  1405. public func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler) {
  1406. guard let mux = handler as? MuxFloatingPointCounter else { return }
  1407. for (factory, counter) in zip(self.factories, mux.counters) {
  1408. factory.destroyFloatingPointCounter(counter)
  1409. }
  1410. }
  1411. /// Signal the underlying metrics library that this meter will never be updated again.
  1412. /// - Parameter handler: The meter handler to signal.
  1413. public func destroyMeter(_ handler: MeterHandler) {
  1414. guard let mux = handler as? MuxMeter else { return }
  1415. for (factory, meter) in zip(self.factories, mux.meters) {
  1416. factory.destroyMeter(meter)
  1417. }
  1418. }
  1419. /// Signal the underlying metrics library that this recorder will never be updated again.
  1420. /// - Parameter handler: The recorder handler to signal.
  1421. public func destroyRecorder(_ handler: RecorderHandler) {
  1422. guard let mux = handler as? MuxRecorder else { return }
  1423. for (factory, recorder) in zip(self.factories, mux.recorders) {
  1424. factory.destroyRecorder(recorder)
  1425. }
  1426. }
  1427. /// Signal the underlying metrics library that this timer will never be updated again.
  1428. /// - Parameter handler: The timer handler to signal.
  1429. public func destroyTimer(_ handler: TimerHandler) {
  1430. guard let mux = handler as? MuxTimer else { return }
  1431. for (factory, timer) in zip(self.factories, mux.timers) {
  1432. factory.destroyTimer(timer)
  1433. }
  1434. }
  1435. private final class MuxCounter: CounterHandler {
  1436. let counters: [CounterHandler]
  1437. public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
  1438. self.counters = factories.map { $0.makeCounter(label: label, dimensions: dimensions) }
  1439. }
  1440. func increment(by amount: Int64) {
  1441. for counter in self.counters { counter.increment(by: amount) }
  1442. }
  1443. func reset() {
  1444. for counter in self.counters { counter.reset() }
  1445. }
  1446. }
  1447. private final class MuxFloatingPointCounter: FloatingPointCounterHandler {
  1448. let counters: [FloatingPointCounterHandler]
  1449. public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
  1450. self.counters = factories.map { $0.makeFloatingPointCounter(label: label, dimensions: dimensions) }
  1451. }
  1452. func increment(by amount: Double) {
  1453. for counter in self.counters { counter.increment(by: amount) }
  1454. }
  1455. func reset() {
  1456. for counter in self.counters { counter.reset() }
  1457. }
  1458. }
  1459. private final class MuxMeter: MeterHandler {
  1460. let meters: [MeterHandler]
  1461. public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
  1462. self.meters = factories.map { $0.makeMeter(label: label, dimensions: dimensions) }
  1463. }
  1464. func set(_ value: Int64) {
  1465. for meter in self.meters { meter.set(value) }
  1466. }
  1467. func set(_ value: Double) {
  1468. for meter in self.meters { meter.set(value) }
  1469. }
  1470. func increment(by amount: Double) {
  1471. for meter in self.meters { meter.increment(by: amount) }
  1472. }
  1473. func decrement(by amount: Double) {
  1474. for meter in self.meters { meter.decrement(by: amount) }
  1475. }
  1476. }
  1477. private final class MuxRecorder: RecorderHandler {
  1478. let recorders: [RecorderHandler]
  1479. public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)], aggregate: Bool) {
  1480. self.recorders = factories.map {
  1481. $0.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
  1482. }
  1483. }
  1484. func record(_ value: Int64) {
  1485. for recorder in self.recorders { recorder.record(value) }
  1486. }
  1487. func record(_ value: Double) {
  1488. for recorder in self.recorders { recorder.record(value) }
  1489. }
  1490. }
  1491. private final class MuxTimer: TimerHandler {
  1492. let timers: [TimerHandler]
  1493. public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
  1494. self.timers = factories.map { $0.makeTimer(label: label, dimensions: dimensions) }
  1495. }
  1496. func recordNanoseconds(_ duration: Int64) {
  1497. for timer in self.timers { timer.recordNanoseconds(duration) }
  1498. }
  1499. func preferDisplayUnit(_ unit: TimeUnit) {
  1500. for timer in self.timers { timer.preferDisplayUnit(unit) }
  1501. }
  1502. }
  1503. }
  1504. /// A metrics handler that implements the protocols but does nothing.
  1505. ///
  1506. /// The no-op metrics handler ships with the metrics module, and is used by default unless a different metrics backend is bootstrapped.
  1507. public final class NOOPMetricsHandler: MetricsFactory, CounterHandler, FloatingPointCounterHandler, MeterHandler,
  1508. RecorderHandler, TimerHandler
  1509. {
  1510. /// A sharable instance of a No-op metrics handler.
  1511. public static let instance = NOOPMetricsHandler()
  1512. private init() {}
  1513. public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
  1514. self
  1515. }
  1516. public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
  1517. self
  1518. }
  1519. public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
  1520. self
  1521. }
  1522. public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
  1523. self
  1524. }
  1525. public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
  1526. self
  1527. }
  1528. public func destroyCounter(_: CounterHandler) {}
  1529. public func destroyFloatingPointCounter(_: FloatingPointCounterHandler) {}
  1530. public func destroyMeter(_: MeterHandler) {}
  1531. public func destroyRecorder(_: RecorderHandler) {}
  1532. public func destroyTimer(_: TimerHandler) {}
  1533. public func increment(by: Int64) {}
  1534. public func increment(by: Double) {}
  1535. public func decrement(by: Double) {}
  1536. public func reset() {}
  1537. public func record(_: Int64) {}
  1538. public func record(_: Double) {}
  1539. public func recordNanoseconds(_: Int64) {}
  1540. public func set(_: Int64) {}
  1541. public func set(_: Double) {}
  1542. }
  1543. // MARK: - Sendable support helpers
  1544. extension MetricsSystem: Sendable {}
  1545. extension Counter: Sendable {}
  1546. extension FloatingPointCounter: Sendable {}
  1547. // must be @unchecked since Gauge inherits Recorder :(
  1548. extension Recorder: @unchecked Sendable {}
  1549. extension Timer: Sendable {}
  1550. extension Meter: Sendable {}
  1551. // ideally we would not be using @unchecked here, but concurrency-safety checks do not recognize locks
  1552. extension AccumulatingRoundingFloatingPointCounter: @unchecked Sendable {}
  1553. @preconcurrency public protocol _SwiftMetricsSendableProtocol: Sendable {}