Migrate to GitHub Actions (#151)

This commit is contained in:
Rick Newton-Rogers 2024-11-06 13:32:20 +00:00 committed by GitHub
parent 569db3a632
commit d720898dbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 451 additions and 699 deletions

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

14
.github/release.yml vendored Normal file
View File

@ -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

18
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,18 @@
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: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --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_nightly_6_0_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"

26
.github/workflows/pull_request.yml vendored Normal file
View File

@ -0,0 +1,26 @@
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: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --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_nightly_6_0_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

View File

@ -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

35
.licenseignore Normal file
View File

@ -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

68
.swift-format Normal file
View File

@ -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
}
}

View File

@ -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

View File

@ -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 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. 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).
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.
## How to contribute your work ## How to contribute your work

View File

@ -22,10 +22,6 @@ let package = Package(
.library(name: "Metrics", targets: ["Metrics"]), .library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]), .library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
], ],
dependencies: [
// ~~~ SwiftPM Plugins ~~~
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
],
targets: [ targets: [
.target( .target(
name: "CoreMetrics" name: "CoreMetrics"

View File

@ -48,7 +48,7 @@ public final class Counter {
/// Increment the counter. /// Increment the counter.
/// ///
/// - parameters: /// - parameters:
/// - by: Amount to increment by. /// - amount: Amount to increment by.
@inlinable @inlinable
public func increment<DataType: BinaryInteger>(by amount: DataType) { public func increment<DataType: BinaryInteger>(by amount: DataType) {
self._handler.increment(by: Int64(amount)) self._handler.increment(by: Int64(amount))
@ -88,7 +88,7 @@ extension Counter {
extension Counter: CustomStringConvertible { extension Counter: CustomStringConvertible {
public var description: String { public var description: String {
return "Counter(\(self.label), dimensions: \(self.dimensions))" "Counter(\(self.label), dimensions: \(self.dimensions))"
} }
} }
@ -127,7 +127,7 @@ public final class FloatingPointCounter {
/// Increment the FloatingPointCounter. /// Increment the FloatingPointCounter.
/// ///
/// - parameters: /// - parameters:
/// - by: Amount to increment by. /// - amount: Amount to increment by.
@inlinable @inlinable
public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) { public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
self._handler.increment(by: Double(amount)) self._handler.increment(by: Double(amount))
@ -167,7 +167,7 @@ extension FloatingPointCounter {
extension FloatingPointCounter: CustomStringConvertible { extension FloatingPointCounter: CustomStringConvertible {
public var description: String { public var description: String {
return "FloatingPointCounter(\(self.label), dimensions: \(self.dimensions))" "FloatingPointCounter(\(self.label), dimensions: \(self.dimensions))"
} }
} }
@ -235,7 +235,7 @@ public final class Meter {
/// Increment the Meter. /// Increment the Meter.
/// ///
/// - parameters: /// - parameters:
/// - by: Amount to increment by. /// - amount: Amount to increment by.
@inlinable @inlinable
public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) { public func increment<DataType: BinaryFloatingPoint>(by amount: DataType) {
self._handler.increment(by: Double(amount)) self._handler.increment(by: Double(amount))
@ -250,7 +250,7 @@ public final class Meter {
/// Decrement the Meter. /// Decrement the Meter.
/// ///
/// - parameters: /// - parameters:
/// - by: Amount to decrement by. /// - amount: Amount to decrement by.
@inlinable @inlinable
public func decrement<DataType: BinaryFloatingPoint>(by amount: DataType) { public func decrement<DataType: BinaryFloatingPoint>(by amount: DataType) {
self._handler.decrement(by: Double(amount)) self._handler.decrement(by: Double(amount))
@ -284,7 +284,7 @@ extension Meter {
extension Meter: CustomStringConvertible { extension Meter: CustomStringConvertible {
public var description: String { public var description: String {
return "\(type(of: self))(\(self.label), dimensions: \(self.dimensions))" "\(type(of: self))(\(self.label), dimensions: \(self.dimensions))"
} }
} }
@ -368,7 +368,7 @@ extension Recorder {
extension Recorder: CustomStringConvertible { extension Recorder: CustomStringConvertible {
public var description: String { 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))"
} }
} }
@ -397,9 +397,18 @@ public struct TimeUnit: Equatable, Sendable {
public static let nanoseconds = TimeUnit(code: .nanoseconds, scaleFromNanoseconds: 1) public static let nanoseconds = TimeUnit(code: .nanoseconds, scaleFromNanoseconds: 1)
public static let microseconds = TimeUnit(code: .microseconds, scaleFromNanoseconds: 1000) public static let microseconds = TimeUnit(code: .microseconds, scaleFromNanoseconds: 1000)
public static let milliseconds = TimeUnit(code: .milliseconds, scaleFromNanoseconds: 1000 * TimeUnit.microseconds.scaleFromNanoseconds) public static let milliseconds = TimeUnit(
public static let seconds = TimeUnit(code: .seconds, scaleFromNanoseconds: 1000 * TimeUnit.milliseconds.scaleFromNanoseconds) code: .milliseconds,
public static let minutes = TimeUnit(code: .minutes, scaleFromNanoseconds: 60 * TimeUnit.seconds.scaleFromNanoseconds) 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 hours = TimeUnit(code: .hours, scaleFromNanoseconds: 60 * TimeUnit.minutes.scaleFromNanoseconds)
public static let days = TimeUnit(code: .days, scaleFromNanoseconds: 24 * TimeUnit.hours.scaleFromNanoseconds) public static let days = TimeUnit(code: .days, scaleFromNanoseconds: 24 * TimeUnit.hours.scaleFromNanoseconds)
} }
@ -436,7 +445,7 @@ public final class Timer {
/// Record a duration in nanoseconds. /// Record a duration in nanoseconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordNanoseconds(_ duration: Int64) { public func recordNanoseconds(_ duration: Int64) {
self._handler.recordNanoseconds(duration) self._handler.recordNanoseconds(duration)
@ -445,7 +454,7 @@ public final class Timer {
/// Record a duration in nanoseconds. /// Record a duration in nanoseconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordNanoseconds<DataType: BinaryInteger>(_ duration: DataType) { public func recordNanoseconds<DataType: BinaryInteger>(_ duration: DataType) {
self.recordNanoseconds(duration >= Int64.max ? Int64.max : Int64(duration)) self.recordNanoseconds(duration >= Int64.max ? Int64.max : Int64(duration))
@ -454,7 +463,7 @@ public final class Timer {
/// Record a duration in microseconds. /// Record a duration in microseconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) { public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) } guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
@ -470,7 +479,7 @@ public final class Timer {
/// Record a duration in microseconds. /// Record a duration in microseconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordMicroseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) { public func recordMicroseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
self.recordNanoseconds(Double(duration * 1000) < Double(Int64.max) ? Int64(duration * 1000) : Int64.max) self.recordNanoseconds(Double(duration * 1000) < Double(Int64.max) ? Int64(duration * 1000) : Int64.max)
@ -479,7 +488,7 @@ public final class Timer {
/// Record a duration in milliseconds. /// Record a duration in milliseconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) { public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) } guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
@ -495,16 +504,18 @@ public final class Timer {
/// Record a duration in milliseconds. /// Record a duration in milliseconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordMilliseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) { 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. /// Record a duration in seconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) { public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) } guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
@ -520,10 +531,12 @@ public final class Timer {
/// Record a duration in seconds. /// Record a duration in seconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
@inlinable @inlinable
public func recordSeconds<DataType: BinaryFloatingPoint>(_ duration: DataType) { 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
)
} }
} }
@ -544,7 +557,11 @@ extension Timer {
/// - label: The label for the `Timer`. /// - label: The label for the `Timer`.
/// - dimensions: The dimensions 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. /// - 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) { public convenience init(
label: String,
dimensions: [(String, String)] = [],
preferredDisplayUnit displayUnit: TimeUnit
) {
let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions) let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions)
handler.preferDisplayUnit(displayUnit) handler.preferDisplayUnit(displayUnit)
self.init(label: label, dimensions: dimensions, handler: handler) self.init(label: label, dimensions: dimensions, handler: handler)
@ -560,7 +577,7 @@ extension Timer {
extension Timer: CustomStringConvertible { extension Timer: CustomStringConvertible {
public var description: String { public var description: String {
return "Timer(\(self.label), dimensions: \(self.dimensions))" "Timer(\(self.label), dimensions: \(self.dimensions))"
} }
} }
@ -589,7 +606,7 @@ public enum MetricsSystem {
/// Returns a reference to the configured factory. /// Returns a reference to the configured factory.
public static var factory: MetricsFactory { public static var factory: MetricsFactory {
return self._factory.underlying self._factory.underlying
} }
/// Acquire a writer lock for the duration of the given block. /// Acquire a writer lock for the duration of the given block.
@ -597,7 +614,7 @@ public enum MetricsSystem {
/// - Parameter body: The block to execute while holding the lock. /// - Parameter body: The block to execute while holding the lock.
/// - Returns: The value returned by the block. /// - Returns: The value returned by the block.
public static func withWriterLock<T>(_ body: () throws -> T) rethrows -> T { public static func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
return try self._factory.withWriterLock(body) try self._factory.withWriterLock(body)
} }
// This can be `@unchecked Sendable` because we're manually gating access to mutable state with a lock. // This can be `@unchecked Sendable` because we're manually gating access to mutable state with a lock.
@ -612,20 +629,23 @@ public enum MetricsSystem {
func replaceFactory(_ factory: MetricsFactory, validate: Bool) { func replaceFactory(_ factory: MetricsFactory, validate: Bool) {
self.lock.withWriterLock { 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._underlying = factory
self.initialized = true self.initialized = true
} }
} }
var underlying: MetricsFactory { var underlying: MetricsFactory {
return self.lock.withReaderLock { self.lock.withReaderLock {
return self._underlying self._underlying
} }
} }
func withWriterLock<T>(_ body: () throws -> T) rethrows -> T { func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
return try self.lock.withWriterLock(body) try self.lock.withWriterLock(body)
} }
} }
} }
@ -891,7 +911,7 @@ extension MetricsFactory {
/// - label: The label for the `FloatingPointCounterHandler`. /// - label: The label for the `FloatingPointCounterHandler`.
/// - dimensions: The dimensions for the `FloatingPointCounterHandler`. /// - dimensions: The dimensions for the `FloatingPointCounterHandler`.
public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler { public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
return AccumulatingRoundingFloatingPointCounter(label: label, dimensions: dimensions) AccumulatingRoundingFloatingPointCounter(label: label, dimensions: dimensions)
} }
/// Invoked when the corresponding `FloatingPointCounter`'s `destroy()` function is invoked. /// Invoked when the corresponding `FloatingPointCounter`'s `destroy()` function is invoked.
@ -915,7 +935,7 @@ extension MetricsFactory {
/// - label: The label for the `MeterHandler`. /// - label: The label for the `MeterHandler`.
/// - dimensions: The dimensions for the `MeterHandler`. /// - dimensions: The dimensions for the `MeterHandler`.
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler { public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
return AccumulatingMeter(label: label, dimensions: dimensions) AccumulatingMeter(label: label, dimensions: dimensions)
} }
/// Invoked when the corresponding `Meter`'s `destroy()` function is invoked. /// Invoked when the corresponding `Meter`'s `destroy()` function is invoked.
@ -1051,7 +1071,7 @@ public protocol TimerHandler: AnyObject, _SwiftMetricsSendableProtocol {
/// Record a duration in nanoseconds. /// Record a duration in nanoseconds.
/// ///
/// - parameters: /// - parameters:
/// - value: Duration to record. /// - duration: Duration to record.
func recordNanoseconds(_ duration: Int64) func recordNanoseconds(_ duration: Int64)
/// Set the preferred display unit for this TimerHandler. /// Set the preferred display unit for this TimerHandler.
@ -1077,23 +1097,23 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler { 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 { 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 { 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 { 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 { 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) { public func destroyCounter(_ handler: CounterHandler) {
@ -1133,11 +1153,11 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
func increment(by amount: Int64) { func increment(by amount: Int64) {
self.counters.forEach { $0.increment(by: amount) } for counter in self.counters { counter.increment(by: amount) }
} }
func reset() { func reset() {
self.counters.forEach { $0.reset() } for counter in self.counters { counter.reset() }
} }
} }
@ -1148,11 +1168,11 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
func increment(by amount: Double) { func increment(by amount: Double) {
self.counters.forEach { $0.increment(by: amount) } for counter in self.counters { counter.increment(by: amount) }
} }
func reset() { func reset() {
self.counters.forEach { $0.reset() } for counter in self.counters { counter.reset() }
} }
} }
@ -1163,34 +1183,36 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
func set(_ value: Int64) { func set(_ value: Int64) {
self.meters.forEach { $0.set(value) } for meter in self.meters { meter.set(value) }
} }
func set(_ value: Double) { func set(_ value: Double) {
self.meters.forEach { $0.set(value) } for meter in self.meters { meter.set(value) }
} }
func increment(by amount: Double) { 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) { 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 { private final class MuxRecorder: RecorderHandler {
let recorders: [RecorderHandler] let recorders: [RecorderHandler]
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)], aggregate: Bool) { 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) { func record(_ value: Int64) {
self.recorders.forEach { $0.record(value) } for recorder in self.recorders { recorder.record(value) }
} }
func record(_ value: Double) { func record(_ value: Double) {
self.recorders.forEach { $0.record(value) } for recorder in self.recorders { recorder.record(value) }
} }
} }
@ -1201,39 +1223,41 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
func recordNanoseconds(_ duration: Int64) { func recordNanoseconds(_ duration: Int64) {
self.timers.forEach { $0.recordNanoseconds(duration) } for timer in self.timers { timer.recordNanoseconds(duration) }
} }
func preferDisplayUnit(_ unit: TimeUnit) { 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. /// 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() public static let instance = NOOPMetricsHandler()
private init() {} private init() {}
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler { public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
return self self
} }
public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler { public func makeFloatingPointCounter(label: String, dimensions: [(String, String)]) -> FloatingPointCounterHandler {
return self self
} }
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler { public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
return self self
} }
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler { public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
return self self
} }
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler { public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
return self self
} }
public func destroyCounter(_: CounterHandler) {} public func destroyCounter(_: CounterHandler) {}

View File

@ -13,9 +13,10 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@_exported import CoreMetrics @_exported import CoreMetrics
@_exported import class CoreMetrics.Timer
import Foundation import Foundation
@_exported import class CoreMetrics.Timer
extension Timer { extension Timer {
/// Convenience for measuring duration of a closure. /// Convenience for measuring duration of a closure.
/// ///
@ -24,7 +25,11 @@ extension Timer {
/// - dimensions: The dimensions for the Timer. /// - dimensions: The dimensions for the Timer.
/// - body: Closure to run & record. /// - body: Closure to run & record.
@inlinable @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 timer = Timer(label: label, dimensions: dimensions)
let start = DispatchTime.now().uptimeNanoseconds let start = DispatchTime.now().uptimeNanoseconds
defer { defer {
@ -88,7 +93,7 @@ extension Timer {
} }
extension Timer { 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. /// `Duration` will be converted to an `Int64` number of nanoseconds, and then recorded with nanosecond precision.
/// ///

View File

@ -69,7 +69,7 @@ public final class TestMetrics: MetricsFactory {
} }
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler { 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)] { if let existing = self._counters[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
@ -80,7 +80,7 @@ public final class TestMetrics: MetricsFactory {
} }
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler { 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)] { if let existing = self._meters[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
@ -91,7 +91,7 @@ public final class TestMetrics: MetricsFactory {
} }
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler { 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)] { if let existing = self._recorders[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
@ -102,7 +102,7 @@ public final class TestMetrics: MetricsFactory {
} }
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler { 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)] { if let existing = self._timers[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
@ -148,15 +148,15 @@ public final class TestMetrics: MetricsFactory {
extension TestMetrics.FullKey: Hashable { extension TestMetrics.FullKey: Hashable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
self.label.hash(into: &hasher) self.label.hash(into: &hasher)
self.dimensions.forEach { dim in for dim in self.dimensions {
dim.0.hash(into: &hasher) dim.0.hash(into: &hasher)
dim.1.hash(into: &hasher) dim.1.hash(into: &hasher)
} }
} }
public static func == (lhs: TestMetrics.FullKey, rhs: TestMetrics.FullKey) -> Bool { public static func == (lhs: TestMetrics.FullKey, rhs: TestMetrics.FullKey) -> Bool {
return lhs.label == rhs.label && lhs.label == rhs.label
Dictionary(uniqueKeysWithValues: lhs.dimensions) == Dictionary(uniqueKeysWithValues: rhs.dimensions) && Dictionary(uniqueKeysWithValues: lhs.dimensions) == Dictionary(uniqueKeysWithValues: rhs.dimensions)
} }
} }
@ -193,11 +193,11 @@ extension TestMetrics {
// MARK: - Gauge // MARK: - Gauge
public func expectGauge(_ metric: Gauge) throws -> TestRecorder { 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 { public func expectGauge(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestRecorder {
return try self.expectRecorder(label, dimensions) try self.expectRecorder(label, dimensions)
} }
// MARK: - Meter // MARK: - Meter
@ -299,7 +299,7 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable {
public let dimensions: [(String, String)] public let dimensions: [(String, String)]
public var key: TestMetrics.FullKey { 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() let lock = NSLock()
@ -318,33 +318,33 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable {
} }
public func reset() { public func reset() {
return self.lock.withLock { self.lock.withLock {
self._values = [] self._values = []
} }
} }
public var lastValue: Int64? { public var lastValue: Int64? {
return self.last?.1 self.last?.1
} }
public var totalValue: Int64 { public var totalValue: Int64 {
return self.values.reduce(0, +) self.values.reduce(0, +)
} }
public var last: (Date, Int64)? { public var last: (Date, Int64)? {
return self.lock.withLock { self.lock.withLock {
self._values.last self._values.last
} }
} }
public var values: [Int64] { public var values: [Int64] {
return self.lock.withLock { self.lock.withLock {
self._values.map { $0.1 } self._values.map { $0.1 }
} }
} }
public static func == (lhs: TestCounter, rhs: TestCounter) -> Bool { 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 let dimensions: [(String, String)]
public var key: TestMetrics.FullKey { 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() let lock = NSLock()
@ -430,23 +430,23 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable {
} }
public var lastValue: Double? { public var lastValue: Double? {
return self.last?.1 self.last?.1
} }
public var last: (Date, Double)? { public var last: (Date, Double)? {
return self.lock.withLock { self.lock.withLock {
self._values.last self._values.last
} }
} }
public var values: [Double] { public var values: [Double] {
return self.lock.withLock { self.lock.withLock {
self._values.map { $0.1 } self._values.map { $0.1 }
} }
} }
public static func == (lhs: TestMeter, rhs: TestMeter) -> Bool { 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 let aggregate: Bool
public var key: TestMetrics.FullKey { 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() let lock = NSLock()
@ -482,23 +482,23 @@ public final class TestRecorder: TestMetric, RecorderHandler, Equatable {
} }
public var lastValue: Double? { public var lastValue: Double? {
return self.last?.1 self.last?.1
} }
public var last: (Date, Double)? { public var last: (Date, Double)? {
return self.lock.withLock { self.lock.withLock {
self._values.last self._values.last
} }
} }
public var values: [Double] { public var values: [Double] {
return self.lock.withLock { self.lock.withLock {
self._values.map { $0.1 } self._values.map { $0.1 }
} }
} }
public static func == (lhs: TestRecorder, rhs: TestRecorder) -> Bool { 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 let dimensions: [(String, String)]
public var key: TestMetrics.FullKey { 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() let lock = NSLock()
@ -543,23 +543,23 @@ public final class TestTimer: TestMetric, TimerHandler, Equatable {
} }
public var lastValue: Int64? { public var lastValue: Int64? {
return self.last?.1 self.last?.1
} }
public var values: [Int64] { public var values: [Int64] {
return self.lock.withLock { self.lock.withLock {
return self._values.map { $0.1 } self._values.map { $0.1 }
} }
} }
public var last: (Date, Int64)? { public var last: (Date, Int64)? {
return self.lock.withLock { self.lock.withLock {
return self._values.last self._values.last
} }
} }
public static func == (lhs: TestTimer, rhs: TestTimer) -> Bool { public static func == (lhs: TestTimer, rhs: TestTimer) -> Bool {
return lhs.id == rhs.id lhs.id == rhs.id
} }
} }

View File

@ -12,10 +12,11 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@testable import CoreMetrics
import MetricsTestKit import MetricsTestKit
import XCTest import XCTest
@testable import CoreMetrics
class MetricsTests: XCTestCase { class MetricsTests: XCTestCase {
func testCounters() throws { func testCounters() throws {
// bootstrap with our test metrics // bootstrap with our test metrics
@ -25,12 +26,12 @@ class MetricsTests: XCTestCase {
let name = "counter-\(UUID().uuidString)" let name = "counter-\(UUID().uuidString)"
let counter = Counter(label: name) let counter = Counter(label: name)
let testCounter = try metrics.expectCounter(counter) let testCounter = try metrics.expectCounter(counter)
let total = Int.random(in: 500 ... 1000) let total = Int.random(in: 500...1000)
for _ in 0 ..< total { for _ in 0..<total {
group.enter() group.enter()
DispatchQueue(label: "\(name)-queue").async { DispatchQueue(label: "\(name)-queue").async {
defer { group.leave() } defer { group.leave() }
counter.increment(by: Int.random(in: 0 ... 1000)) counter.increment(by: Int.random(in: 0...1000))
} }
} }
group.wait() group.wait()
@ -45,7 +46,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "counter-\(UUID().uuidString)" 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) Counter(label: name).increment(by: value)
let counter = try metrics.expectCounter(name) let counter = try metrics.expectCounter(name)
XCTAssertEqual(counter.values.count, 1, "expected number of entries to match") XCTAssertEqual(counter.values.count, 1, "expected number of entries to match")
@ -154,12 +155,12 @@ class MetricsTests: XCTestCase {
let name = "recorder-\(UUID().uuidString)" let name = "recorder-\(UUID().uuidString)"
let recorder = Recorder(label: name) let recorder = Recorder(label: name)
let testRecorder = try metrics.expectRecorder(recorder) let testRecorder = try metrics.expectRecorder(recorder)
let total = Int.random(in: 500 ... 1000) let total = Int.random(in: 500...1000)
for _ in 0 ..< total { for _ in 0..<total {
group.enter() group.enter()
DispatchQueue(label: "\(name)-queue").async { DispatchQueue(label: "\(name)-queue").async {
defer { group.leave() } defer { group.leave() }
recorder.record(Int.random(in: Int.min ... Int.max)) recorder.record(Int.random(in: Int.min...Int.max))
} }
} }
group.wait() group.wait()
@ -172,12 +173,12 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let recorder = Recorder(label: "test-recorder") let recorder = Recorder(label: "test-recorder")
let testRecorder = try metrics.expectRecorder(recorder) let testRecorder = try metrics.expectRecorder(recorder)
let values = (0 ... 999).map { _ in Int32.random(in: Int32.min ... Int32.max) } let values = (0...999).map { _ in Int32.random(in: Int32.min...Int32.max) }
for i in 0 ..< values.count { for i in 0..<values.count {
recorder.record(values[i]) recorder.record(values[i])
} }
XCTAssertEqual(values.count, testRecorder.values.count, "expected number of entries to match") 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.") XCTAssertEqual(Int32(testRecorder.values[i]), values[i], "expected value #\(i) to match.")
} }
} }
@ -188,12 +189,12 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let recorder = Recorder(label: "test-recorder") let recorder = Recorder(label: "test-recorder")
let testRecorder = try metrics.expectRecorder(recorder) let testRecorder = try metrics.expectRecorder(recorder)
let values = (0 ... 999).map { _ in Float.random(in: Float(Int32.min) ... Float(Int32.max)) } let values = (0...999).map { _ in Float.random(in: Float(Int32.min)...Float(Int32.max)) }
for i in 0 ..< values.count { for i in 0..<values.count {
recorder.record(values[i]) recorder.record(values[i])
} }
XCTAssertEqual(values.count, testRecorder.values.count, "expected number of entries to match") 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.") XCTAssertEqual(Float(testRecorder.values[i]), values[i], "expected value #\(i) to match.")
} }
} }
@ -204,7 +205,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "recorder-\(UUID().uuidString)" 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) Recorder(label: name).record(value)
let recorder = try metrics.expectRecorder(name) let recorder = try metrics.expectRecorder(name)
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
@ -219,12 +220,12 @@ class MetricsTests: XCTestCase {
let name = "timer-\(UUID().uuidString)" let name = "timer-\(UUID().uuidString)"
let timer = Timer(label: name) let timer = Timer(label: name)
let testTimer = try metrics.expectTimer(timer) let testTimer = try metrics.expectTimer(timer)
let total = Int.random(in: 500 ... 1000) let total = Int.random(in: 500...1000)
for _ in 0 ..< total { for _ in 0..<total {
group.enter() group.enter()
DispatchQueue(label: "\(name)-queue").async { DispatchQueue(label: "\(name)-queue").async {
defer { group.leave() } defer { group.leave() }
timer.recordNanoseconds(Int64.random(in: Int64.min ... Int64.max)) timer.recordNanoseconds(Int64.random(in: Int64.min...Int64.max))
} }
} }
group.wait() group.wait()
@ -237,7 +238,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "timer-\(UUID().uuidString)" 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) Timer(label: name).recordNanoseconds(value)
let timer = try metrics.expectTimer(name) let timer = try metrics.expectTimer(name)
XCTAssertEqual(timer.values.count, 1, "expected number of entries to match") 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 timer = Timer(label: "test-timer")
let testTimer = try metrics.expectTimer(timer) let testTimer = try metrics.expectTimer(timer)
// nano // nano
let nano = Int64.random(in: 0 ... 5) let nano = Int64.random(in: 0...5)
timer.recordNanoseconds(nano) timer.recordNanoseconds(nano)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimer.values[0], nano, "expected value to match") XCTAssertEqual(testTimer.values[0], nano, "expected value to match")
// micro // micro
let micro = Int64.random(in: 0 ... 5) let micro = Int64.random(in: 0...5)
timer.recordMicroseconds(micro) timer.recordMicroseconds(micro)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(testTimer.values[1], micro * 1000, "expected value to match") XCTAssertEqual(testTimer.values[1], micro * 1000, "expected value to match")
// milli // milli
let milli = Int64.random(in: 0 ... 5) let milli = Int64.random(in: 0...5)
timer.recordMilliseconds(milli) timer.recordMilliseconds(milli)
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
XCTAssertEqual(testTimer.values[2], milli * 1_000_000, "expected value to match") XCTAssertEqual(testTimer.values[2], milli * 1_000_000, "expected value to match")
// seconds // seconds
let sec = Int64.random(in: 0 ... 5) let sec = Int64.random(in: 0...5)
timer.recordSeconds(sec) timer.recordSeconds(sec)
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
XCTAssertEqual(testTimer.values[3], sec * 1_000_000_000, "expected value 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) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "gauge-\(UUID().uuidString)" let name = "gauge-\(UUID().uuidString)"
let value = Double.random(in: -1000 ... 1000) let value = Double.random(in: -1000...1000)
let gauge = Gauge(label: name) let gauge = Gauge(label: name)
gauge.record(value) gauge.record(value)
let recorder = try metrics.expectRecorder(gauge) let recorder = try metrics.expectRecorder(gauge)
@ -355,7 +356,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "gauge-\(UUID().uuidString)" let name = "gauge-\(UUID().uuidString)"
let value = Double.random(in: -1000 ... 1000) let value = Double.random(in: -1000...1000)
Gauge(label: name).record(value) Gauge(label: name).record(value)
let recorder = try metrics.expectRecorder(name) let recorder = try metrics.expectRecorder(name)
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
@ -368,7 +369,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "meter-\(UUID().uuidString)" 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 meter = Meter(label: name)
meter.set(value) meter.set(value)
let testMeter = try metrics.expectMeter(meter) let testMeter = try metrics.expectMeter(meter)
@ -382,7 +383,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "meter-\(UUID().uuidString)" let name = "meter-\(UUID().uuidString)"
let value = Double.random(in: -1000 ... 1000) let value = Double.random(in: -1000...1000)
Meter(label: name).set(value) Meter(label: name).set(value)
let testMeter = try metrics.expectMeter(name) let testMeter = try metrics.expectMeter(name)
XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match") XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match")
@ -395,12 +396,12 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let meter = Meter(label: name) let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter) let testMeter = try metrics.expectMeter(meter)
let values = (0 ... 999).map { _ in Int32.random(in: Int32.min ... Int32.max) } let values = (0...999).map { _ in Int32.random(in: Int32.min...Int32.max) }
for i in 0 ..< values.count { for i in 0..<values.count {
meter.set(values[i]) meter.set(values[i])
} }
XCTAssertEqual(values.count, testMeter.values.count, "expected number of entries to match") 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.") XCTAssertEqual(Int32(testMeter.values[i]), values[i], "expected value #\(i) to match.")
} }
} }
@ -411,12 +412,12 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let meter = Meter(label: name) let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter) let testMeter = try metrics.expectMeter(meter)
let values = (0 ... 999).map { _ in Float.random(in: Float(Int32.min) ... Float(Int32.max)) } let values = (0...999).map { _ in Float.random(in: Float(Int32.min)...Float(Int32.max)) }
for i in 0 ..< values.count { for i in 0..<values.count {
meter.set(values[i]) meter.set(values[i])
} }
XCTAssertEqual(values.count, testMeter.values.count, "expected number of entries to match") 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.") 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 name = "meter-\(UUID().uuidString)"
let meter = Meter(label: name) let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter) let testMeter = try metrics.expectMeter(meter)
let values = (500 ... 1000).map { _ in Double.random(in: 0 ... Double(Int32.max)) } let values = (500...1000).map { _ in Double.random(in: 0...Double(Int32.max)) }
for i in 0 ..< values.count { for i in 0..<values.count {
group.enter() group.enter()
DispatchQueue(label: "\(name)-queue").async { DispatchQueue(label: "\(name)-queue").async {
defer { group.leave() } defer { group.leave() }
@ -453,8 +454,8 @@ class MetricsTests: XCTestCase {
let meter = Meter(label: name) let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter) let testMeter = try metrics.expectMeter(meter)
let values = (500 ... 1000).map { _ in Double.random(in: 0 ... Double(Int32.max)) } let values = (500...1000).map { _ in Double.random(in: 0...Double(Int32.max)) }
for i in 0 ..< values.count { for i in 0..<values.count {
group.enter() group.enter()
DispatchQueue(label: "\(name)-queue").async { DispatchQueue(label: "\(name)-queue").async {
defer { group.leave() } defer { group.leave() }
@ -523,17 +524,17 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories)) MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
// run the test // run the test
let name = UUID().uuidString 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) let muxCounter = Counter(label: name)
muxCounter.increment(by: value) muxCounter.increment(by: value)
factories.forEach { factory in for factory in factories {
let counter = factory.counters.first let counter = factory.counters.first
XCTAssertEqual(counter?.label, name, "expected label to match") XCTAssertEqual(counter?.label, name, "expected label to match")
XCTAssertEqual(counter?.values.count, 1, "expected number of entries to match") XCTAssertEqual(counter?.values.count, 1, "expected number of entries to match")
XCTAssertEqual(counter?.lastValue, Int64(value), "expected value to match") XCTAssertEqual(counter?.lastValue, Int64(value), "expected value to match")
} }
muxCounter.reset() muxCounter.reset()
factories.forEach { factory in for factory in factories {
let counter = factory.counters.first let counter = factory.counters.first
XCTAssertEqual(counter?.values.count, 0, "expected number of entries to match") XCTAssertEqual(counter?.values.count, 0, "expected number of entries to match")
} }
@ -545,10 +546,10 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories)) MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
// run the test // run the test
let name = UUID().uuidString let name = UUID().uuidString
let value = Double.random(in: 0 ... 1) let value = Double.random(in: 0...1)
let muxMeter = Meter(label: name) let muxMeter = Meter(label: name)
muxMeter.set(value) muxMeter.set(value)
factories.forEach { factory in for factory in factories {
let meter = factory.meters.first let meter = factory.meters.first
XCTAssertEqual(meter?.label, name, "expected label to match") XCTAssertEqual(meter?.label, name, "expected label to match")
XCTAssertEqual(meter?.values.count, 1, "expected number of entries 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)) MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
// run the test // run the test
let name = UUID().uuidString let name = UUID().uuidString
let value = Double.random(in: 0 ... 1) let value = Double.random(in: 0...1)
let muxRecorder = Recorder(label: name) let muxRecorder = Recorder(label: name)
muxRecorder.record(value) muxRecorder.record(value)
factories.forEach { factory in for factory in factories {
let recorder = factory.recorders.first let recorder = factory.recorders.first
XCTAssertEqual(recorder?.label, name, "expected label to match") XCTAssertEqual(recorder?.label, name, "expected label to match")
XCTAssertEqual(recorder?.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder?.values.count, 1, "expected number of entries to match")
@ -579,16 +580,20 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories)) MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
// run the test // run the test
let name = UUID().uuidString 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) let muxTimer = Timer(label: name, preferredDisplayUnit: .minutes)
muxTimer.recordSeconds(seconds) muxTimer.recordSeconds(seconds)
factories.forEach { factory in for factory in factories {
let timer = factory.timers.first let timer = factory.timers.first
XCTAssertEqual(timer?.label, name, "expected label to match") XCTAssertEqual(timer?.label, name, "expected label to match")
XCTAssertEqual(timer?.values.count, 1, "expected number of entries 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?.values[0], Int64(seconds * 1_000_000_000), "expected value to match")
XCTAssertEqual(timer?.displayUnit, .minutes, "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"
)
} }
} }
@ -609,7 +614,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let name = "gauge-\(UUID().uuidString)" let name = "gauge-\(UUID().uuidString)"
let value = Double.random(in: -1000 ... 1000) let value = Double.random(in: -1000...1000)
let gauge = Gauge(label: name) let gauge = Gauge(label: name)
gauge.record(value) gauge.record(value)
@ -631,7 +636,11 @@ class MetricsTests: XCTestCase {
XCTAssertEqual(recorderAgain.values.first, -value, "expected value to match") XCTAssertEqual(recorderAgain.values.first, -value, "expected value to match")
let identityAgain = ObjectIdentifier(recorderAgain) 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 { func testDestroyingMeter() throws {
@ -639,7 +648,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let name = "meter-\(UUID().uuidString)" 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 meter = Meter(label: name)
meter.set(value) meter.set(value)
@ -661,7 +670,11 @@ class MetricsTests: XCTestCase {
XCTAssertEqual(testMeterAgain.values.first, -value, "expected value to match") XCTAssertEqual(testMeterAgain.values.first, -value, "expected value to match")
let identityAgain = ObjectIdentifier(testMeterAgain) 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 { func testDestroyingCounter() throws {
@ -669,7 +682,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let name = "counter-\(UUID().uuidString)" 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 counter = Counter(label: name)
counter.increment(by: value) counter.increment(by: value)
@ -691,7 +704,11 @@ class MetricsTests: XCTestCase {
XCTAssertEqual(testCounterAgain.values.first, Int64(value), "expected value to match") XCTAssertEqual(testCounterAgain.values.first, Int64(value), "expected value to match")
let identityAgain = ObjectIdentifier(counterAgain) 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 { func testDestroyingTimer() throws {
@ -699,7 +716,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let name = "timer-\(UUID().uuidString)" 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 timer = Timer(label: name)
timer.recordNanoseconds(value) timer.recordNanoseconds(value)
@ -720,7 +737,11 @@ class MetricsTests: XCTestCase {
XCTAssertEqual(testTimerAgain.values.first, value, "expected value to match") XCTAssertEqual(testTimerAgain.values.first, value, "expected value to match")
let identityAgain = ObjectIdentifier(timerAgain) 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 { func testDescriptions() throws {

View File

@ -12,11 +12,12 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@testable import CoreMetrics
@testable import Metrics
import MetricsTestKit import MetricsTestKit
import XCTest import XCTest
@testable import CoreMetrics
@testable import Metrics
class MetricsExtensionsTests: XCTestCase { class MetricsExtensionsTests: XCTestCase {
func testTimerBlock() throws { func testTimerBlock() throws {
// bootstrap with our test metrics // bootstrap with our test metrics
@ -40,7 +41,7 @@ class MetricsExtensionsTests: XCTestCase {
// run the test // run the test
let timer = Timer(label: "test-timer") let timer = Timer(label: "test-timer")
let testTimer = try metrics.expectTimer(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) timer.record(timeInterval)
XCTAssertEqual(1, testTimer.values.count, "expected number of entries to match") 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") 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 timer = Timer(label: "test-timer")
let testTimer = try metrics.expectTimer(timer) let testTimer = try metrics.expectTimer(timer)
// nano // nano
let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1 ... 500)) let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1...500))
timer.record(nano) timer.record(nano)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[0]), nano.nano(), "expected value to match") XCTAssertEqual(Int(testTimer.values[0]), nano.nano(), "expected value to match")
// micro // micro
let micro = DispatchTimeInterval.microseconds(Int.random(in: 1 ... 500)) let micro = DispatchTimeInterval.microseconds(Int.random(in: 1...500))
timer.record(micro) timer.record(micro)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[1]), micro.nano(), "expected value to match") XCTAssertEqual(Int(testTimer.values[1]), micro.nano(), "expected value to match")
// milli // milli
let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1 ... 500)) let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1...500))
timer.record(milli) timer.record(milli)
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[2]), milli.nano(), "expected value to match") XCTAssertEqual(Int(testTimer.values[2]), milli.nano(), "expected value to match")
// seconds // seconds
let sec = DispatchTimeInterval.seconds(Int.random(in: 1 ... 500)) let sec = DispatchTimeInterval.seconds(Int.random(in: 1...500))
timer.record(sec) timer.record(sec)
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[3]), sec.nano(), "expected value 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) let testTimer = try metrics.expectTimer(timer)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") 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") XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
} }
@ -129,7 +134,7 @@ class MetricsExtensionsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let name = "timer-\(UUID().uuidString)" 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 timer = Timer(label: name)
timer.recordNanoseconds(value) timer.recordNanoseconds(value)
@ -140,7 +145,7 @@ class MetricsExtensionsTests: XCTestCase {
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
let secondsName = "timer-seconds-\(UUID().uuidString)" 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) let secondsTimer = Timer(label: secondsName, preferredDisplayUnit: .seconds)
secondsTimer.recordSeconds(secondsValue) secondsTimer.recordSeconds(secondsValue)
@ -153,32 +158,67 @@ class MetricsExtensionsTests: XCTestCase {
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let value = Double.random(in: 0 ... 1000) let value = Double.random(in: 0...1000)
let timer = Timer(label: "test", preferredDisplayUnit: .seconds) let timer = Timer(label: "test", preferredDisplayUnit: .seconds)
timer.recordSeconds(value) timer.recordSeconds(value)
let testTimer = try metrics.expectTimer(timer) let testTimer = try metrics.expectTimer(timer)
testTimer.preferDisplayUnit(.nanoseconds) 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) 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) 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) 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) 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) 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) 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"
)
} }
} }

View File

@ -12,11 +12,12 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@testable import CoreMetrics
import Dispatch import Dispatch
import MetricsTestKit import MetricsTestKit
import XCTest import XCTest
@testable import CoreMetrics
class SendableTest: XCTestCase { class SendableTest: XCTestCase {
func testSendableMetrics() async throws { func testSendableMetrics() async throws {
// bootstrap with our test metrics // bootstrap with our test metrics
@ -25,7 +26,7 @@ class SendableTest: XCTestCase {
do { do {
let name = "counter-\(UUID().uuidString)" 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 counter = Counter(label: name)
let task = Task.detached { () -> [Int64] in let task = Task.detached { () -> [Int64] in
@ -40,7 +41,7 @@ class SendableTest: XCTestCase {
do { do {
let name = "floating-point-counter-\(UUID().uuidString)" 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 counter = FloatingPointCounter(label: name)
let task = Task.detached { () -> Double in let task = Task.detached { () -> Double in
@ -54,7 +55,7 @@ class SendableTest: XCTestCase {
do { do {
let name = "recorder-\(UUID().uuidString)" 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 recorder = Recorder(label: name)
let task = Task.detached { () -> [Double] in let task = Task.detached { () -> [Double] in
@ -69,7 +70,7 @@ class SendableTest: XCTestCase {
do { do {
let name = "meter-\(UUID().uuidString)" 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 meter = Meter(label: name)
let task = Task.detached { () -> [Double] in let task = Task.detached { () -> [Double] in
@ -84,7 +85,7 @@ class SendableTest: XCTestCase {
do { do {
let name = "timer-\(UUID().uuidString)" 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 timer = Timer(label: name)
let task = Task.detached { () -> [Int64] in let task = Task.detached { () -> [Int64] in

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 $${SANITIZER_ARG-}"
# util
shell:
<<: *common
entrypoint: /bin/bash -l

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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"