Compare commits
23 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
4c83e1cdf4 | |
|
|
98d36172c1 | |
|
|
27ecca7ac1 | |
|
|
071d1cac3c | |
|
|
0556b16079 | |
|
|
44491db7cc | |
|
|
3c0f419970 | |
|
|
cbd39ceaca | |
|
|
029e902273 | |
|
|
58f390a873 | |
|
|
53de3bfa9a | |
|
|
5e63558d12 | |
|
|
726392cf4e | |
|
|
4a491b5ad5 | |
|
|
58e2968a7f | |
|
|
4ec5a219dc | |
|
|
c1976209b5 | |
|
|
cffefdc627 | |
|
|
d720898dbf | |
|
|
569db3a632 | |
|
|
e0165b53d4 | |
|
|
d067b0e0f7 | |
|
|
79e5fb4fe4 |
|
|
@ -0,0 +1,8 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
changelog:
|
||||
categories:
|
||||
- title: SemVer Major
|
||||
labels:
|
||||
- ⚠️ semver/major
|
||||
- title: SemVer Minor
|
||||
labels:
|
||||
- 🆕 semver/minor
|
||||
- title: SemVer Patch
|
||||
labels:
|
||||
- 🔨 semver/patch
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- semver/none
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
name: Main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "0 8,20 * * *"
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
|
||||
with:
|
||||
linux_5_9_arguments_override: "--explicit-target-dependency-import-check error"
|
||||
linux_5_10_arguments_override: "--explicit-target-dependency-import-check error"
|
||||
linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
|
||||
macos-tests:
|
||||
name: macOS tests
|
||||
uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
|
||||
with:
|
||||
runner_pool: nightly
|
||||
build_scheme: swift-metrics-Package
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
name: PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
soundness:
|
||||
name: Soundness
|
||||
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
|
||||
with:
|
||||
license_header_check_project_name: "Swift Metrics API"
|
||||
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
|
||||
with:
|
||||
linux_5_9_arguments_override: "--explicit-target-dependency-import-check error"
|
||||
linux_5_10_arguments_override: "--explicit-target-dependency-import-check error"
|
||||
linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
|
||||
|
||||
cxx-interop:
|
||||
name: Cxx interop
|
||||
uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main
|
||||
|
||||
macos-tests:
|
||||
name: macOS tests
|
||||
uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
|
||||
with:
|
||||
runner_pool: general
|
||||
build_scheme: swift-metrics-Package
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
name: PR label
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled, opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
semver-label-check:
|
||||
name: Semantic version label check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for Semantic Version label
|
||||
uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
.gitignore
|
||||
**/.gitignore
|
||||
.licenseignore
|
||||
.gitattributes
|
||||
.git-blame-ignore-revs
|
||||
.mailfilter
|
||||
.mailmap
|
||||
.spi.yml
|
||||
.swift-format
|
||||
.editorconfig
|
||||
.github/*
|
||||
*.md
|
||||
*.txt
|
||||
*.yml
|
||||
*.yaml
|
||||
*.json
|
||||
Package.swift
|
||||
**/Package.swift
|
||||
Package@-*.swift
|
||||
**/Package@-*.swift
|
||||
Package.resolved
|
||||
**/Package.resolved
|
||||
Makefile
|
||||
*.modulemap
|
||||
**/*.modulemap
|
||||
**/*.docc/*
|
||||
*.xcprivacy
|
||||
**/*.xcprivacy
|
||||
*.symlink
|
||||
**/*.symlink
|
||||
Dockerfile
|
||||
**/Dockerfile
|
||||
Snippets/*
|
||||
dev/git.commit.template
|
||||
.unacceptablelanguageignore
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"version" : 1,
|
||||
"indentation" : {
|
||||
"spaces" : 4
|
||||
},
|
||||
"tabWidth" : 4,
|
||||
"fileScopedDeclarationPrivacy" : {
|
||||
"accessLevel" : "private"
|
||||
},
|
||||
"spacesAroundRangeFormationOperators" : false,
|
||||
"indentConditionalCompilationBlocks" : false,
|
||||
"indentSwitchCaseLabels" : false,
|
||||
"lineBreakAroundMultilineExpressionChainComponents" : false,
|
||||
"lineBreakBeforeControlFlowKeywords" : false,
|
||||
"lineBreakBeforeEachArgument" : true,
|
||||
"lineBreakBeforeEachGenericRequirement" : true,
|
||||
"lineLength" : 120,
|
||||
"maximumBlankLines" : 1,
|
||||
"respectsExistingLineBreaks" : true,
|
||||
"prioritizeKeepingFunctionOutputTogether" : true,
|
||||
"noAssignmentInExpressions" : {
|
||||
"allowedFunctions" : [
|
||||
"XCTAssertNoThrow",
|
||||
"XCTAssertThrowsError"
|
||||
]
|
||||
},
|
||||
"rules" : {
|
||||
"AllPublicDeclarationsHaveDocumentation" : false,
|
||||
"AlwaysUseLiteralForEmptyCollectionInit" : false,
|
||||
"AlwaysUseLowerCamelCase" : false,
|
||||
"AmbiguousTrailingClosureOverload" : true,
|
||||
"BeginDocumentationCommentWithOneLineSummary" : false,
|
||||
"DoNotUseSemicolons" : true,
|
||||
"DontRepeatTypeInStaticProperties" : true,
|
||||
"FileScopedDeclarationPrivacy" : true,
|
||||
"FullyIndirectEnum" : true,
|
||||
"GroupNumericLiterals" : true,
|
||||
"IdentifiersMustBeASCII" : true,
|
||||
"NeverForceUnwrap" : false,
|
||||
"NeverUseForceTry" : false,
|
||||
"NeverUseImplicitlyUnwrappedOptionals" : false,
|
||||
"NoAccessLevelOnExtensionDeclaration" : true,
|
||||
"NoAssignmentInExpressions" : true,
|
||||
"NoBlockComments" : true,
|
||||
"NoCasesWithOnlyFallthrough" : true,
|
||||
"NoEmptyTrailingClosureParentheses" : true,
|
||||
"NoLabelsInCasePatterns" : true,
|
||||
"NoLeadingUnderscores" : false,
|
||||
"NoParensAroundConditions" : true,
|
||||
"NoVoidReturnOnFunctionSignature" : true,
|
||||
"OmitExplicitReturns" : true,
|
||||
"OneCasePerLine" : true,
|
||||
"OneVariableDeclarationPerLine" : true,
|
||||
"OnlyOneTrailingClosureArgument" : true,
|
||||
"OrderedImports" : true,
|
||||
"ReplaceForEachWithForLoop" : true,
|
||||
"ReturnVoidInsteadOfEmptyTuple" : true,
|
||||
"UseEarlyExits" : false,
|
||||
"UseExplicitNilCheckInConditions" : false,
|
||||
"UseLetInEveryBoundCaseVariable" : false,
|
||||
"UseShorthandTypeNames" : true,
|
||||
"UseSingleLinePropertyGetter" : false,
|
||||
"UseSynthesizedInitializer" : false,
|
||||
"UseTripleSlashForDocumentationComments" : true,
|
||||
"UseWhereClausesInForLoops" : false,
|
||||
"ValidateDocumentationComments" : false
|
||||
}
|
||||
}
|
||||
16
.swiftformat
16
.swiftformat
|
|
@ -1,16 +0,0 @@
|
|||
# file options
|
||||
|
||||
--swiftversion 5.0
|
||||
--exclude .build
|
||||
|
||||
# format options
|
||||
|
||||
--self insert
|
||||
--patternlet inline
|
||||
--stripunusedargs unnamed-only
|
||||
--ifdef no-indent
|
||||
|
||||
# Configure the placement of an extension's access control keyword.
|
||||
--extensionacl on-declarations
|
||||
|
||||
# rules
|
||||
|
|
@ -60,36 +60,9 @@ We require that your commit messages match our template. The easiest way to do t
|
|||
|
||||
git config commit.template dev/git.commit.template
|
||||
|
||||
### Make sure Tests work on Linux
|
||||
### Run CI checks locally
|
||||
|
||||
SwiftMetrics uses XCTest to run tests on both macOS and Linux. While the macOS version of XCTest is able to use the Objective-C runtime to discover tests at execution time, the Linux version is not.
|
||||
For this reason, whenever you add new tests **you have to run a script** that generates the hooks needed to run those tests on Linux, or our CI will complain that the tests are not all present on Linux. To do this, merely execute `ruby ./scripts/generate_linux_tests.rb` at the root of the package and check the changes it made.
|
||||
|
||||
### Run `./scripts/soundness.sh`
|
||||
|
||||
The scripts directory contains a [soundness.sh script](https://github.com/apple/swift-metrics/blob/main/scripts/soundness.sh)
|
||||
that enforces additional checks, like license headers and formatting style.
|
||||
|
||||
Please make sure to `./scripts/soundness.sh` before pushing a change upstream, otherwise it is likely the PR validation will fail
|
||||
on minor changes such as a missing `self.` or similar formatting issues.
|
||||
|
||||
> The script also executes the above mentioned `generate_linux_tests.rb`.
|
||||
|
||||
For frequent contributors, we recommend adding the script as a [git pre-push hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks), which you can do via executing the following command in the project root directory:
|
||||
|
||||
```bash
|
||||
cat << EOF > .git/hooks/pre-push
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -f "scripts/soundness.sh" ]]; then
|
||||
scripts/soundness.sh
|
||||
fi
|
||||
EOF
|
||||
```
|
||||
|
||||
Which makes the script execute, and only allow the `git push` to complete if the check has passed.
|
||||
|
||||
In the case of formatting issues, you can then `git add` the formatting changes, and attempt the push again.
|
||||
You can run the Github Actions workflows locally using [act](https://github.com/nektos/act). For detailed steps on how to do this please see [https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally](https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally).
|
||||
|
||||
## How to contribute your work
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// swift-tools-version:5.6
|
||||
// swift-tools-version:5.9
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Metrics API open source project
|
||||
|
|
@ -22,10 +22,6 @@ let package = Package(
|
|||
.library(name: "Metrics", targets: ["Metrics"]),
|
||||
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
|
||||
],
|
||||
dependencies: [
|
||||
// ~~~ SwiftPM Plugins ~~~
|
||||
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "CoreMetrics"
|
||||
|
|
@ -44,3 +40,25 @@ let package = Package(
|
|||
),
|
||||
]
|
||||
)
|
||||
|
||||
for target in package.targets {
|
||||
var settings = target.swiftSettings ?? []
|
||||
settings.append(.enableExperimentalFeature("StrictConcurrency=complete"))
|
||||
target.swiftSettings = settings
|
||||
}
|
||||
|
||||
// --- STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- //
|
||||
for target in package.targets {
|
||||
switch target.type {
|
||||
case .regular, .test, .executable:
|
||||
var settings = target.swiftSettings ?? []
|
||||
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md
|
||||
settings.append(.enableUpcomingFeature("MemberImportVisibility"))
|
||||
target.swiftSettings = settings
|
||||
case .macro, .plugin, .system, .binary:
|
||||
() // not applicable
|
||||
@unknown default:
|
||||
() // we don't know what to do here, do nothing
|
||||
}
|
||||
}
|
||||
// --- END: STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- //
|
||||
|
|
|
|||
|
|
@ -90,12 +90,18 @@ This API was designed with the contributors to the Swift on Server community and
|
|||
|
||||
### Metric types
|
||||
|
||||
The API supports four metric types:
|
||||
The API supports six metric types:
|
||||
|
||||
`Counter`: A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart. For example, you can use a counter to represent the number of requests served, tasks completed, or errors.
|
||||
|
||||
```swift
|
||||
counter.increment(by: 100)
|
||||
```
|
||||
|
||||
- `FloatingPointCounter`: A variation of a `Counter` that records a floating point value, instead of an integer.
|
||||
|
||||
```swift
|
||||
floatingPointCounter.increment(by: 10.5)
|
||||
```
|
||||
|
||||
`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, like the number of active threads. Gauges are modeled as a `Recorder` with a sample size of 1 that does not perform any aggregation.
|
||||
|
|
|
|||
|
|
@ -38,6 +38,6 @@ with the all the required detail.
|
|||
and in the Server → Security Updates category on the [Swift forums][swift-forums-sec].
|
||||
|
||||
[sswg]: https://github.com/swift-server/sswg
|
||||
[sswg-security]: https://github.com/swift-server/sswg/blob/main/security/README.md
|
||||
[sswg-security]: https://www.swift.org/sswg/security/
|
||||
[swift-forums-sec]: https://forums.swift.org/c/server/security-updates/
|
||||
[mitre]: https://cveform.mitre.org/
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@ This API was designed with the contributors to the Swift on Server community and
|
|||
### Metric types
|
||||
|
||||
- ``Counter``
|
||||
- ``FloatingPointCounter``
|
||||
- ``Meter``
|
||||
- ``Recorder``
|
||||
- ``Gauge``
|
||||
- ``Timer``
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import Darwin
|
|||
import WinSDK
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#elseif canImport(Android)
|
||||
import Android
|
||||
#elseif canImport(Musl)
|
||||
import Musl
|
||||
#else
|
||||
|
|
@ -131,6 +133,8 @@ extension Lock {
|
|||
}
|
||||
}
|
||||
|
||||
extension Lock: @unchecked Sendable {}
|
||||
|
||||
/// A reader/writer threading lock based on `libpthread` instead of `libdispatch`.
|
||||
///
|
||||
/// This object provides a lock on top of a single `pthread_rwlock_t`. This kind
|
||||
|
|
@ -273,3 +277,5 @@ extension ReadWriteLock {
|
|||
try self.withWriterLock(body)
|
||||
}
|
||||
}
|
||||
|
||||
extension ReadWriteLock: @unchecked Sendable {}
|
||||
|
|
|
|||
|
|
@ -23,32 +23,51 @@
|
|||
///
|
||||
/// Its behavior depends on the `CounterHandler` implementation.
|
||||
public final class Counter {
|
||||
/// ``_handler`` is only public to allow access from `MetricsTestKit`. Do not consider it part of the public API.
|
||||
/// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
|
||||
/// Do not consider them part of the public API.
|
||||
public let _handler: CounterHandler
|
||||
@usableFromInline
|
||||
package let _factory: MetricsFactory
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
/// Alternative way to create a new `Counter`, while providing an explicit `CounterHandler`.
|
||||
///
|
||||
/// - warning: This initializer provides an escape hatch for situations where one must use a custom factory instead of the global one.
|
||||
/// We do not expect this API to be used in normal circumstances, so if you find yourself using it make sure it's for a good reason.
|
||||
///
|
||||
/// - 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`.
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], handler: CounterHandler) {
|
||||
/// - factory: The custom 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`, while providing an explicit `CounterHandler`.
|
||||
///
|
||||
/// - 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`.
|
||||
/// - 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:
|
||||
/// - by: Amount to increment by.
|
||||
/// - amount: Amount to increment by.
|
||||
@inlinable
|
||||
public func increment<DataType: BinaryInteger>(by amount: DataType) {
|
||||
self._handler.increment(by: Int64(amount))
|
||||
|
|
@ -74,21 +93,31 @@ extension Counter {
|
|||
/// - label: The label for the `Counter`.
|
||||
/// - dimensions: The dimensions for the `Counter`.
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeCounter(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
|
||||
}
|
||||
|
||||
/// Create a new `Counter`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Counter`.
|
||||
/// - dimensions: The dimensions for the `Counter`.
|
||||
/// - factory: The custom 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() {
|
||||
MetricsSystem.factory.destroyCounter(self._handler)
|
||||
self._factory.destroyCounter(self._handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Counter: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Counter(\(self.label), dimensions: \(self.dimensions))"
|
||||
"Counter(\(self.label), dimensions: \(self.dimensions))"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,32 +131,56 @@ extension Counter: CustomStringConvertible {
|
|||
///
|
||||
/// Its behavior depends on the `FloatingCounterHandler` implementation.
|
||||
public final class FloatingPointCounter {
|
||||
/// ``_handler`` is only public to allow access from `MetricsTestKit`. Do not consider it part of the public API.
|
||||
/// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
|
||||
/// Do not consider them part of the public API.
|
||||
public let _handler: FloatingPointCounterHandler
|
||||
@usableFromInline
|
||||
package let _factory: MetricsFactory
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
/// Alternative way to create a new `FloatingPointCounter`, while providing an explicit `FloatingPointCounterHandler`.
|
||||
///
|
||||
/// - warning: This initializer provides an escape hatch for situations where one must use a custom factory instead of the global one.
|
||||
/// We do not expect this API to be used in normal circumstances, so if you find yourself using it make sure it's for a good reason.
|
||||
///
|
||||
/// - SeeAlso: Use `init(label:dimensions:)` to create `FloatingPointCounter` instances using the configured metrics backend.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `FloatingPointCounter`.
|
||||
/// - dimensions: The dimensions for the `FloatingPointCounter`.
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], handler: FloatingPointCounterHandler) {
|
||||
/// - factory: The custom 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 `FloatingPointCounter`, while providing an explicit `FloatingPointCounterHandler`.
|
||||
///
|
||||
/// - SeeAlso: Use `init(label:dimensions:)` to create `FloatingPointCounter` instances using the configured metrics backend.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `FloatingPointCounter`.
|
||||
/// - dimensions: The dimensions for the `FloatingPointCounter`.
|
||||
/// - 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 FloatingPointCounter.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - by: Amount to increment by.
|
||||
/// - amount: Amount to increment by.
|
||||
@inlinable
|
||||
public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
|
||||
self._handler.increment(by: Double(amount))
|
||||
|
|
@ -153,21 +206,31 @@ extension FloatingPointCounter {
|
|||
/// - label: The label for the `FloatingPointCounter`.
|
||||
/// - dimensions: The dimensions for the `FloatingPointCounter`.
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeFloatingPointCounter(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
|
||||
}
|
||||
|
||||
/// Create a new `FloatingPointCounter`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `FloatingPointCounter`.
|
||||
/// - dimensions: The dimensions for the `FloatingPointCounter`.
|
||||
/// - factory: The custom 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 FloatingPointCounter will never be updated again.
|
||||
/// In response the library MAY decide to eagerly release any resources held by this `FloatingPointCounter`.
|
||||
@inlinable
|
||||
public func destroy() {
|
||||
MetricsSystem.factory.destroyFloatingPointCounter(self._handler)
|
||||
self._factory.destroyFloatingPointCounter(self._handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatingPointCounter: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "FloatingPointCounter(\(self.label), dimensions: \(self.dimensions))"
|
||||
"FloatingPointCounter(\(self.label), dimensions: \(self.dimensions))"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +248,16 @@ public final class Gauge: Recorder, @unchecked Sendable {
|
|||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
self.init(label: label, dimensions: dimensions, aggregate: false)
|
||||
}
|
||||
|
||||
/// Create a new `Gauge`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Gauge`.
|
||||
/// - dimensions: The dimensions for the `Gauge`.
|
||||
/// - factory: The custom factory.
|
||||
public convenience init(label: String, dimensions: [(String, String)] = [], factory: MetricsFactory) {
|
||||
self.init(label: label, dimensions: dimensions, aggregate: false, factory: factory)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Meter
|
||||
|
|
@ -192,26 +265,40 @@ public final class Gauge: Recorder, @unchecked Sendable {
|
|||
/// 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, like the number of active threads.
|
||||
public final class Meter {
|
||||
/// ``_handler`` is only public to allow access from `MetricsTestKit`. Do not consider it part of the public API.
|
||||
/// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
|
||||
/// Do not consider them part of the public API.
|
||||
public let _handler: MeterHandler
|
||||
@usableFromInline
|
||||
package let _factory: MetricsFactory
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
/// Alternative way to create a new `Meter`, while providing an explicit `MeterHandler`.
|
||||
///
|
||||
/// - warning: This initializer provides an escape hatch for situations where one must use a custom factory instead of the global one.
|
||||
/// We do not expect this API to be used in normal circumstances, so if you find yourself using it make sure it's for a good reason.
|
||||
///
|
||||
/// - SeeAlso: Use `init(label:dimensions:)` to create `Meter` instances using the configured metrics backend.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Recorder`.
|
||||
/// - dimensions: The dimensions for the `Recorder`.
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], handler: MeterHandler) {
|
||||
/// - factory: The custom 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 `MeterHandler`.
|
||||
///
|
||||
/// - SeeAlso: Use `init(label:dimensions:)` to create `Meter` instances using the configured metrics backend.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Recorder`.
|
||||
/// - dimensions: The dimensions for the `Recorder`.
|
||||
/// - 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 a value.
|
||||
|
|
@ -235,7 +322,7 @@ public final class Meter {
|
|||
/// Increment the Meter.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - by: Amount to increment by.
|
||||
/// - amount: Amount to increment by.
|
||||
@inlinable
|
||||
public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
|
||||
self._handler.increment(by: Double(amount))
|
||||
|
|
@ -250,7 +337,7 @@ public final class Meter {
|
|||
/// Decrement the Meter.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - by: Amount to decrement by.
|
||||
/// - amount: Amount to decrement by.
|
||||
@inlinable
|
||||
public func decrement<DataType: BinaryFloatingPoint>(by amount: DataType) {
|
||||
self._handler.decrement(by: Double(amount))
|
||||
|
|
@ -264,27 +351,37 @@ public final class Meter {
|
|||
}
|
||||
|
||||
extension Meter {
|
||||
/// Create a new `Meter`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Meter`.
|
||||
/// - dimensions: The dimensions for the `Meter`.
|
||||
/// - factory: The custom 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`.
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeMeter(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
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() {
|
||||
MetricsSystem.factory.destroyMeter(self._handler)
|
||||
self._factory.destroyMeter(self._handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Meter: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "\(type(of: self))(\(self.label), dimensions: \(self.dimensions))"
|
||||
"\(type(of: self))(\(self.label), dimensions: \(self.dimensions))"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,17 +393,17 @@ extension Meter: CustomStringConvertible {
|
|||
///
|
||||
/// Its behavior depends on the `RecorderHandler` implementation.
|
||||
public class Recorder {
|
||||
/// ``_handler`` is only public to allow access from `MetricsTestKit`. Do not consider it part of the public API.
|
||||
/// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
|
||||
/// Do not consider them part of the public API.
|
||||
public let _handler: RecorderHandler
|
||||
@usableFromInline
|
||||
package let _factory: MetricsFactory
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
public let aggregate: Bool
|
||||
|
||||
/// Alternative way to create a new `Recorder`, while providing an explicit `RecorderHandler`.
|
||||
///
|
||||
/// - warning: This initializer provides an escape hatch for situations where one must use a custom factory instead of the global one.
|
||||
/// We do not expect this API to be used in normal circumstances, so if you find yourself using it make sure it's for a good reason.
|
||||
///
|
||||
/// - SeeAlso: Use `init(label:dimensions:)` to create `Recorder` instances using the configured metrics backend.
|
||||
///
|
||||
/// - parameters:
|
||||
|
|
@ -314,11 +411,38 @@ public class Recorder {
|
|||
/// - dimensions: The dimensions for the `Recorder`.
|
||||
/// - aggregate: aggregate recorded values to produce statistics across a sample size
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], aggregate: Bool, handler: RecorderHandler) {
|
||||
/// - factory: The custom 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 `RecorderHandler`.
|
||||
///
|
||||
/// - 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`.
|
||||
/// - aggregate: aggregate recorded values to produce statistics across a sample size
|
||||
/// - 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.
|
||||
|
|
@ -354,28 +478,44 @@ extension Recorder {
|
|||
/// - dimensions: The dimensions for the `Recorder`.
|
||||
/// - aggregate: aggregate recorded values to produce statistics across a sample size
|
||||
public convenience init(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true) {
|
||||
let handler = MetricsSystem.factory.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
self.init(label: label, dimensions: dimensions, aggregate: aggregate, handler: handler)
|
||||
self.init(label: label, dimensions: dimensions, aggregate: aggregate, factory: MetricsSystem.factory)
|
||||
}
|
||||
|
||||
/// Create a new `Recorder`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Recorder`.
|
||||
/// - dimensions: The dimensions for the `Recorder`.
|
||||
/// - aggregate: aggregate recorded values to produce statistics across a sample size.
|
||||
/// - factory: The custom 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() {
|
||||
MetricsSystem.factory.destroyRecorder(self._handler)
|
||||
self._factory.destroyRecorder(self._handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Recorder: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "\(type(of: self))(\(self.label), dimensions: \(self.dimensions), aggregate: \(self.aggregate))"
|
||||
"\(type(of: self))(\(self.label), dimensions: \(self.dimensions), aggregate: \(self.aggregate))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
public struct TimeUnit: Equatable {
|
||||
private enum Code: Equatable {
|
||||
public struct TimeUnit: Equatable, Sendable {
|
||||
private enum Code: Equatable, Sendable {
|
||||
case nanoseconds
|
||||
case microseconds
|
||||
case milliseconds
|
||||
|
|
@ -397,9 +537,18 @@ public struct TimeUnit: Equatable {
|
|||
|
||||
public static let nanoseconds = TimeUnit(code: .nanoseconds, scaleFromNanoseconds: 1)
|
||||
public static let microseconds = TimeUnit(code: .microseconds, scaleFromNanoseconds: 1000)
|
||||
public static let milliseconds = TimeUnit(code: .milliseconds, scaleFromNanoseconds: 1000 * TimeUnit.microseconds.scaleFromNanoseconds)
|
||||
public static let seconds = TimeUnit(code: .seconds, scaleFromNanoseconds: 1000 * TimeUnit.milliseconds.scaleFromNanoseconds)
|
||||
public static let minutes = TimeUnit(code: .minutes, scaleFromNanoseconds: 60 * TimeUnit.seconds.scaleFromNanoseconds)
|
||||
public static let milliseconds = TimeUnit(
|
||||
code: .milliseconds,
|
||||
scaleFromNanoseconds: 1000 * TimeUnit.microseconds.scaleFromNanoseconds
|
||||
)
|
||||
public static let seconds = TimeUnit(
|
||||
code: .seconds,
|
||||
scaleFromNanoseconds: 1000 * TimeUnit.milliseconds.scaleFromNanoseconds
|
||||
)
|
||||
public static let minutes = TimeUnit(
|
||||
code: .minutes,
|
||||
scaleFromNanoseconds: 60 * TimeUnit.seconds.scaleFromNanoseconds
|
||||
)
|
||||
public static let hours = TimeUnit(code: .hours, scaleFromNanoseconds: 60 * TimeUnit.minutes.scaleFromNanoseconds)
|
||||
public static let days = TimeUnit(code: .days, scaleFromNanoseconds: 24 * TimeUnit.hours.scaleFromNanoseconds)
|
||||
}
|
||||
|
|
@ -411,32 +560,46 @@ public struct TimeUnit: Equatable {
|
|||
///
|
||||
/// Its behavior depends on the `TimerHandler` implementation.
|
||||
public final class Timer {
|
||||
/// ``_handler`` is only public to allow access from `MetricsTestKit`. Do not consider it part of the public API.
|
||||
/// `_handler` and `_factory` are only public to allow access from `MetricsTestKit`.
|
||||
/// Do not consider them part of the public API.
|
||||
public let _handler: TimerHandler
|
||||
@usableFromInline
|
||||
package let _factory: MetricsFactory
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
/// Alternative way to create a new `Timer`, while providing an explicit `TimerHandler`.
|
||||
///
|
||||
/// - warning: This initializer provides an escape hatch for situations where one must use a custom factory instead of the global one.
|
||||
/// We do not expect this API to be used in normal circumstances, so if you find yourself using it make sure it's for a good reason.
|
||||
///
|
||||
/// - SeeAlso: Use `init(label:dimensions:)` to create `Recorder` instances using the configured metrics backend.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Timer`.
|
||||
/// - dimensions: The dimensions for the `Timer`.
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], handler: TimerHandler) {
|
||||
/// - 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 `TimerHandler`.
|
||||
///
|
||||
/// - SeeAlso: Use `init(label:dimensions:)` to create `Recorder` instances using the configured metrics backend.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Timer`.
|
||||
/// - dimensions: The dimensions for the `Timer`.
|
||||
/// - 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:
|
||||
/// - value: Duration to record.
|
||||
/// - duration: Duration to record.
|
||||
@inlinable
|
||||
public func recordNanoseconds(_ duration: Int64) {
|
||||
self._handler.recordNanoseconds(duration)
|
||||
|
|
@ -445,7 +608,7 @@ public final class Timer {
|
|||
/// Record a duration in nanoseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - duration: Duration to record.
|
||||
@inlinable
|
||||
public func recordNanoseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
self.recordNanoseconds(duration >= Int64.max ? Int64.max : Int64(duration))
|
||||
|
|
@ -454,7 +617,7 @@ public final class Timer {
|
|||
/// Record a duration in microseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - duration: Duration to record.
|
||||
@inlinable
|
||||
public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
|
||||
|
|
@ -470,7 +633,7 @@ public final class Timer {
|
|||
/// Record a duration in microseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - 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)
|
||||
|
|
@ -479,7 +642,7 @@ public final class Timer {
|
|||
/// Record a duration in milliseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - duration: Duration to record.
|
||||
@inlinable
|
||||
public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
|
||||
|
|
@ -495,16 +658,18 @@ public final class Timer {
|
|||
/// Record a duration in milliseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - 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)
|
||||
self.recordNanoseconds(
|
||||
Double(duration * 1_000_000) < Double(Int64.max) ? Int64(duration * 1_000_000) : Int64.max
|
||||
)
|
||||
}
|
||||
|
||||
/// Record a duration in seconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - duration: Duration to record.
|
||||
@inlinable
|
||||
public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
|
||||
|
|
@ -520,22 +685,34 @@ public final class Timer {
|
|||
/// Record a duration in seconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - 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)
|
||||
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`.
|
||||
/// - 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`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the `Timer`.
|
||||
/// - dimensions: The dimensions for the `Timer`.
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
self.init(label: label, dimensions: dimensions, factory: MetricsSystem.factory)
|
||||
}
|
||||
|
||||
/// Create a new `Timer`.
|
||||
|
|
@ -544,23 +721,48 @@ extension Timer {
|
|||
/// - label: The label for the `Timer`.
|
||||
/// - dimensions: The dimensions for the `Timer`.
|
||||
/// - 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) {
|
||||
let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions)
|
||||
/// - 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)
|
||||
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`.
|
||||
/// - 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() {
|
||||
MetricsSystem.factory.destroyTimer(self._handler)
|
||||
self._factory.destroyTimer(self._handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Timer: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Timer(\(self.label), dimensions: \(self.dimensions))"
|
||||
"Timer(\(self.label), dimensions: \(self.dimensions))"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -589,7 +791,7 @@ public enum MetricsSystem {
|
|||
|
||||
/// Returns a reference to the configured factory.
|
||||
public static var factory: MetricsFactory {
|
||||
return self._factory.underlying
|
||||
self._factory.underlying
|
||||
}
|
||||
|
||||
/// Acquire a writer lock for the duration of the given block.
|
||||
|
|
@ -597,10 +799,11 @@ public enum MetricsSystem {
|
|||
/// - 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 {
|
||||
return try self._factory.withWriterLock(body)
|
||||
try self._factory.withWriterLock(body)
|
||||
}
|
||||
|
||||
private final class FactoryBox {
|
||||
// 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
|
||||
|
|
@ -611,20 +814,23 @@ public enum MetricsSystem {
|
|||
|
||||
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)")
|
||||
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 {
|
||||
return self.lock.withReaderLock {
|
||||
return self._underlying
|
||||
self.lock.withReaderLock {
|
||||
self._underlying
|
||||
}
|
||||
}
|
||||
|
||||
func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
return try self.lock.withWriterLock(body)
|
||||
try self.lock.withWriterLock(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -640,8 +846,7 @@ public enum MetricsSystem {
|
|||
/// * `Recorder` -> `RecorderHandler`
|
||||
/// * `Timer` -> `TimerHandler`
|
||||
///
|
||||
/// - warning: 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 `MetricsSystem`.
|
||||
/// To use the SwiftMetrics API, please refer to the documentation of `MetricsSystem`.
|
||||
///
|
||||
/// # Destroying metrics
|
||||
///
|
||||
|
|
@ -735,11 +940,12 @@ public protocol MetricsFactory: _SwiftMetricsSendableProtocol {
|
|||
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)]) {
|
||||
self.counterHandler = MetricsSystem
|
||||
.factory.makeCounter(label: label, dimensions: dimensions)
|
||||
init(label: String, dimensions: [(String, String)], factory: MetricsFactory) {
|
||||
self.counterHandler = factory.makeCounter(label: label, dimensions: dimensions)
|
||||
self.factory = factory
|
||||
}
|
||||
|
||||
func increment(by amount: Double) {
|
||||
|
|
@ -792,20 +998,22 @@ internal final class AccumulatingRoundingFloatingPointCounter: FloatingPointCoun
|
|||
}
|
||||
|
||||
func destroy() {
|
||||
MetricsSystem.factory.destroyCounter(self.counterHandler)
|
||||
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.
|
||||
internal final class AccumulatingMeter: MeterHandler {
|
||||
/// - 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 atomics when available
|
||||
// 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)]) {
|
||||
self.recorderHandler = MetricsSystem
|
||||
.factory.makeRecorder(label: label, dimensions: dimensions, aggregate: true)
|
||||
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) {
|
||||
|
|
@ -876,7 +1084,7 @@ internal final class AccumulatingMeter: MeterHandler {
|
|||
}
|
||||
|
||||
func destroy() {
|
||||
MetricsSystem.factory.destroyRecorder(self.recorderHandler)
|
||||
self.factory.destroyRecorder(self.recorderHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -889,7 +1097,7 @@ extension MetricsFactory {
|
|||
/// - label: The label for the `FloatingPointCounterHandler`.
|
||||
/// - dimensions: The dimensions for the `FloatingPointCounterHandler`.
|
||||
public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
|
||||
return AccumulatingRoundingFloatingPointCounter(label: label, dimensions: dimensions)
|
||||
AccumulatingRoundingFloatingPointCounter(label: label, dimensions: dimensions, factory: self)
|
||||
}
|
||||
|
||||
/// Invoked when the corresponding `FloatingPointCounter`'s `destroy()` function is invoked.
|
||||
|
|
@ -913,7 +1121,7 @@ extension MetricsFactory {
|
|||
/// - label: The label for the `MeterHandler`.
|
||||
/// - dimensions: The dimensions for the `MeterHandler`.
|
||||
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
|
||||
return AccumulatingMeter(label: label, dimensions: dimensions)
|
||||
AccumulatingMeter(label: label, dimensions: dimensions, factory: self)
|
||||
}
|
||||
|
||||
/// Invoked when the corresponding `Meter`'s `destroy()` function is invoked.
|
||||
|
|
@ -1049,7 +1257,7 @@ public protocol TimerHandler: AnyObject, _SwiftMetricsSendableProtocol {
|
|||
/// Record a duration in nanoseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
/// - duration: Duration to record.
|
||||
func recordNanoseconds(_ duration: Int64)
|
||||
|
||||
/// Set the preferred display unit for this TimerHandler.
|
||||
|
|
@ -1075,23 +1283,23 @@ public final class MultiplexMetricsHandler: MetricsFactory {
|
|||
}
|
||||
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return MuxCounter(factories: self.factories, label: label, dimensions: dimensions)
|
||||
MuxCounter(factories: self.factories, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
|
||||
return MuxFloatingPointCounter(factories: self.factories, label: label, dimensions: dimensions)
|
||||
MuxFloatingPointCounter(factories: self.factories, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
|
||||
return MuxMeter(factories: self.factories, label: label, dimensions: dimensions)
|
||||
MuxMeter(factories: self.factories, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
return MuxRecorder(factories: self.factories, label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
MuxRecorder(factories: self.factories, label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
}
|
||||
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return MuxTimer(factories: self.factories, label: label, dimensions: dimensions)
|
||||
MuxTimer(factories: self.factories, label: label, dimensions: dimensions)
|
||||
}
|
||||
|
||||
public func destroyCounter(_ handler: CounterHandler) {
|
||||
|
|
@ -1131,11 +1339,11 @@ public final class MultiplexMetricsHandler: MetricsFactory {
|
|||
}
|
||||
|
||||
func increment(by amount: Int64) {
|
||||
self.counters.forEach { $0.increment(by: amount) }
|
||||
for counter in self.counters { counter.increment(by: amount) }
|
||||
}
|
||||
|
||||
func reset() {
|
||||
self.counters.forEach { $0.reset() }
|
||||
for counter in self.counters { counter.reset() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1146,11 +1354,11 @@ public final class MultiplexMetricsHandler: MetricsFactory {
|
|||
}
|
||||
|
||||
func increment(by amount: Double) {
|
||||
self.counters.forEach { $0.increment(by: amount) }
|
||||
for counter in self.counters { counter.increment(by: amount) }
|
||||
}
|
||||
|
||||
func reset() {
|
||||
self.counters.forEach { $0.reset() }
|
||||
for counter in self.counters { counter.reset() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1161,34 +1369,36 @@ public final class MultiplexMetricsHandler: MetricsFactory {
|
|||
}
|
||||
|
||||
func set(_ value: Int64) {
|
||||
self.meters.forEach { $0.set(value) }
|
||||
for meter in self.meters { meter.set(value) }
|
||||
}
|
||||
|
||||
func set(_ value: Double) {
|
||||
self.meters.forEach { $0.set(value) }
|
||||
for meter in self.meters { meter.set(value) }
|
||||
}
|
||||
|
||||
func increment(by amount: Double) {
|
||||
self.meters.forEach { $0.increment(by: amount) }
|
||||
for meter in self.meters { meter.increment(by: amount) }
|
||||
}
|
||||
|
||||
func decrement(by amount: Double) {
|
||||
self.meters.forEach { $0.decrement(by: amount) }
|
||||
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) }
|
||||
self.recorders = factories.map {
|
||||
$0.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
}
|
||||
}
|
||||
|
||||
func record(_ value: Int64) {
|
||||
self.recorders.forEach { $0.record(value) }
|
||||
for recorder in self.recorders { recorder.record(value) }
|
||||
}
|
||||
|
||||
func record(_ value: Double) {
|
||||
self.recorders.forEach { $0.record(value) }
|
||||
for recorder in self.recorders { recorder.record(value) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1199,39 +1409,41 @@ public final class MultiplexMetricsHandler: MetricsFactory {
|
|||
}
|
||||
|
||||
func recordNanoseconds(_ duration: Int64) {
|
||||
self.timers.forEach { $0.recordNanoseconds(duration) }
|
||||
for timer in self.timers { timer.recordNanoseconds(duration) }
|
||||
}
|
||||
|
||||
func preferDisplayUnit(_ unit: TimeUnit) {
|
||||
self.timers.forEach { $0.preferDisplayUnit(unit) }
|
||||
for timer in self.timers { timer.preferDisplayUnit(unit) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ships with the metrics module, used for initial bootstrapping.
|
||||
public final class NOOPMetricsHandler: MetricsFactory, CounterHandler, FloatingPointCounterHandler, MeterHandler, RecorderHandler, TimerHandler {
|
||||
public final class NOOPMetricsHandler: MetricsFactory, CounterHandler, FloatingPointCounterHandler, MeterHandler,
|
||||
RecorderHandler, TimerHandler
|
||||
{
|
||||
public static let instance = NOOPMetricsHandler()
|
||||
|
||||
private init() {}
|
||||
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return self
|
||||
self
|
||||
}
|
||||
|
||||
public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
|
||||
return self
|
||||
self
|
||||
}
|
||||
|
||||
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
|
||||
return self
|
||||
self
|
||||
}
|
||||
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
return self
|
||||
self
|
||||
}
|
||||
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return self
|
||||
self
|
||||
}
|
||||
|
||||
public func destroyCounter(_: CounterHandler) {}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,15 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// swift-format-ignore-file
|
||||
// Note: Whitespace changes are used to workaround compiler bug
|
||||
// https://github.com/swiftlang/swift/issues/79285
|
||||
|
||||
@_exported import CoreMetrics
|
||||
@_exported import class CoreMetrics.Timer
|
||||
import Foundation
|
||||
|
||||
@_exported import class CoreMetrics.Timer
|
||||
|
||||
extension Timer {
|
||||
/// Convenience for measuring duration of a closure.
|
||||
///
|
||||
|
|
@ -24,7 +28,11 @@ extension Timer {
|
|||
/// - dimensions: The dimensions for the Timer.
|
||||
/// - body: Closure to run & record.
|
||||
@inlinable
|
||||
public static func measure<T>(label: String, dimensions: [(String, String)] = [], body: @escaping () throws -> T) rethrows -> T {
|
||||
public static func measure<T>(
|
||||
label: String,
|
||||
dimensions: [(String, String)] = [],
|
||||
body: @escaping () throws -> T
|
||||
) rethrows -> T {
|
||||
let timer = Timer(label: label, dimensions: dimensions)
|
||||
let start = DispatchTime.now().uptimeNanoseconds
|
||||
defer {
|
||||
|
|
@ -60,6 +68,16 @@ extension Timer {
|
|||
/// - duration: The duration to record.
|
||||
@inlinable
|
||||
public func record(_ duration: DispatchTimeInterval) {
|
||||
// This wrapping in a optional is a workaround because DispatchTimeInterval
|
||||
// is a non-frozen public enum and Dispatch is built with library evolution
|
||||
// mode turned on.
|
||||
// This means we should have an `@unknown default` case, but this breaks
|
||||
// on non-Darwin platforms.
|
||||
// Switching over an optional means that the `.none` case will map to
|
||||
// `default` (which means we'll always have a valid case to go into
|
||||
// the default case), but in reality this case will never exist as this
|
||||
// optional will never be nil.
|
||||
let duration = Optional(duration)
|
||||
switch duration {
|
||||
case .nanoseconds(let value):
|
||||
self.recordNanoseconds(value)
|
||||
|
|
@ -71,12 +89,14 @@ extension Timer {
|
|||
self.recordSeconds(value)
|
||||
case .never:
|
||||
self.record(0)
|
||||
default:
|
||||
self.record(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Timer {
|
||||
/// Convenience for recording a duration based on ``Duration``.
|
||||
/// Convenience for recording a duration based on `Duration`.
|
||||
///
|
||||
/// `Duration` will be converted to an `Int64` number of nanoseconds, and then recorded with nanosecond precision.
|
||||
///
|
||||
|
|
@ -95,4 +115,46 @@ extension Timer {
|
|||
|
||||
self.recordNanoseconds(nanoseconds.partialValue)
|
||||
}
|
||||
|
||||
#if compiler(>=6.0)
|
||||
/// Convenience for measuring duration of a closure.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
|
||||
/// - body: The closure to record the duration of.
|
||||
@inlinable
|
||||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
|
||||
public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
|
||||
clock: Clock = .continuous,
|
||||
// DO NOT FIX THE WHITESPACE IN THE NEXT LINE UNTIL 5.10 IS UNSUPPORTED
|
||||
// https://github.com/swiftlang/swift/issues/79285
|
||||
body: () throws(Failure) -> Result) throws(Failure) -> Result where Clock.Duration == Duration {
|
||||
let start = clock.now
|
||||
defer {
|
||||
self.record(duration: start.duration(to: clock.now))
|
||||
}
|
||||
return try body()
|
||||
}
|
||||
|
||||
/// Convenience for measuring duration of a closure.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
|
||||
/// - isolation: The isolation of the method. Defaults to the isolation of the caller.
|
||||
/// - body: The closure to record the duration of.
|
||||
@inlinable
|
||||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
|
||||
public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
|
||||
clock: Clock = .continuous,
|
||||
isolation: isolated (any Actor)? = #isolation,
|
||||
// DO NOT FIX THE WHITESPACE IN THE NEXT LINE UNTIL 5.10 IS UNSUPPORTED
|
||||
// https://github.com/swiftlang/swift/issues/79285
|
||||
body: () async throws(Failure) -> sending Result) async throws(Failure) -> sending Result where Clock.Duration == Duration {
|
||||
let start = clock.now
|
||||
defer {
|
||||
self.record(duration: start.duration(to: clock.now))
|
||||
}
|
||||
return try await body()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public final class TestMetrics: MetricsFactory {
|
|||
public typealias Label = String
|
||||
public typealias Dimensions = String
|
||||
|
||||
public struct FullKey {
|
||||
public struct FullKey: Sendable {
|
||||
let label: Label
|
||||
let dimensions: [(String, String)]
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ public final class TestMetrics: MetricsFactory {
|
|||
}
|
||||
|
||||
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return self.lock.withLock { () -> CounterHandler in
|
||||
self.lock.withLock { () -> CounterHandler in
|
||||
if let existing = self._counters[.init(label: label, dimensions: dimensions)] {
|
||||
return existing
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ public final class TestMetrics: MetricsFactory {
|
|||
}
|
||||
|
||||
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
|
||||
return self.lock.withLock { () -> MeterHandler in
|
||||
self.lock.withLock { () -> MeterHandler in
|
||||
if let existing = self._meters[.init(label: label, dimensions: dimensions)] {
|
||||
return existing
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ public final class TestMetrics: MetricsFactory {
|
|||
}
|
||||
|
||||
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
return self.lock.withLock { () -> RecorderHandler in
|
||||
self.lock.withLock { () -> RecorderHandler in
|
||||
if let existing = self._recorders[.init(label: label, dimensions: dimensions)] {
|
||||
return existing
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ public final class TestMetrics: MetricsFactory {
|
|||
}
|
||||
|
||||
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return self.lock.withLock { () -> TimerHandler in
|
||||
self.lock.withLock { () -> TimerHandler in
|
||||
if let existing = self._timers[.init(label: label, dimensions: dimensions)] {
|
||||
return existing
|
||||
}
|
||||
|
|
@ -148,15 +148,15 @@ public final class TestMetrics: MetricsFactory {
|
|||
extension TestMetrics.FullKey: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
self.label.hash(into: &hasher)
|
||||
self.dimensions.forEach { dim in
|
||||
for dim in self.dimensions {
|
||||
dim.0.hash(into: &hasher)
|
||||
dim.1.hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: TestMetrics.FullKey, rhs: TestMetrics.FullKey) -> Bool {
|
||||
return lhs.label == rhs.label &&
|
||||
Dictionary(uniqueKeysWithValues: lhs.dimensions) == Dictionary(uniqueKeysWithValues: rhs.dimensions)
|
||||
lhs.label == rhs.label
|
||||
&& Dictionary(uniqueKeysWithValues: lhs.dimensions) == Dictionary(uniqueKeysWithValues: rhs.dimensions)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,11 +193,11 @@ extension TestMetrics {
|
|||
// MARK: - Gauge
|
||||
|
||||
public func expectGauge(_ metric: Gauge) throws -> TestRecorder {
|
||||
return try self.expectRecorder(metric)
|
||||
try self.expectRecorder(metric)
|
||||
}
|
||||
|
||||
public func expectGauge(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestRecorder {
|
||||
return try self.expectRecorder(label, dimensions)
|
||||
try self.expectRecorder(label, dimensions)
|
||||
}
|
||||
|
||||
// MARK: - Meter
|
||||
|
|
@ -299,7 +299,7 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable {
|
|||
public let dimensions: [(String, String)]
|
||||
|
||||
public var key: TestMetrics.FullKey {
|
||||
return TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
}
|
||||
|
||||
let lock = NSLock()
|
||||
|
|
@ -318,33 +318,33 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable {
|
|||
}
|
||||
|
||||
public func reset() {
|
||||
return self.lock.withLock {
|
||||
self.lock.withLock {
|
||||
self._values = []
|
||||
}
|
||||
}
|
||||
|
||||
public var lastValue: Int64? {
|
||||
return self.last?.1
|
||||
self.last?.1
|
||||
}
|
||||
|
||||
public var totalValue: Int64 {
|
||||
return self.values.reduce(0, +)
|
||||
self.values.reduce(0, +)
|
||||
}
|
||||
|
||||
public var last: (Date, Int64)? {
|
||||
return self.lock.withLock {
|
||||
self.lock.withLock {
|
||||
self._values.last
|
||||
}
|
||||
}
|
||||
|
||||
public var values: [Int64] {
|
||||
return self.lock.withLock {
|
||||
self.lock.withLock {
|
||||
self._values.map { $0.1 }
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: TestCounter, rhs: TestCounter) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +354,7 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable {
|
|||
public let dimensions: [(String, String)]
|
||||
|
||||
public var key: TestMetrics.FullKey {
|
||||
return TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
}
|
||||
|
||||
let lock = NSLock()
|
||||
|
|
@ -430,23 +430,23 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable {
|
|||
}
|
||||
|
||||
public var lastValue: Double? {
|
||||
return self.last?.1
|
||||
self.last?.1
|
||||
}
|
||||
|
||||
public var last: (Date, Double)? {
|
||||
return self.lock.withLock {
|
||||
self.lock.withLock {
|
||||
self._values.last
|
||||
}
|
||||
}
|
||||
|
||||
public var values: [Double] {
|
||||
return self.lock.withLock {
|
||||
self.lock.withLock {
|
||||
self._values.map { $0.1 }
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: TestMeter, rhs: TestMeter) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,7 +457,7 @@ public final class TestRecorder: TestMetric, RecorderHandler, Equatable {
|
|||
public let aggregate: Bool
|
||||
|
||||
public var key: TestMetrics.FullKey {
|
||||
return TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
}
|
||||
|
||||
let lock = NSLock()
|
||||
|
|
@ -482,23 +482,23 @@ public final class TestRecorder: TestMetric, RecorderHandler, Equatable {
|
|||
}
|
||||
|
||||
public var lastValue: Double? {
|
||||
return self.last?.1
|
||||
self.last?.1
|
||||
}
|
||||
|
||||
public var last: (Date, Double)? {
|
||||
return self.lock.withLock {
|
||||
self.lock.withLock {
|
||||
self._values.last
|
||||
}
|
||||
}
|
||||
|
||||
public var values: [Double] {
|
||||
return self.lock.withLock {
|
||||
self.lock.withLock {
|
||||
self._values.map { $0.1 }
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: TestRecorder, rhs: TestRecorder) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +509,7 @@ public final class TestTimer: TestMetric, TimerHandler, Equatable {
|
|||
public let dimensions: [(String, String)]
|
||||
|
||||
public var key: TestMetrics.FullKey {
|
||||
return TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
TestMetrics.FullKey(label: self.label, dimensions: self.dimensions)
|
||||
}
|
||||
|
||||
let lock = NSLock()
|
||||
|
|
@ -543,23 +543,23 @@ public final class TestTimer: TestMetric, TimerHandler, Equatable {
|
|||
}
|
||||
|
||||
public var lastValue: Int64? {
|
||||
return self.last?.1
|
||||
self.last?.1
|
||||
}
|
||||
|
||||
public var values: [Int64] {
|
||||
return self.lock.withLock {
|
||||
return self._values.map { $0.1 }
|
||||
self.lock.withLock {
|
||||
self._values.map { $0.1 }
|
||||
}
|
||||
}
|
||||
|
||||
public var last: (Date, Int64)? {
|
||||
return self.lock.withLock {
|
||||
return self._values.last
|
||||
self.lock.withLock {
|
||||
self._values.last
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: TestTimer, rhs: TestTimer) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// LinuxMain.swift
|
||||
//
|
||||
import XCTest
|
||||
|
||||
///
|
||||
/// NOTE: This file was generated by generate_linux_tests.rb
|
||||
///
|
||||
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
||||
///
|
||||
|
||||
#if os(Linux) || os(FreeBSD)
|
||||
@testable import MetricsTests
|
||||
|
||||
XCTMain([
|
||||
testCase(MetricsExtensionsTests.allTests),
|
||||
testCase(MetricsTests.allTests),
|
||||
])
|
||||
#endif
|
||||
|
|
@ -12,10 +12,11 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import CoreMetrics
|
||||
import MetricsTestKit
|
||||
import XCTest
|
||||
|
||||
@testable import CoreMetrics
|
||||
|
||||
class MetricsTests: XCTestCase {
|
||||
func testCounters() throws {
|
||||
// bootstrap with our test metrics
|
||||
|
|
@ -25,12 +26,12 @@ class MetricsTests: XCTestCase {
|
|||
let name = "counter-\(UUID().uuidString)"
|
||||
let counter = Counter(label: name)
|
||||
let testCounter = try metrics.expectCounter(counter)
|
||||
let total = Int.random(in: 500 ... 1000)
|
||||
for _ in 0 ..< total {
|
||||
let total = Int.random(in: 500...1000)
|
||||
for _ in 0..<total {
|
||||
group.enter()
|
||||
DispatchQueue(label: "\(name)-queue").async {
|
||||
defer { group.leave() }
|
||||
counter.increment(by: Int.random(in: 0 ... 1000))
|
||||
counter.increment(by: Int.random(in: 0...1000))
|
||||
}
|
||||
}
|
||||
group.wait()
|
||||
|
|
@ -45,7 +46,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "counter-\(UUID().uuidString)"
|
||||
let value = Int.random(in: Int.min ... Int.max)
|
||||
let value = Int.random(in: Int.min...Int.max)
|
||||
Counter(label: name).increment(by: value)
|
||||
let counter = try metrics.expectCounter(name)
|
||||
XCTAssertEqual(counter.values.count, 1, "expected number of entries to match")
|
||||
|
|
@ -154,12 +155,12 @@ class MetricsTests: XCTestCase {
|
|||
let name = "recorder-\(UUID().uuidString)"
|
||||
let recorder = Recorder(label: name)
|
||||
let testRecorder = try metrics.expectRecorder(recorder)
|
||||
let total = Int.random(in: 500 ... 1000)
|
||||
for _ in 0 ..< total {
|
||||
let total = Int.random(in: 500...1000)
|
||||
for _ in 0..<total {
|
||||
group.enter()
|
||||
DispatchQueue(label: "\(name)-queue").async {
|
||||
defer { group.leave() }
|
||||
recorder.record(Int.random(in: Int.min ... Int.max))
|
||||
recorder.record(Int.random(in: Int.min...Int.max))
|
||||
}
|
||||
}
|
||||
group.wait()
|
||||
|
|
@ -172,12 +173,12 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let recorder = Recorder(label: "test-recorder")
|
||||
let testRecorder = try metrics.expectRecorder(recorder)
|
||||
let values = (0 ... 999).map { _ in Int32.random(in: Int32.min ... Int32.max) }
|
||||
for i in 0 ..< values.count {
|
||||
let values = (0...999).map { _ in Int32.random(in: Int32.min...Int32.max) }
|
||||
for i in 0..<values.count {
|
||||
recorder.record(values[i])
|
||||
}
|
||||
XCTAssertEqual(values.count, testRecorder.values.count, "expected number of entries to match")
|
||||
for i in 0 ..< values.count {
|
||||
for i in 0..<values.count {
|
||||
XCTAssertEqual(Int32(testRecorder.values[i]), values[i], "expected value #\(i) to match.")
|
||||
}
|
||||
}
|
||||
|
|
@ -188,12 +189,12 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let recorder = Recorder(label: "test-recorder")
|
||||
let testRecorder = try metrics.expectRecorder(recorder)
|
||||
let values = (0 ... 999).map { _ in Float.random(in: Float(Int32.min) ... Float(Int32.max)) }
|
||||
for i in 0 ..< values.count {
|
||||
let values = (0...999).map { _ in Float.random(in: Float(Int32.min)...Float(Int32.max)) }
|
||||
for i in 0..<values.count {
|
||||
recorder.record(values[i])
|
||||
}
|
||||
XCTAssertEqual(values.count, testRecorder.values.count, "expected number of entries to match")
|
||||
for i in 0 ..< values.count {
|
||||
for i in 0..<values.count {
|
||||
XCTAssertEqual(Float(testRecorder.values[i]), values[i], "expected value #\(i) to match.")
|
||||
}
|
||||
}
|
||||
|
|
@ -204,7 +205,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "recorder-\(UUID().uuidString)"
|
||||
let value = Double.random(in: Double(Int.min) ... Double(Int.max))
|
||||
let value = Double.random(in: Double(Int.min)...Double(Int.max))
|
||||
Recorder(label: name).record(value)
|
||||
let recorder = try metrics.expectRecorder(name)
|
||||
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
|
||||
|
|
@ -219,12 +220,12 @@ class MetricsTests: XCTestCase {
|
|||
let name = "timer-\(UUID().uuidString)"
|
||||
let timer = Timer(label: name)
|
||||
let testTimer = try metrics.expectTimer(timer)
|
||||
let total = Int.random(in: 500 ... 1000)
|
||||
for _ in 0 ..< total {
|
||||
let total = Int.random(in: 500...1000)
|
||||
for _ in 0..<total {
|
||||
group.enter()
|
||||
DispatchQueue(label: "\(name)-queue").async {
|
||||
defer { group.leave() }
|
||||
timer.recordNanoseconds(Int64.random(in: Int64.min ... Int64.max))
|
||||
timer.recordNanoseconds(Int64.random(in: Int64.min...Int64.max))
|
||||
}
|
||||
}
|
||||
group.wait()
|
||||
|
|
@ -237,7 +238,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "timer-\(UUID().uuidString)"
|
||||
let value = Int64.random(in: Int64.min ... Int64.max)
|
||||
let value = Int64.random(in: Int64.min...Int64.max)
|
||||
Timer(label: name).recordNanoseconds(value)
|
||||
let timer = try metrics.expectTimer(name)
|
||||
XCTAssertEqual(timer.values.count, 1, "expected number of entries to match")
|
||||
|
|
@ -252,22 +253,22 @@ class MetricsTests: XCTestCase {
|
|||
let timer = Timer(label: "test-timer")
|
||||
let testTimer = try metrics.expectTimer(timer)
|
||||
// nano
|
||||
let nano = Int64.random(in: 0 ... 5)
|
||||
let nano = Int64.random(in: 0...5)
|
||||
timer.recordNanoseconds(nano)
|
||||
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[0], nano, "expected value to match")
|
||||
// micro
|
||||
let micro = Int64.random(in: 0 ... 5)
|
||||
let micro = Int64.random(in: 0...5)
|
||||
timer.recordMicroseconds(micro)
|
||||
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[1], micro * 1000, "expected value to match")
|
||||
// milli
|
||||
let milli = Int64.random(in: 0 ... 5)
|
||||
let milli = Int64.random(in: 0...5)
|
||||
timer.recordMilliseconds(milli)
|
||||
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[2], milli * 1_000_000, "expected value to match")
|
||||
// seconds
|
||||
let sec = Int64.random(in: 0 ... 5)
|
||||
let sec = Int64.random(in: 0...5)
|
||||
timer.recordSeconds(sec)
|
||||
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[3], sec * 1_000_000_000, "expected value to match")
|
||||
|
|
@ -341,7 +342,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "gauge-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
let gauge = Gauge(label: name)
|
||||
gauge.record(value)
|
||||
let recorder = try metrics.expectRecorder(gauge)
|
||||
|
|
@ -355,7 +356,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "gauge-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
Gauge(label: name).record(value)
|
||||
let recorder = try metrics.expectRecorder(name)
|
||||
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
|
||||
|
|
@ -368,7 +369,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "meter-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
let meter = Meter(label: name)
|
||||
meter.set(value)
|
||||
let testMeter = try metrics.expectMeter(meter)
|
||||
|
|
@ -382,7 +383,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "meter-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
Meter(label: name).set(value)
|
||||
let testMeter = try metrics.expectMeter(name)
|
||||
XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match")
|
||||
|
|
@ -395,12 +396,12 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let meter = Meter(label: name)
|
||||
let testMeter = try metrics.expectMeter(meter)
|
||||
let values = (0 ... 999).map { _ in Int32.random(in: Int32.min ... Int32.max) }
|
||||
for i in 0 ..< values.count {
|
||||
let values = (0...999).map { _ in Int32.random(in: Int32.min...Int32.max) }
|
||||
for i in 0..<values.count {
|
||||
meter.set(values[i])
|
||||
}
|
||||
XCTAssertEqual(values.count, testMeter.values.count, "expected number of entries to match")
|
||||
for i in 0 ..< values.count {
|
||||
for i in 0..<values.count {
|
||||
XCTAssertEqual(Int32(testMeter.values[i]), values[i], "expected value #\(i) to match.")
|
||||
}
|
||||
}
|
||||
|
|
@ -411,12 +412,12 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
let meter = Meter(label: name)
|
||||
let testMeter = try metrics.expectMeter(meter)
|
||||
let values = (0 ... 999).map { _ in Float.random(in: Float(Int32.min) ... Float(Int32.max)) }
|
||||
for i in 0 ..< values.count {
|
||||
let values = (0...999).map { _ in Float.random(in: Float(Int32.min)...Float(Int32.max)) }
|
||||
for i in 0..<values.count {
|
||||
meter.set(values[i])
|
||||
}
|
||||
XCTAssertEqual(values.count, testMeter.values.count, "expected number of entries to match")
|
||||
for i in 0 ..< values.count {
|
||||
for i in 0..<values.count {
|
||||
XCTAssertEqual(Float(testMeter.values[i]), values[i], "expected value #\(i) to match.")
|
||||
}
|
||||
}
|
||||
|
|
@ -430,8 +431,8 @@ class MetricsTests: XCTestCase {
|
|||
let name = "meter-\(UUID().uuidString)"
|
||||
let meter = Meter(label: name)
|
||||
let testMeter = try metrics.expectMeter(meter)
|
||||
let values = (500 ... 1000).map { _ in Double.random(in: 0 ... Double(Int32.max)) }
|
||||
for i in 0 ..< values.count {
|
||||
let values = (500...1000).map { _ in Double.random(in: 0...Double(Int32.max)) }
|
||||
for i in 0..<values.count {
|
||||
group.enter()
|
||||
DispatchQueue(label: "\(name)-queue").async {
|
||||
defer { group.leave() }
|
||||
|
|
@ -453,8 +454,8 @@ class MetricsTests: XCTestCase {
|
|||
let meter = Meter(label: name)
|
||||
let testMeter = try metrics.expectMeter(meter)
|
||||
|
||||
let values = (500 ... 1000).map { _ in Double.random(in: 0 ... Double(Int32.max)) }
|
||||
for i in 0 ..< values.count {
|
||||
let values = (500...1000).map { _ in Double.random(in: 0...Double(Int32.max)) }
|
||||
for i in 0..<values.count {
|
||||
group.enter()
|
||||
DispatchQueue(label: "\(name)-queue").async {
|
||||
defer { group.leave() }
|
||||
|
|
@ -523,17 +524,17 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
|
||||
// run the test
|
||||
let name = UUID().uuidString
|
||||
let value = Int.random(in: Int.min ... Int.max)
|
||||
let value = Int.random(in: Int.min...Int.max)
|
||||
let muxCounter = Counter(label: name)
|
||||
muxCounter.increment(by: value)
|
||||
factories.forEach { factory in
|
||||
for factory in factories {
|
||||
let counter = factory.counters.first
|
||||
XCTAssertEqual(counter?.label, name, "expected label to match")
|
||||
XCTAssertEqual(counter?.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(counter?.lastValue, Int64(value), "expected value to match")
|
||||
}
|
||||
muxCounter.reset()
|
||||
factories.forEach { factory in
|
||||
for factory in factories {
|
||||
let counter = factory.counters.first
|
||||
XCTAssertEqual(counter?.values.count, 0, "expected number of entries to match")
|
||||
}
|
||||
|
|
@ -545,10 +546,10 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
|
||||
// run the test
|
||||
let name = UUID().uuidString
|
||||
let value = Double.random(in: 0 ... 1)
|
||||
let value = Double.random(in: 0...1)
|
||||
let muxMeter = Meter(label: name)
|
||||
muxMeter.set(value)
|
||||
factories.forEach { factory in
|
||||
for factory in factories {
|
||||
let meter = factory.meters.first
|
||||
XCTAssertEqual(meter?.label, name, "expected label to match")
|
||||
XCTAssertEqual(meter?.values.count, 1, "expected number of entries to match")
|
||||
|
|
@ -562,10 +563,10 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
|
||||
// run the test
|
||||
let name = UUID().uuidString
|
||||
let value = Double.random(in: 0 ... 1)
|
||||
let value = Double.random(in: 0...1)
|
||||
let muxRecorder = Recorder(label: name)
|
||||
muxRecorder.record(value)
|
||||
factories.forEach { factory in
|
||||
for factory in factories {
|
||||
let recorder = factory.recorders.first
|
||||
XCTAssertEqual(recorder?.label, name, "expected label to match")
|
||||
XCTAssertEqual(recorder?.values.count, 1, "expected number of entries to match")
|
||||
|
|
@ -579,20 +580,24 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
|
||||
// run the test
|
||||
let name = UUID().uuidString
|
||||
let seconds = Int.random(in: 1 ... 10)
|
||||
let seconds = Int.random(in: 1...10)
|
||||
let muxTimer = Timer(label: name, preferredDisplayUnit: .minutes)
|
||||
muxTimer.recordSeconds(seconds)
|
||||
factories.forEach { factory in
|
||||
for factory in factories {
|
||||
let timer = factory.timers.first
|
||||
XCTAssertEqual(timer?.label, name, "expected label to match")
|
||||
XCTAssertEqual(timer?.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(timer?.values[0], Int64(seconds * 1_000_000_000), "expected value to match")
|
||||
XCTAssertEqual(timer?.displayUnit, .minutes, "expected value to match")
|
||||
XCTAssertEqual(timer?.valueInPreferredUnit(atIndex: 0), Double(seconds) / 60.0, "seconds should be returned as minutes")
|
||||
XCTAssertEqual(
|
||||
timer?.valueInPreferredUnit(atIndex: 0),
|
||||
Double(seconds) / 60.0,
|
||||
"seconds should be returned as minutes"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testCustomFactory() {
|
||||
func testCustomHandler() {
|
||||
final class CustomHandler: CounterHandler {
|
||||
func increment<DataType>(by: DataType) where DataType: BinaryInteger {}
|
||||
func reset() {}
|
||||
|
|
@ -604,12 +609,85 @@ class MetricsTests: XCTestCase {
|
|||
XCTAssertTrue(counter2._handler is CustomHandler, "expected custom log handler")
|
||||
}
|
||||
|
||||
func testCustomFactory() {
|
||||
// @unchecked Sendable is okay here - locking is done manually.
|
||||
final class CustomFactory: MetricsFactory, @unchecked Sendable {
|
||||
|
||||
init(handler: CustomHandler) {
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
final class CustomHandler: CounterHandler {
|
||||
func increment<DataType>(by: DataType) where DataType: BinaryInteger {}
|
||||
func reset() {}
|
||||
}
|
||||
private let handler: CustomHandler
|
||||
private let lock: NSLock = NSLock()
|
||||
private var locked_didCallDestroyCounter: Bool = false
|
||||
var didCallDestroyCounter: Bool {
|
||||
self.lock.lock()
|
||||
defer {
|
||||
lock.unlock()
|
||||
}
|
||||
return self.locked_didCallDestroyCounter
|
||||
}
|
||||
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> any CoreMetrics.CounterHandler {
|
||||
handler
|
||||
}
|
||||
|
||||
func makeRecorder(
|
||||
label: String,
|
||||
dimensions: [(String, String)],
|
||||
aggregate: Bool
|
||||
) -> any CoreMetrics.RecorderHandler {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> any CoreMetrics.TimerHandler {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
|
||||
func destroyCounter(_ handler: any CoreMetrics.CounterHandler) {
|
||||
XCTAssertTrue(
|
||||
handler === self.handler,
|
||||
"The handler to be destroyed doesn't match the expected handler."
|
||||
)
|
||||
self.lock.lock()
|
||||
defer {
|
||||
lock.unlock()
|
||||
}
|
||||
self.locked_didCallDestroyCounter = true
|
||||
}
|
||||
|
||||
func destroyRecorder(_ handler: any CoreMetrics.RecorderHandler) {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
|
||||
func destroyTimer(_ handler: any CoreMetrics.TimerHandler) {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
}
|
||||
|
||||
let handler = CustomFactory.CustomHandler()
|
||||
let factory = CustomFactory(handler: handler)
|
||||
|
||||
XCTAssertFalse(factory.didCallDestroyCounter)
|
||||
do {
|
||||
let counter1 = Counter(label: "foo", factory: factory)
|
||||
XCTAssertTrue(counter1._handler is CustomFactory.CustomHandler, "expected a custom metrics handler")
|
||||
XCTAssertTrue(counter1._factory is CustomFactory, "expected a custom metrics factory")
|
||||
counter1.destroy()
|
||||
}
|
||||
XCTAssertTrue(factory.didCallDestroyCounter)
|
||||
}
|
||||
|
||||
func testDestroyingGauge() throws {
|
||||
let metrics = TestMetrics()
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
|
||||
let name = "gauge-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
|
||||
let gauge = Gauge(label: name)
|
||||
gauge.record(value)
|
||||
|
|
@ -631,7 +709,11 @@ class MetricsTests: XCTestCase {
|
|||
XCTAssertEqual(recorderAgain.values.first, -value, "expected value to match")
|
||||
|
||||
let identityAgain = ObjectIdentifier(recorderAgain)
|
||||
XCTAssertNotEqual(identity, identityAgain, "since the cached metric was released, the created a new should have a different identity")
|
||||
XCTAssertNotEqual(
|
||||
identity,
|
||||
identityAgain,
|
||||
"since the cached metric was released, the created a new should have a different identity"
|
||||
)
|
||||
}
|
||||
|
||||
func testDestroyingMeter() throws {
|
||||
|
|
@ -639,7 +721,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
|
||||
let name = "meter-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
|
||||
let meter = Meter(label: name)
|
||||
meter.set(value)
|
||||
|
|
@ -661,7 +743,11 @@ class MetricsTests: XCTestCase {
|
|||
XCTAssertEqual(testMeterAgain.values.first, -value, "expected value to match")
|
||||
|
||||
let identityAgain = ObjectIdentifier(testMeterAgain)
|
||||
XCTAssertNotEqual(identity, identityAgain, "since the cached metric was released, the created a new should have a different identity")
|
||||
XCTAssertNotEqual(
|
||||
identity,
|
||||
identityAgain,
|
||||
"since the cached metric was released, the created a new should have a different identity"
|
||||
)
|
||||
}
|
||||
|
||||
func testDestroyingCounter() throws {
|
||||
|
|
@ -669,7 +755,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
|
||||
let name = "counter-\(UUID().uuidString)"
|
||||
let value = Int.random(in: 0 ... 1000)
|
||||
let value = Int.random(in: 0...1000)
|
||||
|
||||
let counter = Counter(label: name)
|
||||
counter.increment(by: value)
|
||||
|
|
@ -691,7 +777,11 @@ class MetricsTests: XCTestCase {
|
|||
XCTAssertEqual(testCounterAgain.values.first, Int64(value), "expected value to match")
|
||||
|
||||
let identityAgain = ObjectIdentifier(counterAgain)
|
||||
XCTAssertNotEqual(identity, identityAgain, "since the cached metric was released, the created a new should have a different identity")
|
||||
XCTAssertNotEqual(
|
||||
identity,
|
||||
identityAgain,
|
||||
"since the cached metric was released, the created a new should have a different identity"
|
||||
)
|
||||
}
|
||||
|
||||
func testDestroyingTimer() throws {
|
||||
|
|
@ -699,7 +789,7 @@ class MetricsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
|
||||
let name = "timer-\(UUID().uuidString)"
|
||||
let value = Int64.random(in: 0 ... 1000)
|
||||
let value = Int64.random(in: 0...1000)
|
||||
|
||||
let timer = Timer(label: name)
|
||||
timer.recordNanoseconds(value)
|
||||
|
|
@ -720,7 +810,11 @@ class MetricsTests: XCTestCase {
|
|||
XCTAssertEqual(testTimerAgain.values.first, value, "expected value to match")
|
||||
|
||||
let identityAgain = ObjectIdentifier(timerAgain)
|
||||
XCTAssertNotEqual(identity, identityAgain, "since the cached metric was released, the created a new should have a different identity")
|
||||
XCTAssertNotEqual(
|
||||
identity,
|
||||
identityAgain,
|
||||
"since the cached metric was released, the created a new should have a different identity"
|
||||
)
|
||||
}
|
||||
|
||||
func testDescriptions() throws {
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import CoreMetrics
|
||||
@testable import Metrics
|
||||
import MetricsTestKit
|
||||
import XCTest
|
||||
|
||||
@testable import CoreMetrics
|
||||
@testable import Metrics
|
||||
|
||||
class MetricsExtensionsTests: XCTestCase {
|
||||
func testTimerBlock() throws {
|
||||
// bootstrap with our test metrics
|
||||
|
|
@ -40,7 +41,7 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
// run the test
|
||||
let timer = Timer(label: "test-timer")
|
||||
let testTimer = try metrics.expectTimer(timer)
|
||||
let timeInterval = TimeInterval(Double.random(in: 1 ... 500))
|
||||
let timeInterval = TimeInterval(Double.random(in: 1...500))
|
||||
timer.record(timeInterval)
|
||||
XCTAssertEqual(1, testTimer.values.count, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[0], Int64(timeInterval * 1_000_000_000), "expected value to match")
|
||||
|
|
@ -54,22 +55,22 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
let timer = Timer(label: "test-timer")
|
||||
let testTimer = try metrics.expectTimer(timer)
|
||||
// nano
|
||||
let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1 ... 500))
|
||||
let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1...500))
|
||||
timer.record(nano)
|
||||
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(Int(testTimer.values[0]), nano.nano(), "expected value to match")
|
||||
// micro
|
||||
let micro = DispatchTimeInterval.microseconds(Int.random(in: 1 ... 500))
|
||||
let micro = DispatchTimeInterval.microseconds(Int.random(in: 1...500))
|
||||
timer.record(micro)
|
||||
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
|
||||
XCTAssertEqual(Int(testTimer.values[1]), micro.nano(), "expected value to match")
|
||||
// milli
|
||||
let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1 ... 500))
|
||||
let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1...500))
|
||||
timer.record(milli)
|
||||
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
|
||||
XCTAssertEqual(Int(testTimer.values[2]), milli.nano(), "expected value to match")
|
||||
// seconds
|
||||
let sec = DispatchTimeInterval.seconds(Int.random(in: 1 ... 500))
|
||||
let sec = DispatchTimeInterval.seconds(Int.random(in: 1...500))
|
||||
timer.record(sec)
|
||||
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
|
||||
XCTAssertEqual(Int(testTimer.values[3]), sec.nano(), "expected value to match")
|
||||
|
|
@ -92,7 +93,11 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
|
||||
let testTimer = try metrics.expectTimer(timer)
|
||||
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(UInt64(testTimer.values.first!), end.uptimeNanoseconds - start.uptimeNanoseconds, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
UInt64(testTimer.values.first!),
|
||||
end.uptimeNanoseconds - start.uptimeNanoseconds,
|
||||
"expected value to match"
|
||||
)
|
||||
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +134,7 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
MetricsSystem.bootstrapInternal(metrics)
|
||||
|
||||
let name = "timer-\(UUID().uuidString)"
|
||||
let value = Int64.random(in: 0 ... 1000)
|
||||
let value = Int64.random(in: 0...1000)
|
||||
|
||||
let timer = Timer(label: name)
|
||||
timer.recordNanoseconds(value)
|
||||
|
|
@ -140,7 +145,7 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
|
||||
|
||||
let secondsName = "timer-seconds-\(UUID().uuidString)"
|
||||
let secondsValue = Int64.random(in: 0 ... 1000)
|
||||
let secondsValue = Int64.random(in: 0...1000)
|
||||
let secondsTimer = Timer(label: secondsName, preferredDisplayUnit: .seconds)
|
||||
secondsTimer.recordSeconds(secondsValue)
|
||||
|
||||
|
|
@ -153,39 +158,121 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
let metrics = TestMetrics()
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
|
||||
let value = Double.random(in: 0 ... 1000)
|
||||
let value = Double.random(in: 0...1000)
|
||||
let timer = Timer(label: "test", preferredDisplayUnit: .seconds)
|
||||
timer.recordSeconds(value)
|
||||
|
||||
let testTimer = try metrics.expectTimer(timer)
|
||||
|
||||
testTimer.preferDisplayUnit(.nanoseconds)
|
||||
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value * 1000 * 1000 * 1000, accuracy: 1.0, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
testTimer.valueInPreferredUnit(atIndex: 0),
|
||||
value * 1000 * 1000 * 1000,
|
||||
accuracy: 1.0,
|
||||
"expected value to match"
|
||||
)
|
||||
|
||||
testTimer.preferDisplayUnit(.microseconds)
|
||||
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value * 1000 * 1000, accuracy: 0.001, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
testTimer.valueInPreferredUnit(atIndex: 0),
|
||||
value * 1000 * 1000,
|
||||
accuracy: 0.001,
|
||||
"expected value to match"
|
||||
)
|
||||
|
||||
testTimer.preferDisplayUnit(.milliseconds)
|
||||
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value * 1000, accuracy: 0.000001, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
testTimer.valueInPreferredUnit(atIndex: 0),
|
||||
value * 1000,
|
||||
accuracy: 0.000001,
|
||||
"expected value to match"
|
||||
)
|
||||
|
||||
testTimer.preferDisplayUnit(.seconds)
|
||||
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value, accuracy: 0.000000001, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
testTimer.valueInPreferredUnit(atIndex: 0),
|
||||
value,
|
||||
accuracy: 0.000000001,
|
||||
"expected value to match"
|
||||
)
|
||||
|
||||
testTimer.preferDisplayUnit(.minutes)
|
||||
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value / 60, accuracy: 0.000000001, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
testTimer.valueInPreferredUnit(atIndex: 0),
|
||||
value / 60,
|
||||
accuracy: 0.000000001,
|
||||
"expected value to match"
|
||||
)
|
||||
|
||||
testTimer.preferDisplayUnit(.hours)
|
||||
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value / (60 * 60), accuracy: 0.000000001, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
testTimer.valueInPreferredUnit(atIndex: 0),
|
||||
value / (60 * 60),
|
||||
accuracy: 0.000000001,
|
||||
"expected value to match"
|
||||
)
|
||||
|
||||
testTimer.preferDisplayUnit(.days)
|
||||
XCTAssertEqual(testTimer.valueInPreferredUnit(atIndex: 0), value / (60 * 60 * 24), accuracy: 0.000000001, "expected value to match")
|
||||
XCTAssertEqual(
|
||||
testTimer.valueInPreferredUnit(atIndex: 0),
|
||||
value / (60 * 60 * 24),
|
||||
accuracy: 0.000000001,
|
||||
"expected value to match"
|
||||
)
|
||||
}
|
||||
|
||||
#if compiler(>=6.0)
|
||||
func testTimerMeasure() async throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "timer-\(UUID().uuidString)"
|
||||
let delay = Duration.milliseconds(5)
|
||||
let timer = Timer(label: name)
|
||||
try await timer.measure {
|
||||
try await Task.sleep(for: delay)
|
||||
}
|
||||
|
||||
let expectedTimer = try metrics.expectTimer(name)
|
||||
XCTAssertEqual(1, expectedTimer.values.count, "expected number of entries to match")
|
||||
XCTAssertGreaterThan(expectedTimer.values[0], delay.nanosecondsClamped, "expected delay to match")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testTimerMeasureFromMainActor() async throws {
|
||||
// bootstrap with our test metrics
|
||||
let metrics = TestMetrics()
|
||||
MetricsSystem.bootstrapInternal(metrics)
|
||||
// run the test
|
||||
let name = "timer-\(UUID().uuidString)"
|
||||
let delay = Duration.milliseconds(5)
|
||||
let timer = Timer(label: name)
|
||||
try await timer.measure {
|
||||
try await Task.sleep(for: delay)
|
||||
}
|
||||
|
||||
let expectedTimer = try metrics.expectTimer(name)
|
||||
XCTAssertEqual(1, expectedTimer.values.count, "expected number of entries to match")
|
||||
XCTAssertGreaterThan(expectedTimer.values[0], delay.nanosecondsClamped, "expected delay to match")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// https://bugs.swift.org/browse/SR-6310
|
||||
extension DispatchTimeInterval {
|
||||
func nano() -> Int {
|
||||
switch self {
|
||||
// This wrapping in a optional is a workaround because DispatchTimeInterval
|
||||
// is a non-frozen public enum and Dispatch is built with library evolution
|
||||
// mode turned on.
|
||||
// This means we should have an `@unknown default` case, but this breaks
|
||||
// on non-Darwin platforms.
|
||||
// Switching over an optional means that the `.none` case will map to
|
||||
// `default` (which means we'll always have a valid case to go into
|
||||
// the default case), but in reality this case will never exist as this
|
||||
// optional will never be nil.
|
||||
let interval = Optional(self)
|
||||
switch interval {
|
||||
case .nanoseconds(let value):
|
||||
return value
|
||||
case .microseconds(let value):
|
||||
|
|
@ -196,6 +283,30 @@ extension DispatchTimeInterval {
|
|||
return value * 1_000_000_000
|
||||
case .never:
|
||||
return 0
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.7)
|
||||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
|
||||
extension Swift.Duration {
|
||||
fileprivate var nanosecondsClamped: Int64 {
|
||||
let components = self.components
|
||||
|
||||
let secondsComponentNanos = components.seconds.multipliedReportingOverflow(by: 1_000_000_000)
|
||||
let attosCompononentNanos = components.attoseconds / 1_000_000_000
|
||||
let combinedNanos = secondsComponentNanos.partialValue.addingReportingOverflow(attosCompononentNanos)
|
||||
|
||||
guard
|
||||
!secondsComponentNanos.overflow,
|
||||
!combinedNanos.overflow
|
||||
else {
|
||||
return .max
|
||||
}
|
||||
|
||||
return combinedNanos.partialValue
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import CoreMetrics
|
||||
import Dispatch
|
||||
import MetricsTestKit
|
||||
import XCTest
|
||||
|
||||
@testable import CoreMetrics
|
||||
|
||||
class SendableTest: XCTestCase {
|
||||
func testSendableMetrics() async throws {
|
||||
// bootstrap with our test metrics
|
||||
|
|
@ -25,7 +26,7 @@ class SendableTest: XCTestCase {
|
|||
|
||||
do {
|
||||
let name = "counter-\(UUID().uuidString)"
|
||||
let value = Int.random(in: 0 ... 1000)
|
||||
let value = Int.random(in: 0...1000)
|
||||
let counter = Counter(label: name)
|
||||
|
||||
let task = Task.detached { () -> [Int64] in
|
||||
|
|
@ -40,7 +41,7 @@ class SendableTest: XCTestCase {
|
|||
|
||||
do {
|
||||
let name = "floating-point-counter-\(UUID().uuidString)"
|
||||
let value = Double.random(in: 0 ... 0.9999)
|
||||
let value = Double.random(in: 0...0.9999)
|
||||
let counter = FloatingPointCounter(label: name)
|
||||
|
||||
let task = Task.detached { () -> Double in
|
||||
|
|
@ -54,7 +55,7 @@ class SendableTest: XCTestCase {
|
|||
|
||||
do {
|
||||
let name = "recorder-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
let recorder = Recorder(label: name)
|
||||
|
||||
let task = Task.detached { () -> [Double] in
|
||||
|
|
@ -69,7 +70,7 @@ class SendableTest: XCTestCase {
|
|||
|
||||
do {
|
||||
let name = "meter-\(UUID().uuidString)"
|
||||
let value = Double.random(in: -1000 ... 1000)
|
||||
let value = Double.random(in: -1000...1000)
|
||||
let meter = Meter(label: name)
|
||||
|
||||
let task = Task.detached { () -> [Double] in
|
||||
|
|
@ -84,7 +85,7 @@ class SendableTest: XCTestCase {
|
|||
|
||||
do {
|
||||
let name = "timer-\(UUID().uuidString)"
|
||||
let value = Int64.random(in: 0 ... 1000)
|
||||
let value = Int64.random(in: 0...1000)
|
||||
let timer = Timer(label: name)
|
||||
|
||||
let task = Task.detached { () -> [Int64] in
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
ARG swift_version=5.7
|
||||
ARG ubuntu_version=focal
|
||||
ARG base_image=swift:$swift_version-$ubuntu_version
|
||||
FROM $base_image
|
||||
# needed to do again after FROM due to docker limitation
|
||||
ARG swift_version
|
||||
ARG ubuntu_version
|
||||
|
||||
# set as UTF-8
|
||||
RUN apt-get update && apt-get install -y locales locales-all
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US.UTF-8
|
||||
|
||||
# dependencies
|
||||
RUN apt-get update && apt-get install -y wget
|
||||
RUN apt-get update && apt-get install -y lsof dnsutils netcat-openbsd net-tools curl jq # used by integration tests
|
||||
|
||||
# ruby and jazzy for docs generation
|
||||
RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev build-essential
|
||||
|
||||
# tools
|
||||
RUN mkdir -p $HOME/.tools
|
||||
RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile
|
||||
|
||||
# swiftformat (until part of the toolchain)
|
||||
|
||||
ARG swiftformat_version=0.49.6
|
||||
RUN if [ "${swift_version}" = "5.7" ]; then git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format; fi
|
||||
RUN if [ "${swift_version}" = "5.7" ]; then cd $HOME/.tools/swift-format && swift build -c release; fi
|
||||
RUN if [ "${swift_version}" = "5.7" ]; then ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat; fi
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
runtime-setup:
|
||||
image: swift-metrics:20.04-5.8
|
||||
build:
|
||||
args:
|
||||
ubuntu_version: "focal"
|
||||
swift_version: "5.8"
|
||||
|
||||
test:
|
||||
image: swift-metrics:20.04-5.8
|
||||
environment: []
|
||||
#- SANITIZER_ARG=--sanitize=thread
|
||||
|
||||
shell:
|
||||
image: swift-metrics:20.04-5.8
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
runtime-setup:
|
||||
image: swift-metrics:22.04-5.10
|
||||
build:
|
||||
args:
|
||||
base_image: "swiftlang/swift:nightly-5.10-jammy"
|
||||
|
||||
test:
|
||||
image: swift-metrics:22.04-5.10
|
||||
environment: []
|
||||
#- SANITIZER_ARG=--sanitize=thread
|
||||
|
||||
shell:
|
||||
image: swift-metrics:22.04-5.10
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
runtime-setup:
|
||||
image: swift-metrics:22.04-5.9
|
||||
build:
|
||||
args:
|
||||
ubuntu_version: "jammy"
|
||||
swift_version: "5.9"
|
||||
|
||||
test:
|
||||
image: swift-metrics:22.04-5.9
|
||||
environment: []
|
||||
#- SANITIZER_ARG=--sanitize=thread
|
||||
|
||||
shell:
|
||||
image: swift-metrics:22.04-5.9
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
runtime-setup:
|
||||
image: swift-metrics:22.04-main
|
||||
build:
|
||||
args:
|
||||
base_image: "swiftlang/swift:nightly-main-jammy"
|
||||
|
||||
test:
|
||||
image: swift-metrics:22.04-main
|
||||
environment: []
|
||||
#- SANITIZER_ARG=--sanitize=thread
|
||||
|
||||
shell:
|
||||
image: swift-metrics:22.04-main
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# this file is not designed to be run directly
|
||||
# instead, use the docker-compose.<os>.<swift> files
|
||||
# eg docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.1804.50.yaml run test
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
runtime-setup:
|
||||
image: swift-metrics:default
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
||||
common: &common
|
||||
image: swift-metrics:default
|
||||
depends_on: [runtime-setup]
|
||||
volumes:
|
||||
- ~/.ssh:/root/.ssh
|
||||
- ..:/code:z
|
||||
working_dir: /code
|
||||
cap_drop:
|
||||
- CAP_NET_RAW
|
||||
- CAP_NET_BIND_SERVICE
|
||||
|
||||
soundness:
|
||||
<<: *common
|
||||
command: /bin/bash -xcl "./scripts/soundness.sh"
|
||||
|
||||
test:
|
||||
<<: *common
|
||||
command: /bin/bash -xcl "swift test -Xswiftc -warnings-as-errors --enable-test-discovery $${SANITIZER_ARG-}"
|
||||
|
||||
# util
|
||||
|
||||
shell:
|
||||
<<: *common
|
||||
entrypoint: /bin/bash -l
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the Swift Metrics API open source project
|
||||
##
|
||||
## Copyright (c) 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftNIO open source project
|
||||
##
|
||||
## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
set -eu
|
||||
|
||||
function usage() {
|
||||
echo "$0 [-u] version"
|
||||
echo
|
||||
echo "OPTIONS:"
|
||||
echo " -u: Additionally upload the podspec"
|
||||
}
|
||||
|
||||
upload=false
|
||||
while getopts ":u" opt; do
|
||||
case $opt in
|
||||
u)
|
||||
upload=true
|
||||
;;
|
||||
\?)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Must provide target version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$1
|
||||
name="SwiftMetrics"
|
||||
|
||||
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
tmpdir=$(mktemp -d /tmp/.build_podspecsXXXXXX)
|
||||
echo "Building podspec in $tmpdir"
|
||||
|
||||
cat >> "${tmpdir}/${name}.podspec" <<- EOF
|
||||
Pod::Spec.new do |s|
|
||||
s.name = '$name'
|
||||
s.version = '$version'
|
||||
s.license = { :type => 'Apache 2.0', :file => 'LICENSE.txt' }
|
||||
s.summary = 'A Metrics API for Swift.'
|
||||
s.homepage = 'https://github.com/apple/swift-metrics'
|
||||
s.author = 'Apple Inc.'
|
||||
s.source = { :git => 'https://github.com/apple/swift-metrics.git', :tag => s.version.to_s }
|
||||
s.documentation_url = 'https://apple.github.io/swift-metrics'
|
||||
s.module_name = '$name'
|
||||
|
||||
s.swift_version = '4.2'
|
||||
s.cocoapods_version = '>=1.6.0'
|
||||
s.ios.deployment_target = '8.0'
|
||||
s.osx.deployment_target = '10.9'
|
||||
s.tvos.deployment_target = '9.0'
|
||||
s.watchos.deployment_target = '2.0'
|
||||
|
||||
s.source_files = 'Sources/CoreMetrics/**/*.swift'
|
||||
end
|
||||
EOF
|
||||
|
||||
if $upload; then
|
||||
echo "Uploading ${tmpdir}/${name}.podspec"
|
||||
pod trunk push "${tmpdir}/${name}.podspec"
|
||||
else
|
||||
echo "Linting ${tmpdir}/${name}.podspec"
|
||||
pod spec lint "${tmpdir}/${name}.podspec"
|
||||
fi
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the Swift Metrics API open source project
|
||||
##
|
||||
## Copyright (c) 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftNIO open source project
|
||||
##
|
||||
## Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
set -eu
|
||||
|
||||
function usage() {
|
||||
echo >&2 "Usage: $0 REPO-GITHUB-URL NEW-VERSION OLD-VERSIONS..."
|
||||
echo >&2
|
||||
echo >&2 "This script requires a Swift 5.2+ toolchain."
|
||||
echo >&2
|
||||
echo >&2 "Examples:"
|
||||
echo >&2
|
||||
echo >&2 "Check between main and tag 2.1.1 of swift-nio:"
|
||||
echo >&2 " $0 https://github.com/apple/swift-nio main 2.1.1"
|
||||
echo >&2
|
||||
echo >&2 "Check between HEAD and commit 64cf63d7 using the provided toolchain:"
|
||||
echo >&2 " xcrun --toolchain org.swift.5120190702a $0 ../some-local-repo HEAD 64cf63d7"
|
||||
}
|
||||
|
||||
if [[ $# -lt 3 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmpdir=$(mktemp -d /tmp/.check-api_XXXXXX)
|
||||
repo_url=$1
|
||||
new_tag=$2
|
||||
shift 2
|
||||
|
||||
repodir="$tmpdir/repo"
|
||||
git clone "$repo_url" "$repodir"
|
||||
git -C "$repodir" fetch -q origin '+refs/pull/*:refs/remotes/origin/pr/*'
|
||||
cd "$repodir"
|
||||
git checkout -q "$new_tag"
|
||||
|
||||
for old_tag in "$@"; do
|
||||
echo "Checking public API breakages from $old_tag to $new_tag"
|
||||
|
||||
swift package diagnose-api-breaking-changes "$old_tag"
|
||||
done
|
||||
|
||||
echo done
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
set -eu
|
||||
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
contributors=$( cd "$here"/.. && git shortlog -es | cut -f2 | sed 's/^/- /' )
|
||||
|
||||
cat > "$here/../CONTRIBUTORS.txt" <<- EOF
|
||||
For the purpose of tracking copyright, this is the list of individuals and
|
||||
organizations who have contributed source code to the Swift Metrics API.
|
||||
|
||||
For employees of an organization/company where the copyright of work done
|
||||
by employees of that company is held by the company itself, only the company
|
||||
needs to be listed here.
|
||||
|
||||
## COPYRIGHT HOLDERS
|
||||
|
||||
- Apple Inc. (all contributors with '@apple.com')
|
||||
|
||||
### Contributors
|
||||
|
||||
$contributors
|
||||
|
||||
**Updating this list**
|
||||
|
||||
Please do not edit this file manually. It is generated using \`./scripts/generate_contributors_list.sh\`. If a name is misspelled or appearing multiple times: add an entry in \`./.mailmap\`
|
||||
EOF
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
swift package --disable-sandbox preview-documentation --target "$1"
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftNIO open source project
|
||||
##
|
||||
## Copyright (c) 2017-2019 Apple Inc. and the SwiftNIO project authors
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
set -eu
|
||||
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
function replace_acceptable_years() {
|
||||
# this needs to replace all acceptable forms with 'YEARS'
|
||||
sed -e 's/20[12][789012]-20[12][89012]/YEARS/' -e 's/20[12][89012]/YEARS/'
|
||||
}
|
||||
|
||||
printf "=> Checking for unacceptable language... "
|
||||
# This greps for unacceptable terminology. The square bracket[s] are so that
|
||||
# "git grep" doesn't find the lines that greps :).
|
||||
unacceptable_terms=(
|
||||
-e blacklis[t]
|
||||
-e whitelis[t]
|
||||
-e slav[e]
|
||||
-e sanit[y]
|
||||
)
|
||||
if git grep --color=never -i "${unacceptable_terms[@]}" > /dev/null; then
|
||||
printf "\033[0;31mUnacceptable language found.\033[0m\n"
|
||||
git grep -i "${unacceptable_terms[@]}"
|
||||
exit 1
|
||||
fi
|
||||
printf "\033[0;32mokay.\033[0m\n"
|
||||
|
||||
printf "=> Checking format... "
|
||||
|
||||
if [[ ! -x $(which swiftformat) ]]; then
|
||||
printf "\033[0;31mswiftformat not found!\033[0m\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FIRST_OUT="$(git status --porcelain)"
|
||||
swiftformat . > /dev/null 2>&1
|
||||
SECOND_OUT="$(git status --porcelain)"
|
||||
if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then
|
||||
printf "\033[0;31mformatting issues!\033[0m\n"
|
||||
git --no-pager diff
|
||||
exit 1
|
||||
else
|
||||
printf "\033[0;32mokay.\033[0m\n"
|
||||
fi
|
||||
|
||||
printf "=> Checking license headers\n"
|
||||
tmp=$(mktemp /tmp/.swift-metrics-soundness_XXXXXX)
|
||||
|
||||
for language in swift-or-c bash dtrace; do
|
||||
printf " * $language... "
|
||||
declare -a matching_files
|
||||
declare -a exceptions
|
||||
expections=( )
|
||||
matching_files=( -name '*' )
|
||||
case "$language" in
|
||||
swift-or-c)
|
||||
exceptions=( -name Package.swift -o -name "Package@swift-*.swift" )
|
||||
matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' )
|
||||
cat > "$tmp" <<"EOF"
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Metrics API open source project
|
||||
//
|
||||
// Copyright (c) YEARS 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
EOF
|
||||
;;
|
||||
bash)
|
||||
matching_files=( -name '*.sh' )
|
||||
cat > "$tmp" <<"EOF"
|
||||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the Swift Metrics API open source project
|
||||
##
|
||||
## Copyright (c) YEARS 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
EOF
|
||||
;;
|
||||
dtrace)
|
||||
matching_files=( -name '*.d' )
|
||||
cat > "$tmp" <<"EOF"
|
||||
#!/usr/sbin/dtrace -q -s
|
||||
/*===----------------------------------------------------------------------===*
|
||||
*
|
||||
* This source file is part of the Swift Metrics API open source project
|
||||
*
|
||||
* Copyright (c) YEARS 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
|
||||
*
|
||||
*===----------------------------------------------------------------------===*/
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
echo >&2 "ERROR: unknown language '$language'"
|
||||
;;
|
||||
esac
|
||||
|
||||
expected_lines=$(cat "$tmp" | wc -l)
|
||||
expected_sha=$(cat "$tmp" | shasum)
|
||||
|
||||
(
|
||||
cd "$here/.."
|
||||
find . \
|
||||
\( \! -path './.build/*' -a \
|
||||
\( "${matching_files[@]}" \) -a \
|
||||
\( \! \( "${exceptions[@]}" \) \) \) | while read line; do
|
||||
if [[ "$(cat "$line" | replace_acceptable_years | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then
|
||||
printf "\033[0;31mmissing headers in file '$line'!\033[0m\n"
|
||||
diff -u <(cat "$line" | replace_acceptable_years | head -n $expected_lines) "$tmp"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
printf "\033[0;32mokay.\033[0m\n"
|
||||
)
|
||||
done
|
||||
|
||||
rm "$tmp"
|
||||
Loading…
Reference in New Issue