| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733 |
- //===----------------------------------------------------------------------===//
- //
- // This source file is part of the Swift Metrics API open source project
- //
- // Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
- // Licensed under Apache License v2.0
- //
- // See LICENSE.txt for license information
- // See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
- //
- // SPDX-License-Identifier: Apache-2.0
- //
- //===----------------------------------------------------------------------===//
- // MARK: - User API
- // MARK: - Counter
- /// A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero.
- ///
- /// For example, you can use a counter to represent the number of requests served, tasks completed, or errors.
- ///
- /// This is the user-facing counter API.
- /// Its behavior is defined by the ``CounterHandler`` implementation.
- ///
- /// Increment a counter:
- ///
- /// ```swift
- /// counter.increment(by: 5)
- /// counter.increment() // increments counter by 1
- /// ```
- ///
- /// Reset a counter:
- ///
- /// ```swift
- /// counter.reset()
- /// ````
- public final class Counter {
- /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
- /// Do not consider them part of the public API.
- @_documentation(visibility: internal)
- public let _handler: CounterHandler
- @_documentation(visibility: internal)
- @usableFromInline
- package let _factory: MetricsFactory
- /// The label for the counter.
- public let label: String
- /// The dimensions for the counter, as `(name, value)` tuples.
- public let dimensions: [(String, String)]
- /// Alternative way to create a new counter with an explicit counter handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create `Counter` instances using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Counter`.
- /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
- /// - handler: The custom backend.
- /// - factory: The custom metrics factory.
- public init(label: String, dimensions: [(String, String)], handler: CounterHandler, factory: MetricsFactory) {
- self.label = label
- self.dimensions = dimensions
- self._handler = handler
- self._factory = factory
- }
- /// Alternative way to create a new counter, with an explicit counter handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Counter`` using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Counter`.
- /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
- /// - handler: The custom backend, created by the global metrics factory.
- public convenience init(label: String, dimensions: [(String, String)], handler: CounterHandler) {
- self.init(
- label: label,
- dimensions: dimensions,
- handler: handler,
- factory: MetricsSystem.factory
- )
- }
- /// Increment the counter.
- ///
- /// - parameters:
- /// - amount: Amount to increment by.
- @inlinable
- public func increment<DataType: BinaryInteger>(by amount: DataType) {
- self._handler.increment(by: Int64(amount))
- }
- /// Increment the counter by one.
- @inlinable
- public func increment() {
- self.increment(by: 1)
- }
- /// Reset the counter back to zero.
- @inlinable
- public func reset() {
- self._handler.reset()
- }
- }
- extension Counter {
- /// Create a new counter.
- ///
- /// - parameters:
- /// - label: The label for the `Counter`.
- /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
- public convenience init(label: String, dimensions: [(String, String)] = []) {
- self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
- }
- /// Create a new counter using a custom metrics factory that you provide.
- ///
- /// - parameters:
- /// - label: The label for the `Counter`.
- /// - dimensions: The dimensions for the `Counter`, as `(name, value)` tuples.
- /// - factory: The custom metrics factory.
- public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
- let handler = factory.makeCounter(label: label, dimensions: dimensions)
- self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
- }
- /// Signal the underlying metrics library that this counter will never be updated again.
- ///
- /// In response the library MAY decide to eagerly release any resources held by this counter.
- @inlinable
- public func destroy() {
- self._factory.destroyCounter(self._handler)
- }
- }
- extension Counter: CustomStringConvertible {
- public var description: String {
- "Counter(\(self.label), dimensions: \(self.dimensions))"
- }
- }
- // MARK: - FloatingPointCounter
- /// 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.
- ///
- /// For example, you can use a floating-point counter to represent the number of requests served, tasks completed, or errors.
- /// 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.
- ///
- /// This is the user-facing floating-point counter API.
- /// Its behavior depends on the ``FloatingPointCounterHandler`` implementation.
- ///
- /// Increment a counter:
- ///
- /// ```swift
- /// counter.increment(by: 5.1)
- /// counter.increment() // increments counter by 1
- /// ```
- ///
- /// Reset a counter:
- ///
- /// ```swift
- /// counter.reset()
- /// ````
- public final class FloatingPointCounter {
- /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
- /// Do not consider them part of the public API.
- @_documentation(visibility: internal)
- public let _handler: FloatingPointCounterHandler
- @_documentation(visibility: internal)
- @usableFromInline
- package let _factory: MetricsFactory
- /// The label for the floating point counter.
- public let label: String
- /// The dimensions for the floating point counter, as `(name, value)` tuples.
- public let dimensions: [(String, String)]
- /// Alternative way to create a new floating-point counter, while providing an explicit floating-point counter handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``FloatingPointCounter``using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `FloatingPointCounter`.
- /// - dimensions: The dimensions for the `FloatingPointCounter`.
- /// - handler: The custom backend.
- /// - factory: The custom metrics factory.
- public init(
- label: String,
- dimensions: [(String, String)],
- handler: FloatingPointCounterHandler,
- factory: MetricsFactory
- ) {
- self.label = label
- self.dimensions = dimensions
- self._handler = handler
- self._factory = factory
- }
- /// Alternative way to create a new floating-point counter, while providing an explicit floating-point counter handler..
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``FloatingPointCounter`` using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `FloatingPointCounter`.
- /// - dimensions: The dimensions for the `FloatingPointCounter`, as `(name, value)` tuples.
- /// - handler: The custom backend.
- public convenience init(label: String, dimensions: [(String, String)], handler: FloatingPointCounterHandler) {
- self.init(
- label: label,
- dimensions: dimensions,
- handler: handler,
- factory: MetricsSystem.factory
- )
- }
- /// Increment the floating-point counter.
- ///
- /// - parameters:
- /// - amount: Amount to increment by.
- @inlinable
- public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
- self._handler.increment(by: Double(amount))
- }
- /// Increment the floating-point counter by one.
- @inlinable
- public func increment() {
- self.increment(by: 1.0)
- }
- /// Reset the floating-point counter back to zero.
- @inlinable
- public func reset() {
- self._handler.reset()
- }
- }
- extension FloatingPointCounter {
- /// Create a new floating-point counter.
- ///
- /// - parameters:
- /// - label: The label for the `FloatingPointCounter`.
- /// - dimensions: The dimensions for the `FloatingPointCounter`, as `(name, value)` tuples.
- public convenience init(label: String, dimensions: [(String, String)] = []) {
- self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
- }
- /// Create a new floating-point counter using a custom metrics factory that you provide.
- ///
- /// - parameters:
- /// - label: The label for the `FloatingPointCounter`.
- /// - dimensions: The dimensions for the `FloatingPointCounter`, as `(name, value)` tuples.
- /// - factory: The custom metrics factory.
- public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
- let handler = factory.makeFloatingPointCounter(label: label, dimensions: dimensions)
- self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
- }
- /// Signal the underlying metrics library that this floating point counter will never be updated again.
- ///
- /// In response the library MAY decide to eagerly release any resources held by this `FloatingPointCounter`.
- @inlinable
- public func destroy() {
- self._factory.destroyFloatingPointCounter(self._handler)
- }
- }
- extension FloatingPointCounter: CustomStringConvertible {
- public var description: String {
- "FloatingPointCounter(\(self.label), dimensions: \(self.dimensions))"
- }
- }
- // MARK: - Gauge
- /// A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
- ///
- /// 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.
- /// Gauges are modeled as `Recorder` with a sample size of 1 and that does not perform any aggregation.
- ///
- /// Recording a value with a gauge:
- ///
- /// ```swift
- /// guage.record(100)
- /// ```
- public final class Gauge: Recorder, @unchecked Sendable {
- /// Create a new gauge.
- ///
- /// - parameters:
- /// - label: The label for the `Gauge`.
- /// - dimensions: The dimensions for the `Gauge`, as `(name, value)` tuples.
- public convenience init(label: String, dimensions: [(String, String)] = []) {
- self.init(label: label, dimensions: dimensions, aggregate: false)
- }
- /// Create a new gauge using a custom metrics factory that you provide.
- ///
- /// - parameters:
- /// - label: The label for the `Gauge`.
- /// - dimensions: The dimensions for the `Gauge`, as `(name, value)` tuples.
- /// - factory: The custom metrics factory.
- public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
- self.init(label: label, dimensions: dimensions, aggregate: false, factory: factory)
- }
- }
- // MARK: - Meter
- /// A meter is similar to a gauge, it is a metric that represents a single numerical value that can arbitrarily go up and down.
- ///
- /// 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.
- ///
- /// Recording a value with a meter:
- /// ```swift
- /// meter.record(100)
- /// ```
- public final class Meter {
- /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
- /// Do not consider them part of the public API.
- @_documentation(visibility: internal)
- public let _handler: MeterHandler
- @usableFromInline
- @_documentation(visibility: internal)
- package let _factory: MetricsFactory
- /// The label for the meter.
- public let label: String
- /// The dimensions for the meter, as `(name, value)` tuples.
- public let dimensions: [(String, String)]
- /// Alternative way to create a new meter, while providing an explicit meter handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Meter`` using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Recorder`.
- /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
- /// - handler: The custom backend.
- /// - factory: The custom metrics factory.
- public init(label: String, dimensions: [(String, String)], handler: MeterHandler, factory: MetricsFactory) {
- self.label = label
- self.dimensions = dimensions
- self._handler = handler
- self._factory = factory
- }
- /// Alternative way to create a new meter, while providing an explicit meter handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Meter`` using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Recorder`.
- /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
- /// - handler: The custom backend.
- public convenience init(label: String, dimensions: [(String, String)], handler: MeterHandler) {
- self.init(label: label, dimensions: dimensions, handler: handler, factory: MetricsSystem.factory)
- }
- /// Set an integer value.
- ///
- /// - parameters:
- /// - value: Value to set.
- @inlinable
- public func set<DataType: BinaryInteger>(_ value: DataType) {
- self._handler.set(Int64(value))
- }
- /// Set a floating-point value.
- ///
- /// - parameters:
- /// - value: Value to est.
- @inlinable
- public func set<DataType: BinaryFloatingPoint>(_ value: DataType) {
- self._handler.set(Double(value))
- }
- /// Increment the meter.
- ///
- /// - parameters:
- /// - amount: Amount to increment by.
- @inlinable
- public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
- self._handler.increment(by: Double(amount))
- }
- /// Increment the meter by one.
- @inlinable
- public func increment() {
- self.increment(by: 1.0)
- }
- /// Decrement the meter.
- ///
- /// - parameters:
- /// - amount: Amount to decrement by.
- @inlinable
- public func decrement<DataType: BinaryFloatingPoint>(by amount: DataType) {
- self._handler.decrement(by: Double(amount))
- }
- /// Decrement the meter by one.
- @inlinable
- public func decrement() {
- self.decrement(by: 1.0)
- }
- }
- extension Meter {
- /// Create a new meter.
- ///
- /// - parameters:
- /// - label: The label for the `Meter`.
- /// - dimensions: The dimensions for the `Meter`, as `(name, value)` tuples.
- /// - factory: The custom metrics factory.
- public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
- let handler = factory.makeMeter(label: label, dimensions: dimensions)
- self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
- }
- /// Create a new meter.
- ///
- /// - parameters:
- /// - label: The label for the `Meter`.
- /// - dimensions: The dimensions for the `Meter`, as `(name, value)` tuples.
- public convenience init(label: String, dimensions: [(String, String)] = []) {
- self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
- }
- /// Signal the underlying metrics library that this recorder will never be updated again.
- ///
- /// In response the library MAY decide to eagerly release any resources held by this `Recorder`.
- @inlinable
- public func destroy() {
- self._factory.destroyMeter(self._handler)
- }
- }
- extension Meter: CustomStringConvertible {
- public var description: String {
- "\(type(of: self))(\(self.label), dimensions: \(self.dimensions))"
- }
- }
- // MARK: - Recorder
- /// A recorder collects observations within a time window.
- ///
- /// An example is using a recorder to capture response sizes.
- /// A recorder *can* provide aggregated information about the data sample such as count, sum, min, max, and various quantiles.
- ///
- /// This is the user-facing Recorder API.
- /// Its behavior depends on the ``RecorderHandler`` implementation.
- ///
- /// Recording a value:
- ///
- /// ```swift
- /// recorder.record(101)
- /// ```
- public class Recorder {
- /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
- /// Do not consider them part of the public API.
- @_documentation(visibility: internal)
- public let _handler: RecorderHandler
- @_documentation(visibility: internal)
- @usableFromInline
- package let _factory: MetricsFactory
- /// The label for the recorder.
- public let label: String
- /// The dimensions for the recorder, as `(name, value)` tuples.
- public let dimensions: [(String, String)]
- /// Whether the backend summarizes recorded values as a distribution.
- public let aggregate: Bool
- /// Alternative way to create a new recorder, while providing an explicit recorder handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create `Recorder` instances using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Recorder`.
- /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
- /// - aggregate: A Boolean value that indicates whether to aggregate values.
- /// - handler: The custom backend.
- /// - factory: The custom metrics factory.
- public init(
- label: String,
- dimensions: [(String, String)],
- aggregate: Bool,
- handler: RecorderHandler,
- factory: MetricsFactory
- ) {
- self.label = label
- self.dimensions = dimensions
- self.aggregate = aggregate
- self._handler = handler
- self._factory = factory
- }
- /// Alternative way to create a new recorder, while providing an explicit recorder handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create `Recorder` instances using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Recorder`.
- /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
- /// - aggregate: A Boolean value that indicates whether to aggregate values.
- /// - handler: The custom backend.
- public convenience init(label: String, dimensions: [(String, String)], aggregate: Bool, handler: RecorderHandler) {
- self.init(
- label: label,
- dimensions: dimensions,
- aggregate: aggregate,
- handler: handler,
- factory: MetricsSystem.factory
- )
- }
- /// Record a value.
- ///
- /// Recording a value is meant to have "set" semantics, rather than "add" semantics.
- /// This means that the value of this `Recorder` will match the passed in value, rather than accumulate and sum the values up.
- ///
- /// - parameters:
- /// - value: Value to record.
- @inlinable
- public func record<DataType: BinaryInteger>(_ value: DataType) {
- self._handler.record(Int64(value))
- }
- /// Record a value.
- ///
- /// Recording a value is meant to have "set" semantics, rather than "add" semantics.
- /// This means that the value of this `Recorder` will match the passed in value, rather than accumulate and sum the values up.
- ///
- /// - parameters:
- /// - value: Value to record.
- @inlinable
- public func record<DataType: BinaryFloatingPoint>(_ value: DataType) {
- self._handler.record(Double(value))
- }
- }
- extension Recorder {
- /// Create a new recorder.
- ///
- /// - parameters:
- /// - label: The label for the `Recorder`.
- /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
- /// - aggregate: A Boolean value that indicates whether to aggregate values.
- public convenience init(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true) {
- self.init(label: label, dimensions: dimensions, aggregate: aggregate, factory: MetricsSystem.factory)
- }
- /// Create a new recorder using a custom metrics factory that you provide..
- ///
- /// - parameters:
- /// - label: The label for the `Recorder`.
- /// - dimensions: The dimensions for the `Recorder`, as `(name, value)` tuples.
- /// - aggregate: A Boolean value that indicates whether to aggregate values.
- /// - factory: The custom metrics factory.
- public convenience init(
- label: String,
- dimensions: [(String, String)] = [],
- aggregate: Bool = true,
- factory: MetricsFactory
- ) {
- let handler = factory.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
- self.init(label: label, dimensions: dimensions, aggregate: aggregate, handler: handler, factory: factory)
- }
- /// Signal the underlying metrics library that this recorder will never be updated again.
- ///
- /// In response the library MAY decide to eagerly release any resources held by this `Recorder`.
- @inlinable
- public func destroy() {
- self._factory.destroyRecorder(self._handler)
- }
- }
- extension Recorder: CustomStringConvertible {
- public var description: String {
- "\(type(of: self))(\(self.label), dimensions: \(self.dimensions), aggregate: \(self.aggregate))"
- }
- }
- // MARK: - Timer
- /// A unit of time.
- public struct TimeUnit: Equatable, Sendable {
- private enum Code: Equatable, Sendable {
- case nanoseconds
- case microseconds
- case milliseconds
- case seconds
- case minutes
- case hours
- case days
- }
- private let code: Code
- /// The number of nanoseconds in this time unit.
- public let scaleFromNanoseconds: UInt64
- private init(code: Code, scaleFromNanoseconds: UInt64) {
- assert(scaleFromNanoseconds > 0, "invalid scale from nanoseconds")
- self.code = code
- self.scaleFromNanoseconds = scaleFromNanoseconds
- }
- /// A nanosecond.
- public static let nanoseconds = TimeUnit(code: .nanoseconds, scaleFromNanoseconds: 1)
- /// A microsecond.
- public static let microseconds = TimeUnit(code: .microseconds, scaleFromNanoseconds: 1000)
- /// A millisecond.
- public static let milliseconds = TimeUnit(
- code: .milliseconds,
- scaleFromNanoseconds: 1000 * TimeUnit.microseconds.scaleFromNanoseconds
- )
- /// A second.
- public static let seconds = TimeUnit(
- code: .seconds,
- scaleFromNanoseconds: 1000 * TimeUnit.milliseconds.scaleFromNanoseconds
- )
- /// A minute.
- public static let minutes = TimeUnit(
- code: .minutes,
- scaleFromNanoseconds: 60 * TimeUnit.seconds.scaleFromNanoseconds
- )
- /// An hour.
- public static let hours = TimeUnit(code: .hours, scaleFromNanoseconds: 60 * TimeUnit.minutes.scaleFromNanoseconds)
- /// A day.
- public static let days = TimeUnit(code: .days, scaleFromNanoseconds: 24 * TimeUnit.hours.scaleFromNanoseconds)
- }
- /// A timer collects observations that represents durations within a time window.
- ///
- /// It is similar to a `Recorder` but specialized for values that represent durations, such as request durations.
- /// A timer provides aggregated information about the data sample, such as min, max, and various quantiles.
- ///
- /// This is the user-facing Timer API.
- /// Its behavior depends on the ``TimerHandler`` implementation.
- ///
- /// Recording a value:
- ///
- /// ```swift
- /// timer.recordMilliseconds(52)
- /// ```
- public final class Timer {
- /// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
- /// Do not consider them part of the public API.
- @_documentation(visibility: internal)
- public let _handler: TimerHandler
- @_documentation(visibility: internal)
- @usableFromInline
- package let _factory: MetricsFactory
- /// The label for the timer.
- public let label: String
- /// The dimensions for the timer, as `(name, value)` tuples.
- public let dimensions: [(String, String)]
- /// Alternative way to create a new timer, while providing an explicit timer handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Timer`` using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Timer`.
- /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
- /// - handler: The custom backend.
- /// - factory: The custom factory.
- public init(label: String, dimensions: [(String, String)], handler: TimerHandler, factory: MetricsFactory) {
- self.label = label
- self.dimensions = dimensions
- self._handler = handler
- self._factory = factory
- }
- /// Alternative way to create a new timer, while providing an explicit timer handler.
- ///
- /// - SeeAlso: Use `init(label:dimensions:)` to create instances of ``Timer`` using the configured metrics backend.
- ///
- /// - parameters:
- /// - label: The label for the `Timer`.
- /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
- /// - handler: The custom backend.
- public convenience init(label: String, dimensions: [(String, String)], handler: TimerHandler) {
- self.init(label: label, dimensions: dimensions, handler: handler, factory: MetricsSystem.factory)
- }
- /// Record a duration in nanoseconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordNanoseconds(_ duration: Int64) {
- self._handler.recordNanoseconds(duration)
- }
- /// Record a duration in nanoseconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordNanoseconds<DataType: BinaryInteger>(_ duration: DataType) {
- self.recordNanoseconds(duration >= Int64.max ? Int64.max : Int64(duration))
- }
- /// Record a duration in microseconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
- guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
- let result = Int64(duration).multipliedReportingOverflow(by: 1000)
- if result.overflow {
- self.recordNanoseconds(Int64.max)
- } else {
- self.recordNanoseconds(result.partialValue)
- }
- }
- /// Record a duration in microseconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordMicroseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
- self.recordNanoseconds(Double(duration * 1000) < Double(Int64.max) ? Int64(duration * 1000) : Int64.max)
- }
- /// Record a duration in milliseconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
- guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
- let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000)
- if result.overflow {
- self.recordNanoseconds(Int64.max)
- } else {
- self.recordNanoseconds(result.partialValue)
- }
- }
- /// Record a duration in milliseconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordMilliseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
- self.recordNanoseconds(
- Double(duration * 1_000_000) < Double(Int64.max) ? Int64(duration * 1_000_000) : Int64.max
- )
- }
- /// Record a duration in seconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
- guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
- let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000_000)
- if result.overflow {
- self.recordNanoseconds(Int64.max)
- } else {
- self.recordNanoseconds(result.partialValue)
- }
- }
- /// Record a duration in seconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- @inlinable
- public func recordSeconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
- self.recordNanoseconds(
- Double(duration * 1_000_000_000) < Double(Int64.max) ? Int64(duration * 1_000_000_000) : Int64.max
- )
- }
- }
- extension Timer {
- /// Create a new timer.
- ///
- /// - parameters:
- /// - label: The label for the `Timer`.
- /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
- /// - factory: The custom factory.
- public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
- let handler = factory.makeTimer(label: label, dimensions: dimensions)
- self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
- }
- /// Create a new timer using a custom metrics factory that you provide..
- ///
- /// - parameters:
- /// - label: The label for the `Timer`.
- /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
- public convenience init(label: String, dimensions: [(String, String)] = []) {
- self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
- }
- /// Create a new timer.
- ///
- /// - parameters:
- /// - label: The label for the `Timer`.
- /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
- /// - 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.
- /// - factory: The custom factory.
- public convenience init(
- label: String,
- dimensions: [(String, String)] = [],
- preferredDisplayUnit displayUnit: TimeUnit,
- factory: MetricsFactory
- ) {
- let handler = factory.makeTimer(label: label, dimensions: dimensions)
- handler.preferDisplayUnit(displayUnit)
- self.init(label: label, dimensions: dimensions, handler: handler, factory: factory)
- }
- /// Create a new timer.
- ///
- /// - parameters:
- /// - label: The label for the `Timer`.
- /// - dimensions: The dimensions for the `Timer`, as `(name, value)` tuples.
- /// - 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.
- public convenience init(
- label: String,
- dimensions: [(String, String)] = [],
- preferredDisplayUnit displayUnit: TimeUnit
- ) {
- self.init(
- label: label,
- dimensions: dimensions,
- preferredDisplayUnit: displayUnit,
- factory: MetricsSystem.factory
- )
- }
- /// Signal the underlying metrics library that this timer will never be updated again.
- ///
- /// In response the library MAY decide to eagerly release any resources held by this `Timer`.
- @inlinable
- public func destroy() {
- self._factory.destroyTimer(self._handler)
- }
- }
- extension Timer: CustomStringConvertible {
- public var description: String {
- "Timer(\(self.label), dimensions: \(self.dimensions))"
- }
- }
- // MARK: - MetricsSystem
- /// A global facility where the default metrics backend implementation is configured.
- ///
- /// `MetricsSystem` is set up just once in a given program to create the desired metrics backend
- /// implementation using ``MetricsFactory``.
- public enum MetricsSystem {
- private static let _factory = FactoryBox(NOOPMetricsHandler.instance)
- /// A one-time configuration function which globally selects the desired metrics backend
- /// implementation.
- ///
- /// `bootstrap` can be called at maximum once in any given program, calling it more than once will
- /// lead to undefined behavior, most likely a crash.
- ///
- /// - parameters:
- /// - factory: A factory that given an identifier produces instances of metrics handlers such as ``CounterHandler``, ``RecorderHandler``, or ``TimerHandler``.
- public static func bootstrap(_ factory: MetricsFactory) {
- self._factory.replaceFactory(factory, validate: true)
- }
- // for our testing we want to allow multiple bootstrapping
- internal static func bootstrapInternal(_ factory: MetricsFactory) {
- self._factory.replaceFactory(factory, validate: false)
- }
- /// Returns the currently used metrics factory.
- ///
- /// If a task-local factory was bound by using `withMetricsFactory(_:_:)` this factory is returned, otherwise the globally bootstrapped factory will be returned.
- /// This is useful for passing the current factory to APIs that expect an explicit factory parameter.
- ///
- /// ## Example: Passing current factory to explicit API
- ///
- /// ```swift
- /// // Library API that requires explicit factory
- /// func createMetricWithExplicitFactory(label: String, factory: MetricsFactory) -> Counter {
- /// Counter(label: label, factory: factory)
- /// }
- ///
- /// // Usage with task-local factory
- /// withMetricsFactory(testFactory) {
- /// // Pass current factory to API expecting explicit parameter
- /// let counter = createMetricWithExplicitFactory(
- /// label: "requests",
- /// factory: MetricsSystem.factory
- /// )
- /// }
- /// ```
- ///
- /// - Returns: The task-local factory if bound, otherwise the global factory.
- public static var factory: MetricsFactory {
- if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
- self._taskLocalFactory ?? self._factory.underlying
- } else {
- self._factory.underlying
- }
- }
- /// Task-local metrics factory override.
- ///
- /// Used internally by `withMetricsFactory(_:_:)` free functions.
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
- @TaskLocal
- @usableFromInline
- internal static var _taskLocalFactory: MetricsFactory?
- /// Returns the currently used metrics factory.
- ///
- /// If a task-local factory was bound by using `withMetricsFactory(_:_:)` this factory is returned, otherwise the globally bootstrapped factory will be returned.
- /// This is useful for passing the current factory to APIs that expect an explicit factory parameter.
- ///
- /// ## Example: Passing current factory to explicit API
- ///
- /// ```swift
- /// // Library API that requires explicit factory
- /// func createMetricWithExplicitFactory(label: String, factory: MetricsFactory) -> Counter {
- /// Counter(label: label, factory: factory)
- /// }
- ///
- /// // Usage with task-local factory
- /// withMetricsFactory(testFactory) {
- /// // Pass current factory to API expecting explicit parameter
- /// let counter = createMetricWithExplicitFactory(
- /// label: "requests",
- /// factory: MetricsSystem.currentFactory
- /// )
- /// }
- /// ```
- ///
- /// - Returns: The task-local factory if bound, otherwise the global factory.
- @available(*, deprecated, renamed: "factory")
- @inlinable
- public static var currentFactory: MetricsFactory {
- factory
- }
- /// Execute a closure with a factory bound to task-local storage.
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
- @usableFromInline
- internal static func withTaskLocalFactory<R>(
- _ factory: MetricsFactory,
- operation: () throws -> R
- ) rethrows -> R {
- try $_taskLocalFactory.withValue(factory, operation: operation)
- }
- /// Execute an async closure with a factory bound to task-local storage.
- #if compiler(>=6.2)
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
- @usableFromInline
- nonisolated(nonsending)
- internal static func withTaskLocalFactory<R>(
- _ factory: MetricsFactory,
- operation: nonisolated(nonsending) () async throws -> R
- ) async rethrows -> R
- {
- try await $_taskLocalFactory.withValue(factory, operation: operation)
- }
- #else
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
- @usableFromInline
- internal static func withTaskLocalFactory<R>(
- _ factory: MetricsFactory,
- operation: () async throws -> R
- ) async rethrows -> R {
- try await $_taskLocalFactory.withValue(factory, operation: operation)
- }
- #endif
- /// Acquire a writer lock for the duration of the given block.
- ///
- /// - Parameter body: The block to execute while holding the lock.
- /// - Returns: The value returned by the block.
- public static func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
- try self._factory.withWriterLock(body)
- }
- // This can be `@unchecked Sendable` because we're manually gating access to mutable state with a lock.
- private final class FactoryBox: @unchecked Sendable {
- private let lock = ReadWriteLock()
- fileprivate var _underlying: MetricsFactory
- private var initialized = false
- init(_ underlying: MetricsFactory) {
- self._underlying = underlying
- }
- func replaceFactory(_ factory: MetricsFactory, validate: Bool) {
- self.lock.withWriterLock {
- precondition(
- !validate || !self.initialized,
- "metrics system can only be initialized once per process. currently used factory: \(self._underlying)"
- )
- self._underlying = factory
- self.initialized = true
- }
- }
- var underlying: MetricsFactory {
- self.lock.withReaderLock {
- self._underlying
- }
- }
- func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
- try self.lock.withWriterLock(body)
- }
- }
- }
- // MARK: - Library SPI, intended to be implemented by backend libraries
- // MARK: - MetricsFactory
- /// The `MetricsFactory` is the bridge between the `MetricsSystem` and the metrics backend implementation.
- ///
- /// The role of `MetricsFactory` is to initialize concrete implementations of the various metric types:
- /// * `Counter` -> `CounterHandler`
- /// * `FloatingPointCounter` -> `FloatingPointCounterHandler`
- /// * `Recorder` -> `RecorderHandler`
- /// * `Timer` -> `TimerHandler`
- ///
- /// To use the SwiftMetrics API, please refer to the documentation of `MetricsSystem`.
- ///
- /// ### Destroying metrics
- ///
- /// Since _some_ metrics implementations may need to allocate (potentially "heavy") resources for metrics, destroying
- /// metrics offers a signal to libraries when a metric is "known to never be updated again."
- ///
- /// While many metrics are bound to the entire lifetime of an application and thus never need to be destroyed eagerly,
- /// some metrics have well defined unique life-cycles where it may be beneficial to release any resources held by them
- /// more eagerly than awaiting the application's termination. In such cases, a library or application should invoke
- /// a metric's appropriate `destroy()` method, which in turn results in the corresponding handler that it is backed by
- /// to be passed to `destroyCounter(handler:)`, `destroyRecorder(handler:)` or `destroyTimer(handler:)` where the factory
- /// can decide to free any corresponding resources.
- ///
- /// While some libraries may not need to implement this destroying as they may be stateless or similar,
- /// libraries using the metrics API should always assume a library WILL make use of this signal, and shall not
- /// neglect calling these methods when appropriate.
- public protocol MetricsFactory: _SwiftMetricsSendableProtocol {
- /// Create a backing counter handler.
- ///
- /// - parameters:
- /// - label: The label for the `CounterHandler`.
- /// - dimensions: The dimensions for the `CounterHandler`, as `(name, value)` tuples.
- func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler
- /// Create a backing floating-point handler.
- ///
- /// - parameters:
- /// - label: The label for the `FloatingPointCounterHandler`.
- /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
- func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler
- /// Create a backing meter handler.
- ///
- /// - parameters:
- /// - label: The label for the `MeterHandler`.
- /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
- func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler
- /// Create a backing recorder handler.
- ///
- /// - parameters:
- /// - label: The label for the `RecorderHandler`.
- /// - dimensions: The dimensions for the `RecorderHandler`, as `(name, value)` tuples.
- /// - aggregate: Whether the returned handler should summarize recorded values as a distribution.
- func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler
- /// Create a backing timer handler.
- ///
- /// - parameters:
- /// - label: The label for the `TimerHandler`.
- /// - dimensions: The dimensions for the `TimerHandler`, as `(name, value)` tuples.
- func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler
- /// Invoked when the corresponding counter's `destroy()` function is invoked.
- ///
- /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- func destroyCounter(_ handler: CounterHandler)
- /// Invoked when the corresponding meter's `destroy()` function is invoked.
- ///
- /// Upon receiving this signal the factory may eagerly release any resources related to this recorder.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- func destroyMeter(_ handler: MeterHandler)
- /// Invoked when the corresponding floating-point counter's `destroy()` function is invoked.
- ///
- /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler)
- /// Invoked when the corresponding recorder's `destroy()` function is invoked.
- ///
- /// Upon receiving this signal the factory may eagerly release any resources related to this recorder.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- func destroyRecorder(_ handler: RecorderHandler)
- /// Invoked when the corresponding Timer's `destroy()` function is invoked.
- ///
- /// Upon receiving this signal the factory may eagerly release any resources related to this timer.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- func destroyTimer(_ handler: TimerHandler)
- }
- /// 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.
- internal final class AccumulatingRoundingFloatingPointCounter: FloatingPointCounterHandler {
- private let lock = Lock()
- private let counterHandler: CounterHandler
- private let factory: MetricsFactory
- internal var fraction: Double = 0
- init(label: String, dimensions: [(String, String)], factory: MetricsFactory) {
- self.counterHandler = factory.makeCounter(label: label, dimensions: dimensions)
- self.factory = factory
- }
- func increment(by amount: Double) {
- // Drop illegal values
- // - cannot increment by NaN
- guard !amount.isNaN else { return }
- // - cannot increment by infinite quantities
- guard !amount.isInfinite else { return }
- // - cannot increment by negative values
- guard amount.sign == .plus else { return }
- // - cannot increment by zero
- guard !amount.isZero else { return }
- if amount.exponent >= 63 {
- // If amount is in Int64.max..<+Inf, ceil to Int64.max
- self.lock.withLockVoid {
- self.counterHandler.increment(by: .max)
- }
- } else {
- // Split amount into integer and fraction components
- var (increment, fraction) = self.integerAndFractionComponents(of: amount)
- self.lock.withLockVoid {
- // Add the fractional component to the accumulated fraction.
- self.fraction += fraction
- // self.fraction may have cross an integer boundary, Split it
- // and add any integer component.
- let (integer, fraction) = integerAndFractionComponents(of: self.fraction)
- increment += integer
- self.fraction = fraction
- // Increment the handler by the total integer component.
- if increment > 0 {
- self.counterHandler.increment(by: increment)
- }
- }
- }
- }
- @inline(__always)
- private func integerAndFractionComponents(of value: Double) -> (Int64, Double) {
- let integer = Int64(value)
- let fraction = value - value.rounded(.towardZero)
- return (integer, fraction)
- }
- func reset() {
- self.lock.withLockVoid {
- self.fraction = 0
- self.counterHandler.reset()
- }
- }
- func destroy() {
- self.factory.destroyCounter(self.counterHandler)
- }
- }
- /// Wraps a RecorderHandler, adding support for incrementing values by storing an accumulated value and recording increments to the underlying CounterHandler after crossing integer boundaries.
- /// - 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.
- internal final class AccumulatingMeter: MeterHandler, @unchecked Sendable {
- private let recorderHandler: RecorderHandler
- // FIXME: use swift-atomics when floating point support is available
- private var value: Double = 0
- private let lock = Lock()
- private let factory: MetricsFactory
- init(label: String, dimensions: [(String, String)], factory: MetricsFactory) {
- self.recorderHandler = factory.makeRecorder(label: label, dimensions: dimensions, aggregate: true)
- self.factory = factory
- }
- func set(_ value: Int64) {
- self._set(Double(value))
- }
- func set(_ value: Double) {
- self._set(value)
- }
- func increment(by amount: Double) {
- // Drop illegal values
- // - cannot increment by NaN
- guard !amount.isNaN else {
- return
- }
- // - cannot increment by infinite quantities
- guard !amount.isInfinite else {
- return
- }
- // - cannot increment by negative values
- guard amount.sign == .plus else {
- return
- }
- // - cannot increment by zero
- guard !amount.isZero else {
- return
- }
- let newValue: Double = self.lock.withLock {
- self.value += amount
- return self.value
- }
- self.recorderHandler.record(newValue)
- }
- func decrement(by amount: Double) {
- // Drop illegal values
- // - cannot decrement by NaN
- guard !amount.isNaN else {
- return
- }
- // - cannot decrement by infinite quantities
- guard !amount.isInfinite else {
- return
- }
- // - cannot decrement by negative values
- guard amount.sign == .plus else {
- return
- }
- // - cannot decrement by zero
- guard !amount.isZero else {
- return
- }
- let newValue: Double = self.lock.withLock {
- self.value -= amount
- return self.value
- }
- self.recorderHandler.record(newValue)
- }
- private func _set(_ value: Double) {
- self.lock.withLockVoid {
- self.value = value
- }
- self.recorderHandler.record(value)
- }
- func destroy() {
- self.factory.destroyRecorder(self.recorderHandler)
- }
- }
- extension MetricsFactory {
- /// Create a default backing floating-point counter handler for backends which do not naively support floating point counters.
- ///
- /// 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.
- ///
- /// - parameters:
- /// - label: The label for the `FloatingPointCounterHandler`.
- /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
- public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
- AccumulatingRoundingFloatingPointCounter(label: label, dimensions: dimensions, factory: self)
- }
- /// Invoked when the corresponding floating-point counter's `destroy()` function is invoked.
- ///
- /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
- ///
- /// `destroyFloatingPointCounter` must be implemented if `makeFloatingPointCounter` is implemented.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- public func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler) {
- (handler as? AccumulatingRoundingFloatingPointCounter)?.destroy()
- }
- }
- extension MetricsFactory {
- /// Create a default backing meter handler for backends which do not naively support meters.
- ///
- /// The created MeterHandler is a wrapper around a backend's RecorderHandler which records current values.
- ///
- /// - parameters:
- /// - label: The label for the `MeterHandler`.
- /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
- public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
- AccumulatingMeter(label: label, dimensions: dimensions, factory: self)
- }
- /// Invoked when the corresponding meter's `destroy()` function is invoked.
- ///
- /// Upon receiving this signal the factory may eagerly release any resources related to this counter.
- ///
- /// `destroyMeter` must be implemented if `makeMeter` is implemented.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- public func destroyMeter(_ handler: MeterHandler) {
- (handler as? AccumulatingMeter)?.destroy()
- }
- }
- // MARK: - Backend Handlers
- /// A counter handler represents a backend implementation of a counter.
- ///
- /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
- /// To use the SwiftMetrics API, please refer to the documentation of `Counter`.
- ///
- /// ### Implementation requirements
- ///
- /// To implement your own `CounterHandler` you should respect a few requirements that are necessary so applications work
- /// as expected regardless of the selected `CounterHandler` implementation.
- ///
- /// - The `CounterHandler` must be a `class`.
- public protocol CounterHandler: AnyObject, _SwiftMetricsSendableProtocol {
- /// Increment the counter.
- ///
- /// - parameters:
- /// - by: Amount to increment by.
- func increment(by: Int64)
- /// Reset the counter back to zero.
- func reset()
- }
- /// A floating-point counter handler represents a backend implementation of a floating-point counter.
- ///
- /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
- /// To use the SwiftMetrics API, please refer to the documentation of `FloatingPointCounter`.
- ///
- /// ### Implementation requirements
- ///
- /// To implement your own `FloatingPointCounterHandler` you should respect a few requirements that are necessary so applications work
- /// as expected regardless of the selected `FloatingPointCounterHandler` implementation.
- ///
- /// - The `FloatingPointCounterHandler` must be a `class`.
- public protocol FloatingPointCounterHandler: AnyObject, _SwiftMetricsSendableProtocol {
- /// Increment the counter.
- ///
- /// - parameters:
- /// - by: Amount to increment by.
- func increment(by: Double)
- /// Reset the counter back to zero.
- func reset()
- }
- /// A recorder handler represents a backend implementation of a recorder.
- ///
- /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
- /// To use the SwiftMetrics API, please refer to the documentation of `Recorder`.
- ///
- /// The `aggregate` argument to ``MetricsFactory/makeRecorder(label:dimensions:aggregate:)``
- /// selects one of two modes for the handler:
- ///
- /// - **Aggregating** (`aggregate: true`): Each call to `record(_:)` contributes a distribution
- /// that the backend summarizes. This is the default ``Recorder`` behavior.
- /// - **Non-aggregating** (`aggregate: false`): Each call to `record(_:)` replaces the previous value.
- /// This is the behavior ``Gauge`` expects.
- ///
- /// A single handler instance is bound to one mode — the flag is consumed by the factory
- /// when the handler is created, not on each call.
- ///
- /// ### Implementation requirements
- ///
- /// To implement your own `RecorderHandler` you should respect a few requirements that are necessary so applications work
- /// as expected regardless of the selected `RecorderHandler` implementation.
- ///
- /// - The `RecorderHandler` must be a `class`.
- public protocol RecorderHandler: AnyObject, _SwiftMetricsSendableProtocol {
- /// Record an integer value.
- ///
- /// - parameters:
- /// - value: Value to record.
- func record(_ value: Int64)
- /// Record a floating-point value.
- ///
- /// - parameters:
- /// - value: Value to record.
- func record(_ value: Double)
- }
- /// A meter handler represents a backend implementation of a meter.
- ///
- /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
- /// To use the SwiftMetrics API, please refer to the documentation of `Meter`.
- ///
- /// ### Implementation requirements
- ///
- /// To implement your own `MeterHandler` you should respect a few requirements that are necessary so applications work
- /// as expected regardless of the selected `MeterHandler` implementation.
- ///
- /// - The `RecorderHandler` must be a `class`.
- public protocol MeterHandler: AnyObject, _SwiftMetricsSendableProtocol {
- /// Set an integer value.
- ///
- /// - parameters:
- /// - value: Value to set.
- func set(_ value: Int64)
- /// Set a floating-point value.
- ///
- /// - parameters:
- /// - value: Value to set.
- func set(_ value: Double)
- /// Increment the value.
- ///
- /// - parameters:
- /// - by: Amount to increment by.
- func increment(by: Double)
- /// Decrement the value.
- ///
- /// - parameters:
- /// - by: Amount to increment by.
- func decrement(by: Double)
- }
- /// A timer handler represents a backend implementation of a timer.
- ///
- /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
- /// To use the SwiftMetrics API, please refer to the documentation of `Timer`.
- ///
- /// ### Implementation requirements
- ///
- /// To implement your own `TimerHandler` you should respect a few requirements that are necessary so applications work
- /// as expected regardless of the selected `TimerHandler` implementation.
- ///
- /// - The `TimerHandler` must be a `class`.
- public protocol TimerHandler: AnyObject, _SwiftMetricsSendableProtocol {
- /// Record a duration in nanoseconds.
- ///
- /// - parameters:
- /// - duration: Duration to record.
- func recordNanoseconds(_ duration: Int64)
- /// Set the preferred display unit for this timer handler.
- ///
- /// - parameters:
- /// - 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.
- func preferDisplayUnit(_ unit: TimeUnit)
- }
- extension TimerHandler {
- /// Set the preferred display unit for this timer handler.
- /// - 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.
- public func preferDisplayUnit(_ unit: TimeUnit) {
- // NOOP
- }
- }
- // MARK: - Predefined Metrics Handlers
- /// A pseudo-metrics handler that can be used to send messages to multiple other metrics handlers.
- public final class MultiplexMetricsHandler: MetricsFactory {
- private let factories: [MetricsFactory]
- /// Creates a new multiplex metrics handler from the metric factories you provide.
- /// - Parameter factories: The metric factories to multiplex together.
- public init(factories: [MetricsFactory]) {
- self.factories = factories
- }
- /// Creates a new counter handler.
- /// - Parameters:
- /// - label: The label for the `CounterHandler`.
- /// - dimensions: The dimensions for the `CounterHandler`, as `(name, value)` tuples.
- public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
- MuxCounter(factories: self.factories, label: label, dimensions: dimensions)
- }
- /// Creates a new floating point counter handler.
- /// - Parameters:
- /// - label: The label for the `FloatingPointCounterHandler`.
- /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
- public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
- MuxFloatingPointCounter(factories: self.factories, label: label, dimensions: dimensions)
- }
- /// Creates a new meter handler.
- /// - Parameters:
- /// - label: The label for the `MeterHandler`.
- /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
- public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
- MuxMeter(factories: self.factories, label: label, dimensions: dimensions)
- }
- /// Creates a new recorder handler.
- /// - Parameters:
- /// - label: The label for the `RecorderHandler`.
- /// - dimensions: The dimensions for the `RecorderHandler`, as `(name, value)` tuples.
- /// - aggregate: A Boolean value that indicates whether to aggregate values.
- public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
- MuxRecorder(factories: self.factories, label: label, dimensions: dimensions, aggregate: aggregate)
- }
- /// Creates a new timer handler.
- /// - Parameters:
- /// - label: The label for the `TimerHandler`.
- /// - dimensions: The dimensions for the `TimerHandler`, as `(name, value)` tuples.
- public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
- MuxTimer(factories: self.factories, label: label, dimensions: dimensions)
- }
- /// Signal the underlying metrics library that this counter will never be updated again.
- /// - Parameter handler: The counter handler to signal.
- public func destroyCounter(_ handler: CounterHandler) {
- guard let mux = handler as? MuxCounter else { return }
- for (factory, counter) in zip(self.factories, mux.counters) {
- factory.destroyCounter(counter)
- }
- }
- /// Signal the underlying metrics library that this floating point counter will never be updated again.
- /// - Parameter handler: The floating point counter handler to signal.
- public func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler) {
- guard let mux = handler as? MuxFloatingPointCounter else { return }
- for (factory, counter) in zip(self.factories, mux.counters) {
- factory.destroyFloatingPointCounter(counter)
- }
- }
- /// Signal the underlying metrics library that this meter will never be updated again.
- /// - Parameter handler: The meter handler to signal.
- public func destroyMeter(_ handler: MeterHandler) {
- guard let mux = handler as? MuxMeter else { return }
- for (factory, meter) in zip(self.factories, mux.meters) {
- factory.destroyMeter(meter)
- }
- }
- /// Signal the underlying metrics library that this recorder will never be updated again.
- /// - Parameter handler: The recorder handler to signal.
- public func destroyRecorder(_ handler: RecorderHandler) {
- guard let mux = handler as? MuxRecorder else { return }
- for (factory, recorder) in zip(self.factories, mux.recorders) {
- factory.destroyRecorder(recorder)
- }
- }
- /// Signal the underlying metrics library that this timer will never be updated again.
- /// - Parameter handler: The timer handler to signal.
- public func destroyTimer(_ handler: TimerHandler) {
- guard let mux = handler as? MuxTimer else { return }
- for (factory, timer) in zip(self.factories, mux.timers) {
- factory.destroyTimer(timer)
- }
- }
- private final class MuxCounter: CounterHandler {
- let counters: [CounterHandler]
- public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
- self.counters = factories.map { $0.makeCounter(label: label, dimensions: dimensions) }
- }
- func increment(by amount: Int64) {
- for counter in self.counters { counter.increment(by: amount) }
- }
- func reset() {
- for counter in self.counters { counter.reset() }
- }
- }
- private final class MuxFloatingPointCounter: FloatingPointCounterHandler {
- let counters: [FloatingPointCounterHandler]
- public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
- self.counters = factories.map { $0.makeFloatingPointCounter(label: label, dimensions: dimensions) }
- }
- func increment(by amount: Double) {
- for counter in self.counters { counter.increment(by: amount) }
- }
- func reset() {
- for counter in self.counters { counter.reset() }
- }
- }
- private final class MuxMeter: MeterHandler {
- let meters: [MeterHandler]
- public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
- self.meters = factories.map { $0.makeMeter(label: label, dimensions: dimensions) }
- }
- func set(_ value: Int64) {
- for meter in self.meters { meter.set(value) }
- }
- func set(_ value: Double) {
- for meter in self.meters { meter.set(value) }
- }
- func increment(by amount: Double) {
- for meter in self.meters { meter.increment(by: amount) }
- }
- func decrement(by amount: Double) {
- for meter in self.meters { meter.decrement(by: amount) }
- }
- }
- private final class MuxRecorder: RecorderHandler {
- let recorders: [RecorderHandler]
- public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)], aggregate: Bool) {
- self.recorders = factories.map {
- $0.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
- }
- }
- func record(_ value: Int64) {
- for recorder in self.recorders { recorder.record(value) }
- }
- func record(_ value: Double) {
- for recorder in self.recorders { recorder.record(value) }
- }
- }
- private final class MuxTimer: TimerHandler {
- let timers: [TimerHandler]
- public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
- self.timers = factories.map { $0.makeTimer(label: label, dimensions: dimensions) }
- }
- func recordNanoseconds(_ duration: Int64) {
- for timer in self.timers { timer.recordNanoseconds(duration) }
- }
- func preferDisplayUnit(_ unit: TimeUnit) {
- for timer in self.timers { timer.preferDisplayUnit(unit) }
- }
- }
- }
- /// A metrics handler that implements the protocols but does nothing.
- ///
- /// The no-op metrics handler ships with the metrics module, and is used by default unless a different metrics backend is bootstrapped.
- public final class NOOPMetricsHandler: MetricsFactory, CounterHandler, FloatingPointCounterHandler, MeterHandler,
- RecorderHandler, TimerHandler
- {
- /// A sharable instance of a No-op metrics handler.
- public static let instance = NOOPMetricsHandler()
- private init() {}
- public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
- self
- }
- public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
- self
- }
- public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
- self
- }
- public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
- self
- }
- public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
- self
- }
- public func destroyCounter(_: CounterHandler) {}
- public func destroyFloatingPointCounter(_: FloatingPointCounterHandler) {}
- public func destroyMeter(_: MeterHandler) {}
- public func destroyRecorder(_: RecorderHandler) {}
- public func destroyTimer(_: TimerHandler) {}
- public func increment(by: Int64) {}
- public func increment(by: Double) {}
- public func decrement(by: Double) {}
- public func reset() {}
- public func record(_: Int64) {}
- public func record(_: Double) {}
- public func recordNanoseconds(_: Int64) {}
- public func set(_: Int64) {}
- public func set(_: Double) {}
- }
- // MARK: - Sendable support helpers
- extension MetricsSystem: Sendable {}
- extension Counter: Sendable {}
- extension FloatingPointCounter: Sendable {}
- // must be @unchecked since Gauge inherits Recorder :(
- extension Recorder: @unchecked Sendable {}
- extension Timer: Sendable {}
- extension Meter: Sendable {}
- // ideally we would not be using @unchecked here, but concurrency-safety checks do not recognize locks
- extension AccumulatingRoundingFloatingPointCounter: @unchecked Sendable {}
- @preconcurrency public protocol _SwiftMetricsSendableProtocol: Sendable {}
|