Compare commits

...

14 Commits

Author SHA1 Message Date
Franz Busch e9519032b7
Merge 199b458a9f into cbd39ceaca 2025-03-08 12:46:20 +01:00
Franz Busch 199b458a9f Add `Timer.measure` methods
# Motivation

This PR supersedes https://github.com/apple/swift-metrics/pull/135. The goal is to make it easier to measure asynchronous code when using `Metrics`.

# Modification

This PR does:
- Deprecate the current static method for measuring synchronous code
- Add a new instance method to measure synchronous code
- Add a new instance method to measure asynchronous code

# Result

It is now easier to measure asynchronous code.
2025-03-08 12:46:14 +01:00
Rick Newton-Rogers cbd39ceaca
Only apply standard swift settings on valid targets (#163)
Only apply standard swift settings on valid targets. The current check
ignores plugins but that is not comprehensive enough.
2025-03-07 19:34:49 +01:00
Rick Newton-Rogers 029e902273
Rename nightly_6_1 params to nightly_next (#162)
Rename nightly_6_1 params to nightly_next; see
https://github.com/apple/swift-nio/pull/3122
2025-03-03 14:46:47 +00:00
Cory Benfield 58f390a873
Tell the truth about the supported metric types (#161)
Our README claims we support 4 metric types, and then lists five. But it
fails to list FloatingPointCounter, which we also support, so the real
number is six.

While I was here I also fixed the DocC topic, which omitted
FloatingPointCounter from the list of metrics but _also_ omitted Gauge
for some weird reason.
2025-01-31 14:31:14 +00:00
Rick Newton-Rogers 53de3bfa9a
CI use 6.1 nightlies (#160)
CI use 6.1 nightlies now that Swift development is happening in the 6.1
branch
2025-01-30 09:59:23 +01:00
Marc Prud'hommeaux 5e63558d12
Android support (#159)
This PR makes the single `import Android` addition that is needed to get
this package building for Android. `skip android test` passes against
the emulator after I made this one change.
2025-01-09 22:49:28 +01:00
Franz Busch 726392cf4e
Update release.yml (#157)
Update the release.yml file with the latest label changes
2024-12-18 14:19:24 +01:00
Rick Newton-Rogers 4a491b5ad5
Enable MemberImportVisibility check on all targets (#156)
Enable MemberImportVisibility check on all targets. Use a standard
string header and footer to bracket the new block for ease of updating
in the future with scripts.
2024-12-16 10:59:35 +00:00
Honza Dvorsky 58e2968a7f
Enable strict concurrency (#155) 2024-11-29 23:28:08 +01:00
Franz Busch 4ec5a219dc
Aligning semantic version label check name (#154) 2024-11-28 12:01:57 +00:00
George Barnett c1976209b5
remove contributors script (#152)
remove contributors script
2024-11-15 09:51:00 +00:00
Rick Newton-Rogers cffefdc627 remove contributors script 2024-11-14 14:11:23 +00:00
Rick Newton-Rogers d720898dbf
Migrate to GitHub Actions (#151) 2024-11-06 22:32:20 +09:00
30 changed files with 588 additions and 740 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: "--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_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"

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: "--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_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

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

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.8
// 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 --- //

View File

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

View File

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

View File

@ -34,6 +34,8 @@ import Darwin
import WinSDK
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Android)
import Android
#elseif canImport(Musl)
import Musl
#else

View File

@ -48,7 +48,7 @@ public final class Counter {
/// 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))
@ -88,7 +88,7 @@ extension Counter {
extension Counter: CustomStringConvertible {
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.
///
/// - 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))
@ -167,7 +167,7 @@ extension FloatingPointCounter {
extension FloatingPointCounter: CustomStringConvertible {
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.
///
/// - 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 +250,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))
@ -284,7 +284,7 @@ extension Meter {
extension Meter: CustomStringConvertible {
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 {
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 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)
}
@ -436,7 +445,7 @@ public final class Timer {
/// 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 +454,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 +463,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 +479,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 +488,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 +504,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,10 +531,12 @@ 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
)
}
}
@ -544,7 +557,11 @@ 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) {
public convenience init(
label: String,
dimensions: [(String, String)] = [],
preferredDisplayUnit displayUnit: TimeUnit
) {
let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions)
handler.preferDisplayUnit(displayUnit)
self.init(label: label, dimensions: dimensions, handler: handler)
@ -560,7 +577,7 @@ extension Timer {
extension Timer: CustomStringConvertible {
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.
public static var factory: MetricsFactory {
return self._factory.underlying
self._factory.underlying
}
/// 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.
/// - 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)
}
// 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) {
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)
}
}
}
@ -891,7 +911,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)
}
/// Invoked when the corresponding `FloatingPointCounter`'s `destroy()` function is invoked.
@ -915,7 +935,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)
}
/// Invoked when the corresponding `Meter`'s `destroy()` function is invoked.
@ -1051,7 +1071,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.
@ -1077,23 +1097,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) {
@ -1133,11 +1153,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() }
}
}
@ -1148,11 +1168,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() }
}
}
@ -1163,34 +1183,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) }
}
}
@ -1201,39 +1223,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) {}

View File

@ -13,9 +13,10 @@
//===----------------------------------------------------------------------===//
@_exported import CoreMetrics
@_exported import class CoreMetrics.Timer
import Foundation
@_exported import class CoreMetrics.Timer
extension Timer {
/// Convenience for measuring duration of a closure.
///
@ -23,8 +24,15 @@ extension Timer {
/// - label: The label for the Timer.
/// - dimensions: The dimensions for the Timer.
/// - body: Closure to run & record.
#if compiler(>=6.0)
@available(*, deprecated, message: "Please use non-static version on an already created Timer")
#endif
@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 {
@ -88,7 +96,7 @@ 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.
///
@ -107,4 +115,44 @@ 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,
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,
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
}

View File

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

View File

@ -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,16 +580,20 @@ 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"
)
}
}
@ -609,7 +614,7 @@ class MetricsTests: XCTestCase {
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 +636,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 +648,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 +670,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 +682,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 +704,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 +716,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 +737,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 {

View File

@ -12,12 +12,14 @@
//
//===----------------------------------------------------------------------===//
@testable import CoreMetrics
@testable import Metrics
import MetricsTestKit
import XCTest
@testable import CoreMetrics
@testable import Metrics
class MetricsExtensionsTests: XCTestCase {
@available(*, deprecated)
func testTimerBlock() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
@ -40,7 +42,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 +56,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 +94,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 +135,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 +146,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,33 +159,105 @@ 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
@ -211,3 +289,25 @@ extension DispatchTimeInterval {
}
}
}
#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

View File

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

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

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"