Compare commits

...

32 Commits
2.4.0 ... main

Author SHA1 Message Date
Honza Dvorsky 4c83e1cdf4
Remove warnings against explicit dependency injection of the metrics factory (#174)
### Motivation:

In the previous PR, we noticed that these warnings seem to discourage
explicit dependency injection, but it's not clear why we should be
discouraging it.

### Modifications:

Removed the warning.

### Result:

N/A
2025-04-29 15:27:10 +02:00
Honza Dvorsky 98d36172c1
Allow providing a custom MetricsFactory to Counter and friends (#172)
### Motivation:

To allow for things like parallel testing, it'd be useful if we can
explicitly provide a MetricsFactory to the existing Counter/... types.

### Modifications:

Added a `factory: MetricsFactory` parameter to all the initializers of
Counter/... types, and kept the existing methods that continue to
default to `MetricsSystem.factory`.

### Result:

Adopters can use a custom MetricsFactory explicitly passed in at metric
creation time. Existing adopters are not affected, unless you opt in,
you continue to use the global factory.
2025-04-29 15:07:30 +02:00
Rick Newton-Rogers 27ecca7ac1
Enable Swift 6.1 jobs in CI (#171)
Motivation:

Swift 6.1 has been released, we should add it to our CI coverage.

Modifications:

Add additional Swift 6.1 jobs where appropriate in main.yml,
pull_request.yml

Result:

Improved test coverage.
2025-04-14 12:15:45 +02:00
Rick Newton-Rogers 071d1cac3c
Enable macOS CI on pull requests (#170)
Motivation:

* Improve test coverage

Modifications:

Enable macOS CI to be run on pull request commits and make the use of
the nightly runner pool for main.yml jobs explicit.

Result:

Improved test coverage.
2025-04-01 12:20:11 +02:00
Rick Newton-Rogers 0556b16079
Enable macOS CI on merge to main and daily timer (#169)
Motivation:

* Improve test coverage
* Check test pass/fail status
* Monitor CI throughput

Modifications:

Enable macOS CI to be run on all merges to main and on a daily timer.

Result:

Improved test coverage run out-of-band at the moment so we can get a
feeling for if any changes need to be made in the repo or in the CI
pipelines to ensure timely and stable checks.
2025-03-31 13:29:44 +01:00
Franz Busch 44491db7cc
Fix 5.10 compile on Ubuntu 24.04 (#168)
Specifically Swift 5.10 on Intel on Ubuntu Noble (24.04) has a crazy bug
which leads to compilation failures in a #if compiler(>=6.0) block:
swiftlang/swift#79285 .

This workaround fixes the compilation by changing the whitespace.

Fixes https://github.com/apple/swift-metrics/issues/166
2025-03-18 16:21:52 +01:00
Franz Busch 3c0f419970
Add `Timer.measure` methods (#140)
# 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
2025-03-10 10:46:27 +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
LamTrinh.Dev 569db3a632
Correct the link of sswg-security at SECURITY.md (#150) 2024-09-17 21:43:37 +09:00
Gustavo Cairo e0165b53d4
Fix Sendability warnings (#137)
* Fix Sendability warnings

* Apply suggestions from code review

* Fix another warning and @unknown default issue

---------

Co-authored-by: Konrad `ktoso` Malawski <konrad.malawski@project13.pl>
2024-06-21 17:18:13 +01:00
Franz Busch d067b0e0f7
Add missing `Sendable` annotations (#148)
# Motivation
We were missing a few `Sendable` annotations on our public types.

# Modification
This PR adds `Sendable` to the missing types.
2024-06-21 05:40:32 +09:00
Peter Adams 79e5fb4fe4
Raise minimum swift version to 5.8 (#147)
Motivation:

It's consistant with support policy (see https://github.com/apple/swift-metrics/pull/141).
It matches what's being tested.
It will make future changes adopting new features easier.

Modifications:

Bump swift tools version to 5.8
Remove linux tests and test discovery flag which are not required.

Result:

Will not longer build using swift before 5.8 - older compilers will find older
versions of this library when resolving versions.
2024-06-12 10:53:26 +01:00
Konrad `ktoso` Malawski ce594e71e9
fix availability of record(:Duration) API (#146) 2024-05-15 18:08:10 +09:00
hamzahrmalik 0124d9ad5c
Change Timer.record(duration: Duration) signature to avoid source breakage (#144) 2024-05-15 14:36:25 +09:00
Si Beaumont eb18581491
Support compiling against Musl (#142)
Co-authored-by: Konrad `ktoso` Malawski <konrad.malawski@project13.pl>
2024-05-14 13:16:51 +09:00
Si Beaumont 33d3f71e00
Update API breakage CI script (#143)
* Update API breakage CI script

* restate sendable conformance to avoid warning/as-error

---------

Co-authored-by: Konrad `ktoso` Malawski <konrad_malawski@apple.com>
2024-05-14 13:09:54 +09:00
Franz Busch 7e53749103
Align supported Swift versions with rest of the ecosystem (#141)
# Motivation

Most of the server ecosystem follows the supported Swift versions from `swift-nio`. This means supporting the latest 3 Swift releases.

# Modification

This PR drops support for all Swift versions before 5.8.

# Result

This makes maintaining this repository easier and allows us to clean up some no longer needed stuff.
2024-05-09 11:19:06 +09:00
Yim Lee 9c0646af4d
Add CI for Swift 5.10 (#138) 2024-01-23 08:24:32 -08:00
Natik Gadzhi 13ea1fe7fc
Introducing Timer.record(_ duration:), closes #114 (#133)
Motivation:

`Duration` is available starting Swift 5.7, and we
should probably support it in our convinience API.

Modifications:
- Timer.record(_ duration: Duration) implementation
- A unit test case
- Generated Linux tests
2023-06-28 14:56:55 -07:00
tomer doron 971ba26378
add increment and decrement public API to Meter (#132)
motivation: expose increment and decrement public APIs

changes:
* add increment and decrement public API to Meter
* improve the implementation of AccumulatingMeter and TestMeter
* add tests for new APIs
* refactor/modernize other tests
2023-06-21 14:15:23 -07:00
tomer doron bf7ea93e17
update code of conduct (#131)
motivation: align CoC across the swift project

changes: change CoC to link to the swift.org version
2023-06-06 10:00:48 -07:00
51 changed files with 1280 additions and 1903 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

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

@ -0,0 +1,26 @@
name: Main
on:
push:
branches: [main]
schedule:
- cron: "0 8,20 * * *"
jobs:
unit-tests:
name: Unit tests
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
with:
linux_5_9_arguments_override: "--explicit-target-dependency-import-check error"
linux_5_10_arguments_override: "--explicit-target-dependency-import-check error"
linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
macos-tests:
name: macOS tests
uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
with:
runner_pool: nightly
build_scheme: swift-metrics-Package

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

@ -0,0 +1,34 @@
name: PR
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
license_header_check_project_name: "Swift Metrics API"
unit-tests:
name: Unit tests
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
with:
linux_5_9_arguments_override: "--explicit-target-dependency-import-check error"
linux_5_10_arguments_override: "--explicit-target-dependency-import-check error"
linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
cxx-interop:
name: Cxx interop
uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main
macos-tests:
name: macOS tests
uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
with:
runner_pool: general
build_scheme: swift-metrics-Package

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

@ -1,55 +1,3 @@
# Code of Conduct # Code of Conduct
To be a truly great community, SwiftMetrics needs to welcome developers from all walks of life,
with different backgrounds, and with a wide range of experience. A diverse and friendly
community will have more great ideas, more unique perspectives, and produce more great
code. We will work diligently to make the SwiftMetrics community welcoming to everyone.
To give clarity of what is expected of our members, SwiftMetrics has adopted the code of conduct The code of conduct for this project can be found at https://swift.org/code-of-conduct.
defined by [contributor-covenant.org](https://www.contributor-covenant.org). This document is used across many open source
communities, and we think it articulates our values well. The full text is copied below:
### Contributor Code of Conduct v1.3
As contributors and maintainers of this project, and in the interest of fostering an open and
welcoming community, we pledge to respect all people who contribute through reporting
issues, posting feature requests, updating documentation, submitting pull requests or patches,
and other activities.
We are committed to making participation in this project a harassment-free experience for
everyone, regardless of level of experience, gender, gender identity and expression, sexual
orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or
nationality.
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery
- Personal attacks
- Trolling or insulting/derogatory comments
- Public or private harassment
- Publishing others private information, such as physical or electronic addresses, without explicit permission
- Other unethical or unprofessional conduct
Project maintainers have the right and responsibility to remove, edit, or reject comments,
commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of
Conduct, or to ban temporarily or permanently any contributor for other behaviors that they
deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
consistently applying these principles to every aspect of managing this project. Project
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
from the project team.
This code of conduct applies both within project spaces and in public spaces when an
individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
contacting a project maintainer at [swift-server-conduct@group.apple.com](mailto:swift-server-conduct@group.apple.com). All complaints will be reviewed and
investigated and will result in a response that is deemed necessary and appropriate to the
circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter
of an incident.
*This policy is adapted from the Contributor Code of Conduct [version 1.3.0](https://contributor-covenant.org/version/1/3/0/).*
### Reporting
A working group of community members is committed to promptly addressing any [reported issues](mailto:swift-server-conduct@group.apple.com).
Working group members are volunteers appointed by the project lead, with a
preference for individuals with varied backgrounds and perspectives. Membership is expected
to change regularly, and may grow or shrink.

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

@ -1,4 +1,4 @@
// swift-tools-version:5.6 // swift-tools-version:5.9
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// //
// This source file is part of the Swift Metrics API open source project // 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: "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"
@ -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

@ -1,43 +0,0 @@
// swift-tools-version:4.2
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import PackageDescription
let package = Package(
name: "swift-metrics",
products: [
.library(name: "CoreMetrics", targets: ["CoreMetrics"]),
.library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
],
targets: [
.target(
name: "CoreMetrics",
dependencies: []
),
.target(
name: "Metrics",
dependencies: ["CoreMetrics"]
),
.target(
name: "MetricsTestKit",
dependencies: ["Metrics"]
),
.testTarget(
name: "MetricsTests",
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

@ -1,43 +0,0 @@
// swift-tools-version:5.0
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import PackageDescription
let package = Package(
name: "swift-metrics",
products: [
.library(name: "CoreMetrics", targets: ["CoreMetrics"]),
.library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
],
targets: [
.target(
name: "CoreMetrics",
dependencies: []
),
.target(
name: "Metrics",
dependencies: ["CoreMetrics"]
),
.target(
name: "MetricsTestKit",
dependencies: ["Metrics"]
),
.testTarget(
name: "MetricsTests",
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

@ -1,43 +0,0 @@
// swift-tools-version:5.1
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import PackageDescription
let package = Package(
name: "swift-metrics",
products: [
.library(name: "CoreMetrics", targets: ["CoreMetrics"]),
.library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
],
targets: [
.target(
name: "CoreMetrics",
dependencies: []
),
.target(
name: "Metrics",
dependencies: ["CoreMetrics"]
),
.target(
name: "MetricsTestKit",
dependencies: ["Metrics"]
),
.testTarget(
name: "MetricsTests",
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

@ -1,43 +0,0 @@
// swift-tools-version:5.2
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import PackageDescription
let package = Package(
name: "swift-metrics",
products: [
.library(name: "CoreMetrics", targets: ["CoreMetrics"]),
.library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
],
targets: [
.target(
name: "CoreMetrics",
dependencies: []
),
.target(
name: "Metrics",
dependencies: ["CoreMetrics"]
),
.target(
name: "MetricsTestKit",
dependencies: ["Metrics"]
),
.testTarget(
name: "MetricsTests",
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

@ -1,43 +0,0 @@
// swift-tools-version:5.3
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import PackageDescription
let package = Package(
name: "swift-metrics",
products: [
.library(name: "CoreMetrics", targets: ["CoreMetrics"]),
.library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
],
targets: [
.target(
name: "CoreMetrics",
dependencies: []
),
.target(
name: "Metrics",
dependencies: ["CoreMetrics"]
),
.target(
name: "MetricsTestKit",
dependencies: ["Metrics"]
),
.testTarget(
name: "MetricsTests",
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

@ -1,43 +0,0 @@
// swift-tools-version:5.4
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import PackageDescription
let package = Package(
name: "swift-metrics",
products: [
.library(name: "CoreMetrics", targets: ["CoreMetrics"]),
.library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
],
targets: [
.target(
name: "CoreMetrics",
dependencies: []
),
.target(
name: "Metrics",
dependencies: ["CoreMetrics"]
),
.target(
name: "MetricsTestKit",
dependencies: ["Metrics"]
),
.testTarget(
name: "MetricsTests",
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

@ -1,43 +0,0 @@
// swift-tools-version:5.5
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import PackageDescription
let package = Package(
name: "swift-metrics",
products: [
.library(name: "CoreMetrics", targets: ["CoreMetrics"]),
.library(name: "Metrics", targets: ["Metrics"]),
.library(name: "MetricsTestKit", targets: ["MetricsTestKit"]),
],
targets: [
.target(
name: "CoreMetrics",
dependencies: []
),
.target(
name: "Metrics",
dependencies: ["CoreMetrics"]
),
.target(
name: "MetricsTestKit",
dependencies: ["Metrics"]
),
.testTarget(
name: "MetricsTests",
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

@ -90,12 +90,18 @@ This API was designed with the contributors to the Swift on Server community and
### Metric types ### 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. `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 ```swift
counter.increment(by: 100) 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. `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

@ -38,6 +38,6 @@ with the all the required detail.
and in the Server → Security Updates category on the [Swift forums][swift-forums-sec]. and in the Server → Security Updates category on the [Swift forums][swift-forums-sec].
[sswg]: https://github.com/swift-server/sswg [sswg]: https://github.com/swift-server/sswg
[sswg-security]: https://github.com/swift-server/sswg/blob/main/security/README.md [sswg-security]: https://www.swift.org/sswg/security/
[swift-forums-sec]: https://forums.swift.org/c/server/security-updates/ [swift-forums-sec]: https://forums.swift.org/c/server/security-updates/
[mitre]: https://cveform.mitre.org/ [mitre]: https://cveform.mitre.org/

View File

@ -91,7 +91,9 @@ This API was designed with the contributors to the Swift on Server community and
### Metric types ### Metric types
- ``Counter`` - ``Counter``
- ``FloatingPointCounter``
- ``Meter`` - ``Meter``
- ``Recorder`` - ``Recorder``
- ``Gauge``
- ``Timer`` - ``Timer``

View File

@ -34,6 +34,10 @@ import Darwin
import WinSDK import WinSDK
#elseif canImport(Glibc) #elseif canImport(Glibc)
import Glibc import Glibc
#elseif canImport(Android)
import Android
#elseif canImport(Musl)
import Musl
#else #else
#error("Unsupported runtime") #error("Unsupported runtime")
#endif #endif
@ -129,6 +133,8 @@ extension Lock {
} }
} }
extension Lock: @unchecked Sendable {}
/// A reader/writer threading lock based on `libpthread` instead of `libdispatch`. /// A reader/writer threading lock based on `libpthread` instead of `libdispatch`.
/// ///
/// This object provides a lock on top of a single `pthread_rwlock_t`. This kind /// This object provides a lock on top of a single `pthread_rwlock_t`. This kind
@ -271,3 +277,5 @@ extension ReadWriteLock {
try self.withWriterLock(body) try self.withWriterLock(body)
} }
} }
extension ReadWriteLock: @unchecked Sendable {}

File diff suppressed because it is too large Load Diff

View File

@ -11,11 +11,15 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// swift-format-ignore-file
// Note: Whitespace changes are used to workaround compiler bug
// https://github.com/swiftlang/swift/issues/79285
@_exported import CoreMetrics @_exported import 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 +28,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 {
@ -60,6 +68,16 @@ extension Timer {
/// - duration: The duration to record. /// - duration: The duration to record.
@inlinable @inlinable
public func record(_ duration: DispatchTimeInterval) { public func record(_ duration: DispatchTimeInterval) {
// This wrapping in a optional is a workaround because DispatchTimeInterval
// is a non-frozen public enum and Dispatch is built with library evolution
// mode turned on.
// This means we should have an `@unknown default` case, but this breaks
// on non-Darwin platforms.
// Switching over an optional means that the `.none` case will map to
// `default` (which means we'll always have a valid case to go into
// the default case), but in reality this case will never exist as this
// optional will never be nil.
let duration = Optional(duration)
switch duration { switch duration {
case .nanoseconds(let value): case .nanoseconds(let value):
self.recordNanoseconds(value) self.recordNanoseconds(value)
@ -71,6 +89,72 @@ extension Timer {
self.recordSeconds(value) self.recordSeconds(value)
case .never: case .never:
self.record(0) self.record(0)
default:
self.record(0)
} }
} }
} }
extension Timer {
/// Convenience for recording a duration based on `Duration`.
///
/// `Duration` will be converted to an `Int64` number of nanoseconds, and then recorded with nanosecond precision.
///
/// - Parameters:
/// - duration: The `Duration` to record.
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
@inlinable
public func record(duration: Duration) {
// `Duration` doesn't have a nice way to convert it nanoseconds or seconds,
// and manual conversion can overflow.
let seconds = duration.components.seconds.multipliedReportingOverflow(by: 1_000_000_000)
guard !seconds.overflow else { return self.recordNanoseconds(Int64.max) }
let nanoseconds = seconds.partialValue.addingReportingOverflow(duration.components.attoseconds / 1_000_000_000)
guard !nanoseconds.overflow else { return self.recordNanoseconds(Int64.max) }
self.recordNanoseconds(nanoseconds.partialValue)
}
#if compiler(>=6.0)
/// Convenience for measuring duration of a closure.
///
/// - Parameters:
/// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
/// - body: The closure to record the duration of.
@inlinable
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
clock: Clock = .continuous,
// DO NOT FIX THE WHITESPACE IN THE NEXT LINE UNTIL 5.10 IS UNSUPPORTED
// https://github.com/swiftlang/swift/issues/79285
body: () throws(Failure) -> Result) throws(Failure) -> Result where Clock.Duration == Duration {
let start = clock.now
defer {
self.record(duration: start.duration(to: clock.now))
}
return try body()
}
/// Convenience for measuring duration of a closure.
///
/// - Parameters:
/// - clock: The clock used for measuring the duration. Defaults to the continuous clock.
/// - isolation: The isolation of the method. Defaults to the isolation of the caller.
/// - body: The closure to record the duration of.
@inlinable
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public func measure<Result, Failure: Error, Clock: _Concurrency.Clock>(
clock: Clock = .continuous,
isolation: isolated (any Actor)? = #isolation,
// DO NOT FIX THE WHITESPACE IN THE NEXT LINE UNTIL 5.10 IS UNSUPPORTED
// https://github.com/swiftlang/swift/issues/79285
body: () async throws(Failure) -> sending Result) async throws(Failure) -> sending Result where Clock.Duration == Duration {
let start = clock.now
defer {
self.record(duration: start.duration(to: clock.now))
}
return try await body()
}
#endif
}

View File

@ -43,7 +43,7 @@ public final class TestMetrics: MetricsFactory {
public typealias Label = String public typealias Label = String
public typealias Dimensions = String public typealias Dimensions = String
public struct FullKey { public struct FullKey: Sendable {
let label: Label let label: Label
let dimensions: [(String, String)] let dimensions: [(String, String)]
} }
@ -57,7 +57,7 @@ public final class TestMetrics: MetricsFactory {
// nothing to do // nothing to do
} }
/// Reset method to destroy all created ``TestCounter``, ``TestRecorder`` and ``TestTimer``. /// Reset method to destroy all created ``TestCounter``, ``TestMeter``, ``TestRecorder`` and ``TestTimer``.
/// Invoke this method in between test runs to verify that Counters are created as needed. /// Invoke this method in between test runs to verify that Counters are created as needed.
public func reset() { public func reset() {
self.lock.withLock { self.lock.withLock {
@ -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,14 +354,14 @@ 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()
private var _values = [(Date, Double)]() private var _values = [(Date, Double)]()
init(label: String, dimensions: [(String, String)]) { init(label: String, dimensions: [(String, String)]) {
self.id = NSUUID().uuidString self.id = UUID().uuidString
self.label = label self.label = label
self.dimensions = dimensions self.dimensions = dimensions
} }
@ -378,39 +378,75 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable {
} }
public func increment(by amount: Double) { public func increment(by amount: Double) {
// Drop illegal values
// - cannot increment by NaN
guard !amount.isNaN else {
return
}
// - cannot increment by infinite quantities
guard !amount.isInfinite else {
return
}
// - cannot increment by negative values
guard amount.sign == .plus else {
return
}
// - cannot increment by zero
guard !amount.isZero else {
return
}
self.lock.withLock { self.lock.withLock {
let lastValue = self._values.last?.1 ?? 0 let lastValue: Double = self._values.last?.1 ?? 0
let newValue = lastValue - amount let newValue = lastValue + amount
_values.append((Date(), Double(newValue))) _values.append((Date(), newValue))
} }
} }
public func decrement(by amount: Double) { public func decrement(by amount: Double) {
// Drop illegal values
// - cannot decrement by NaN
guard !amount.isNaN else {
return
}
// - cannot decrement by infinite quantities
guard !amount.isInfinite else {
return
}
// - cannot decrement by negative values
guard amount.sign == .plus else {
return
}
// - cannot decrement by zero
guard !amount.isZero else {
return
}
self.lock.withLock { self.lock.withLock {
let lastValue = self._values.last?.1 ?? 0 let lastValue: Double = self._values.last?.1 ?? 0
let newValue = lastValue - amount let newValue = lastValue - amount
_values.append((Date(), Double(newValue))) _values.append((Date(), newValue))
} }
} }
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
} }
} }
@ -421,14 +457,14 @@ 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()
private var _values = [(Date, Double)]() private var _values = [(Date, Double)]()
init(label: String, dimensions: [(String, String)], aggregate: Bool) { init(label: String, dimensions: [(String, String)], aggregate: Bool) {
self.id = NSUUID().uuidString self.id = UUID().uuidString
self.label = label self.label = label
self.dimensions = dimensions self.dimensions = dimensions
self.aggregate = aggregate self.aggregate = aggregate
@ -446,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
} }
} }
@ -473,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()
@ -507,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
} }
} }
@ -540,26 +576,16 @@ extension NSLock {
// MARK: - Errors // MARK: - Errors
#if compiler(>=5.6)
public enum TestMetricsError: Error { public enum TestMetricsError: Error {
case missingMetric(label: String, dimensions: [(String, String)]) case missingMetric(label: String, dimensions: [(String, String)])
case illegalMetricType(metric: Sendable, expected: String) case illegalMetricType(metric: Sendable, expected: String)
} }
#else
public enum TestMetricsError: Error {
case missingMetric(label: String, dimensions: [(String, String)])
case illegalMetricType(metric: Any, expected: String)
}
#endif
// MARK: - Sendable support // MARK: - Sendable support
#if compiler(>=5.6)
// ideally we would not be using @unchecked here, but concurrency-safety checks do not recognize locks // ideally we would not be using @unchecked here, but concurrency-safety checks do not recognize locks
extension TestMetrics: @unchecked Sendable {} extension TestMetrics: @unchecked Sendable {}
extension TestCounter: @unchecked Sendable {} extension TestCounter: @unchecked Sendable {}
extension TestMeter: @unchecked Sendable {} extension TestMeter: @unchecked Sendable {}
extension TestRecorder: @unchecked Sendable {} extension TestRecorder: @unchecked Sendable {}
extension TestTimer: @unchecked Sendable {} extension TestTimer: @unchecked Sendable {}
#endif

View File

@ -1,32 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// LinuxMain.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
#if os(Linux) || os(FreeBSD)
@testable import MetricsTests
XCTMain([
testCase(MetricsExtensionsTests.allTests),
testCase(MetricsTests.allTests),
])
#endif

View File

@ -1,61 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// CoreMetricsTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension MetricsTests {
static var allTests: [(String, (MetricsTests) -> () throws -> Void)] {
return [
("testCounters", testCounters),
("testCounterBlock", testCounterBlock),
("testDefaultFloatingPointCounter_ignoresNan", testDefaultFloatingPointCounter_ignoresNan),
("testDefaultFloatingPointCounter_ignoresInfinity", testDefaultFloatingPointCounter_ignoresInfinity),
("testDefaultFloatingPointCounter_ignoresNegativeValues", testDefaultFloatingPointCounter_ignoresNegativeValues),
("testDefaultFloatingPointCounter_ignoresZero", testDefaultFloatingPointCounter_ignoresZero),
("testDefaultFloatingPointCounter_ceilsExtremeValues", testDefaultFloatingPointCounter_ceilsExtremeValues),
("testDefaultFloatingPointCounter_accumulatesFloatingPointDecimalValues", testDefaultFloatingPointCounter_accumulatesFloatingPointDecimalValues),
("testRecorders", testRecorders),
("testRecordersInt", testRecordersInt),
("testRecordersFloat", testRecordersFloat),
("testRecorderBlock", testRecorderBlock),
("testTimers", testTimers),
("testTimerBlock", testTimerBlock),
("testTimerVariants", testTimerVariants),
("testTimerOverflow", testTimerOverflow),
("testTimerHandlesUnsignedOverflow", testTimerHandlesUnsignedOverflow),
("testGauge", testGauge),
("testGaugeBlock", testGaugeBlock),
("testMeter", testMeter),
("testMeterBlock", testMeterBlock),
("testMUX_Counter", testMUX_Counter),
("testMUX_Meter", testMUX_Meter),
("testMUX_Recorder", testMUX_Recorder),
("testMUX_Timer", testMUX_Timer),
("testCustomFactory", testCustomFactory),
("testDestroyingGauge", testDestroyingGauge),
("testDestroyingMeter", testDestroyingMeter),
("testDestroyingCounter", testDestroyingCounter),
("testDestroyingTimer", testDestroyingTimer),
("testDescriptions", testDescriptions),
]
}
}

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
@ -24,17 +25,17 @@ class MetricsTests: XCTestCase {
let group = DispatchGroup() let group = DispatchGroup()
let name = "counter-\(UUID().uuidString)" let name = "counter-\(UUID().uuidString)"
let counter = Counter(label: name) let counter = Counter(label: name)
let testCounter = counter._handler as! TestCounter 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 {
counter.increment(by: Int.random(in: 0 ... 1000)) defer { group.leave() }
group.leave() counter.increment(by: Int.random(in: 0...1000))
} }
} }
group.wait() group.wait()
XCTAssertEqual(testCounter.values.count - 1, total, "expected number of entries to match") XCTAssertEqual(testCounter.values.count, total, "expected number of entries to match")
testCounter.reset() testCounter.reset()
XCTAssertEqual(testCounter.values.count, 0, "expected number of entries to match") XCTAssertEqual(testCounter.values.count, 0, "expected number of entries to match")
} }
@ -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")
@ -153,17 +154,17 @@ class MetricsTests: XCTestCase {
let group = DispatchGroup() let group = DispatchGroup()
let name = "recorder-\(UUID().uuidString)" let name = "recorder-\(UUID().uuidString)"
let recorder = Recorder(label: name) let recorder = Recorder(label: name)
let testRecorder = recorder._handler as! TestRecorder 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 {
recorder.record(Int.random(in: Int.min ... Int.max)) defer { group.leave() }
group.leave() recorder.record(Int.random(in: Int.min...Int.max))
} }
} }
group.wait() group.wait()
XCTAssertEqual(testRecorder.values.count - 1, total, "expected number of entries to match") XCTAssertEqual(testRecorder.values.count, total, "expected number of entries to match")
} }
func testRecordersInt() throws { func testRecordersInt() throws {
@ -171,13 +172,13 @@ class MetricsTests: XCTestCase {
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let recorder = Recorder(label: "test-recorder") let recorder = Recorder(label: "test-recorder")
let testRecorder = recorder._handler as! TestRecorder 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 - 1 { 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 - 1 { 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.")
} }
} }
@ -187,13 +188,13 @@ class MetricsTests: XCTestCase {
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let recorder = Recorder(label: "test-recorder") let recorder = Recorder(label: "test-recorder")
let testRecorder = recorder._handler as! TestRecorder 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 - 1 { 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 - 1 { 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")
@ -218,17 +219,17 @@ class MetricsTests: XCTestCase {
let group = DispatchGroup() let group = DispatchGroup()
let name = "timer-\(UUID().uuidString)" let name = "timer-\(UUID().uuidString)"
let timer = Timer(label: name) let timer = Timer(label: name)
let testTimer = timer._handler as! TestTimer 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 {
timer.recordNanoseconds(Int64.random(in: Int64.min ... Int64.max)) defer { group.leave() }
group.leave() timer.recordNanoseconds(Int64.random(in: Int64.min...Int64.max))
} }
} }
group.wait() group.wait()
XCTAssertEqual(testTimer.values.count - 1, total, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, total, "expected number of entries to match")
} }
func testTimerBlock() throws { func testTimerBlock() throws {
@ -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")
@ -250,24 +251,24 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let timer = Timer(label: "test-timer") let timer = Timer(label: "test-timer")
let testTimer = timer._handler as! TestTimer 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")
@ -279,7 +280,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let timer = Timer(label: "test-timer") let timer = Timer(label: "test-timer")
let testTimer = timer._handler as! TestTimer let testTimer = try metrics.expectTimer(timer)
// nano (integer) // nano (integer)
timer.recordNanoseconds(Int64.max) timer.recordNanoseconds(Int64.max)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
@ -316,7 +317,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let timer = Timer(label: "test-timer") let timer = Timer(label: "test-timer")
let testTimer = timer._handler as! TestTimer let testTimer = try metrics.expectTimer(timer)
// nano // nano
timer.recordNanoseconds(UInt64.max) timer.recordNanoseconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
@ -341,10 +342,10 @@ 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 = gauge._handler as! TestRecorder let recorder = try metrics.expectRecorder(gauge)
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
XCTAssertEqual(recorder.lastValue, value, "expected value to match") XCTAssertEqual(recorder.lastValue, value, "expected value to match")
} }
@ -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")
@ -367,11 +368,11 @@ class MetricsTests: XCTestCase {
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "meter-\(NSUUID().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 = meter._handler as! TestMeter let testMeter = try metrics.expectMeter(meter)
XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match") XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testMeter.values[0], value, "expected value to match") XCTAssertEqual(testMeter.values[0], value, "expected value to match")
} }
@ -381,31 +382,159 @@ class MetricsTests: XCTestCase {
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let name = "meter-\(NSUUID().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")
XCTAssertEqual(testMeter.values[0], value, "expected value to match") XCTAssertEqual(testMeter.values[0], value, "expected value to match")
} }
func testMeterInt() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
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 {
meter.set(values[i])
}
XCTAssertEqual(values.count, testMeter.values.count, "expected number of entries to match")
for i in 0..<values.count {
XCTAssertEqual(Int32(testMeter.values[i]), values[i], "expected value #\(i) to match.")
}
}
func testMeterFloat() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
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 {
meter.set(values[i])
}
XCTAssertEqual(values.count, testMeter.values.count, "expected number of entries to match")
for i in 0..<values.count {
XCTAssertEqual(Float(testMeter.values[i]), values[i], "expected value #\(i) to match.")
}
}
func testMeterIncrement() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let group = DispatchGroup()
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 {
group.enter()
DispatchQueue(label: "\(name)-queue").async {
defer { group.leave() }
meter.increment(by: values[i])
}
}
group.wait()
XCTAssertEqual(testMeter.values.count, values.count, "expected number of entries to match")
XCTAssertEqual(testMeter.values.last!, values.reduce(0.0, +), accuracy: 0.1, "expected total value to match")
}
func testMeterDecrement() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let group = DispatchGroup()
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 {
group.enter()
DispatchQueue(label: "\(name)-queue").async {
defer { group.leave() }
meter.decrement(by: values[i])
}
}
group.wait()
XCTAssertEqual(testMeter.values.count, values.count, "expected number of entries to match")
XCTAssertEqual(testMeter.values.last!, values.reduce(0.0, -), accuracy: 0.1, "expected total value to match")
}
func testDefaultMeterIgnoresNan() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let name = "meter-\(UUID().uuidString)"
let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter)
meter.increment(by: Double.nan)
meter.increment(by: Double.signalingNaN)
XCTAssertEqual(testMeter.values.count, 0, "expected nan values to be ignored")
}
func testDefaultMeterIgnoresInfinity() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let name = "meter-\(UUID().uuidString)"
let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter)
meter.increment(by: Double.infinity)
meter.increment(by: -Double.infinity)
XCTAssertEqual(testMeter.values.count, 0, "expected infinite values to be ignored")
}
func testDefaultMeterIgnoresNegativeValues() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let name = "meter-\(UUID().uuidString)"
let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter)
meter.increment(by: -100)
XCTAssertEqual(testMeter.values.count, 0, "expected negative values to be ignored")
}
func testDefaultMeterIgnoresZero() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let name = "meter-\(UUID().uuidString)"
let meter = Meter(label: name)
let testMeter = try metrics.expectMeter(meter)
meter.increment(by: 0)
meter.increment(by: -0)
XCTAssertEqual(testMeter.values.count, 0, "expected zero values to be ignored")
}
func testMUX_Counter() throws { func testMUX_Counter() throws {
// bootstrap with our test metrics // bootstrap with our test metrics
let factories = [TestMetrics(), TestMetrics(), TestMetrics()] let factories = [TestMetrics(), TestMetrics(), TestMetrics()]
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")
} }
@ -416,11 +545,11 @@ class MetricsTests: XCTestCase {
let factories = [TestMetrics(), TestMetrics(), TestMetrics()] let factories = [TestMetrics(), TestMetrics(), TestMetrics()]
MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories)) MetricsSystem.bootstrapInternal(MultiplexMetricsHandler(factories: factories))
// run the test // run the test
let name = NSUUID().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")
@ -434,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")
@ -451,20 +580,24 @@ 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"
)
} }
} }
func testCustomFactory() { func testCustomHandler() {
final class CustomHandler: CounterHandler { final class CustomHandler: CounterHandler {
func increment<DataType>(by: DataType) where DataType: BinaryInteger {} func increment<DataType>(by: DataType) where DataType: BinaryInteger {}
func reset() {} func reset() {}
@ -476,17 +609,90 @@ class MetricsTests: XCTestCase {
XCTAssertTrue(counter2._handler is CustomHandler, "expected custom log handler") XCTAssertTrue(counter2._handler is CustomHandler, "expected custom log handler")
} }
func testCustomFactory() {
// @unchecked Sendable is okay here - locking is done manually.
final class CustomFactory: MetricsFactory, @unchecked Sendable {
init(handler: CustomHandler) {
self.handler = handler
}
final class CustomHandler: CounterHandler {
func increment<DataType>(by: DataType) where DataType: BinaryInteger {}
func reset() {}
}
private let handler: CustomHandler
private let lock: NSLock = NSLock()
private var locked_didCallDestroyCounter: Bool = false
var didCallDestroyCounter: Bool {
self.lock.lock()
defer {
lock.unlock()
}
return self.locked_didCallDestroyCounter
}
func makeCounter(label: String, dimensions: [(String, String)]) -> any CoreMetrics.CounterHandler {
handler
}
func makeRecorder(
label: String,
dimensions: [(String, String)],
aggregate: Bool
) -> any CoreMetrics.RecorderHandler {
fatalError("Unsupported")
}
func makeTimer(label: String, dimensions: [(String, String)]) -> any CoreMetrics.TimerHandler {
fatalError("Unsupported")
}
func destroyCounter(_ handler: any CoreMetrics.CounterHandler) {
XCTAssertTrue(
handler === self.handler,
"The handler to be destroyed doesn't match the expected handler."
)
self.lock.lock()
defer {
lock.unlock()
}
self.locked_didCallDestroyCounter = true
}
func destroyRecorder(_ handler: any CoreMetrics.RecorderHandler) {
fatalError("Unsupported")
}
func destroyTimer(_ handler: any CoreMetrics.TimerHandler) {
fatalError("Unsupported")
}
}
let handler = CustomFactory.CustomHandler()
let factory = CustomFactory(handler: handler)
XCTAssertFalse(factory.didCallDestroyCounter)
do {
let counter1 = Counter(label: "foo", factory: factory)
XCTAssertTrue(counter1._handler is CustomFactory.CustomHandler, "expected a custom metrics handler")
XCTAssertTrue(counter1._factory is CustomFactory, "expected a custom metrics factory")
counter1.destroy()
}
XCTAssertTrue(factory.didCallDestroyCounter)
}
func testDestroyingGauge() throws { func testDestroyingGauge() throws {
let metrics = TestMetrics() let metrics = TestMetrics()
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)
let recorder = gauge._handler as! TestRecorder let recorder = try metrics.expectRecorder(gauge)
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
XCTAssertEqual(recorder.values.first, value, "expected value to match") XCTAssertEqual(recorder.values.first, value, "expected value to match")
XCTAssertEqual(metrics.recorders.count, 1, "recorder should have been stored") XCTAssertEqual(metrics.recorders.count, 1, "recorder should have been stored")
@ -498,25 +704,29 @@ class MetricsTests: XCTestCase {
let gaugeAgain = Gauge(label: name) let gaugeAgain = Gauge(label: name)
gaugeAgain.record(-value) gaugeAgain.record(-value)
let recorderAgain = gaugeAgain._handler as! TestRecorder let recorderAgain = try metrics.expectRecorder(gaugeAgain)
XCTAssertEqual(recorderAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorderAgain.values.count, 1, "expected number of entries to match")
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 {
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let name = "meter-\(NSUUID().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 = meter._handler as! TestMeter let testMeter = try metrics.expectMeter(meter)
XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match") XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testMeter.values.first, value, "expected value to match") XCTAssertEqual(testMeter.values.first, value, "expected value to match")
XCTAssertEqual(metrics.meters.count, 1, "recorder should have been stored") XCTAssertEqual(metrics.meters.count, 1, "recorder should have been stored")
@ -528,12 +738,16 @@ class MetricsTests: XCTestCase {
let meterAgain = Meter(label: name) let meterAgain = Meter(label: name)
meterAgain.set(-value) meterAgain.set(-value)
let testMeterAgain = meterAgain._handler as! TestMeter let testMeterAgain = try metrics.expectMeter(meterAgain)
XCTAssertEqual(testMeterAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(testMeterAgain.values.count, 1, "expected number of entries to match")
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 {
@ -541,12 +755,12 @@ 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)
let testCounter = counter._handler as! TestCounter let testCounter = try metrics.expectCounter(counter)
XCTAssertEqual(testCounter.values.count, 1, "expected number of entries to match") XCTAssertEqual(testCounter.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testCounter.values.first, Int64(value), "expected value to match") XCTAssertEqual(testCounter.values.first, Int64(value), "expected value to match")
XCTAssertEqual(metrics.counters.count, 1, "counter should have been stored") XCTAssertEqual(metrics.counters.count, 1, "counter should have been stored")
@ -558,12 +772,16 @@ class MetricsTests: XCTestCase {
let counterAgain = Counter(label: name) let counterAgain = Counter(label: name)
counterAgain.increment(by: value) counterAgain.increment(by: value)
let testCounterAgain = counterAgain._handler as! TestCounter let testCounterAgain = try metrics.expectCounter(counterAgain)
XCTAssertEqual(testCounterAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(testCounterAgain.values.count, 1, "expected number of entries to match")
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 {
@ -571,12 +789,12 @@ 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)
let testTimer = timer._handler as! TestTimer 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(testTimer.values.first, value, "expected value to match") XCTAssertEqual(testTimer.values.first, value, "expected value to match")
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
@ -587,12 +805,16 @@ class MetricsTests: XCTestCase {
let timerAgain = Timer(label: name) let timerAgain = Timer(label: name)
timerAgain.recordNanoseconds(value) timerAgain.recordNanoseconds(value)
let testTimerAgain = timerAgain._handler as! TestTimer let testTimerAgain = try metrics.expectTimer(timerAgain)
XCTAssertEqual(testTimerAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimerAgain.values.count, 1, "expected number of entries to match")
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

@ -1,36 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// MetricsTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension MetricsExtensionsTests {
static var allTests: [(String, (MetricsExtensionsTests) -> () throws -> Void)] {
return [
("testTimerBlock", testTimerBlock),
("testTimerWithTimeInterval", testTimerWithTimeInterval),
("testTimerWithDispatchTime", testTimerWithDispatchTime),
("testTimerWithDispatchTimeInterval", testTimerWithDispatchTimeInterval),
("testTimerUnits", testTimerUnits),
("testPreferDisplayUnit", testPreferDisplayUnit),
]
}
}

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
@ -39,8 +40,8 @@ class MetricsExtensionsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let timer = Timer(label: "test-timer") let timer = Timer(label: "test-timer")
let testTimer = timer._handler as! TestTimer 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")
@ -52,24 +53,24 @@ class MetricsExtensionsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
// run the test // run the test
let timer = Timer(label: "test-timer") let timer = Timer(label: "test-timer")
let testTimer = timer._handler as! TestTimer 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")
@ -79,7 +80,7 @@ class MetricsExtensionsTests: XCTestCase {
XCTAssertEqual(testTimer.values[4], 0, "expected value to match") XCTAssertEqual(testTimer.values[4], 0, "expected value to match")
} }
func testTimerWithDispatchTimeInterval() { func testTimerWithDispatchTimeInterval() throws {
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
@ -90,9 +91,41 @@ class MetricsExtensionsTests: XCTestCase {
let end = DispatchTime(uptimeNanoseconds: start.uptimeNanoseconds + 1000 * 1000 * 1000) let end = DispatchTime(uptimeNanoseconds: start.uptimeNanoseconds + 1000 * 1000 * 1000)
timer.recordInterval(since: start, end: end) timer.recordInterval(since: start, end: end)
let testTimer = timer._handler as! TestTimer 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")
}
func testTimerDuration() throws {
// Wrapping only the insides of the test case so that the generated
// tests on Linux in MetricsTests+XCTest don't complain that the func does not exist.
guard #available(iOS 16, macOS 13, tvOS 15, watchOS 8, *) else {
throw XCTSkip("Timer.record(_ duration: Duration) is not available on this platform")
}
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
let name = "timer-\(UUID().uuidString)"
let timer = Timer(label: name)
let duration = Duration(secondsComponent: 3, attosecondsComponent: 123_000_000_000_000_000)
let nanoseconds = duration.components.seconds * 1_000_000_000 + duration.components.attoseconds / 1_000_000_000
timer.record(duration: duration)
// Record a Duration that would overflow,
// expect Int64.max to be recorded.
timer.record(duration: Duration(secondsComponent: 10_000_000_000, attosecondsComponent: 123))
let testTimer = try metrics.expectTimer(timer)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(testTimer.values.first, nanoseconds, "expected value to match")
XCTAssertEqual(testTimer.values[1], Int64.max, "expected to record Int64.max if Durataion overflows")
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
} }
@ -101,63 +134,145 @@ 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)
let testTimer = timer._handler as! TestTimer 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(testTimer.values.first, value, "expected value to match") XCTAssertEqual(testTimer.values.first, value, "expected value to match")
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)
let testSecondsTimer = secondsTimer._handler as! TestTimer let testSecondsTimer = try metrics.expectTimer(secondsTimer)
XCTAssertEqual(testSecondsTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testSecondsTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(metrics.timers.count, 2, "timer should have been stored") XCTAssertEqual(metrics.timers.count, 2, "timer should have been stored")
} }
func testPreferDisplayUnit() { func testPreferDisplayUnit() throws {
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 = timer._handler as! TestTimer 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"
)
} }
#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 // https://bugs.swift.org/browse/SR-6310
extension DispatchTimeInterval { extension DispatchTimeInterval {
func nano() -> Int { func nano() -> Int {
switch self { // This wrapping in a optional is a workaround because DispatchTimeInterval
// is a non-frozen public enum and Dispatch is built with library evolution
// mode turned on.
// This means we should have an `@unknown default` case, but this breaks
// on non-Darwin platforms.
// Switching over an optional means that the `.none` case will map to
// `default` (which means we'll always have a valid case to go into
// the default case), but in reality this case will never exist as this
// optional will never be nil.
let interval = Optional(self)
switch interval {
case .nanoseconds(let value): case .nanoseconds(let value):
return value return value
case .microseconds(let value): case .microseconds(let value):
@ -168,6 +283,30 @@ extension DispatchTimeInterval {
return value * 1_000_000_000 return value * 1_000_000_000
case .never: case .never:
return 0 return 0
default:
return 0
} }
} }
} }
#if swift(>=5.7)
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
extension Swift.Duration {
fileprivate var nanosecondsClamped: Int64 {
let components = self.components
let secondsComponentNanos = components.seconds.multipliedReportingOverflow(by: 1_000_000_000)
let attosCompononentNanos = components.attoseconds / 1_000_000_000
let combinedNanos = secondsComponentNanos.partialValue.addingReportingOverflow(attosCompononentNanos)
guard
!secondsComponentNanos.overflow,
!combinedNanos.overflow
else {
return .max
}
return combinedNanos.partialValue
}
}
#endif

View File

@ -12,36 +12,36 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@testable import CoreMetrics
import Dispatch import Dispatch
import MetricsTestKit import MetricsTestKit
import XCTest import XCTest
@testable import CoreMetrics
class SendableTest: XCTestCase { class SendableTest: XCTestCase {
#if compiler(>=5.6) func testSendableMetrics() async throws {
func testSendableMetrics() async {
// bootstrap with our test metrics // bootstrap with our test metrics
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
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
counter.increment(by: value) counter.increment(by: value)
let handler = counter._handler as! TestCounter let handler = try metrics.expectCounter(counter)
return handler.values return handler.values
} }
let values = await task.value let values = try await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match") XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], Int64(value), "expected value to match") XCTAssertEqual(values[0], Int64(value), "expected value to match")
} }
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
@ -55,48 +55,47 @@ 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
recorder.record(value) recorder.record(value)
let handler = recorder._handler as! TestRecorder let handler = try metrics.expectRecorder(recorder)
return handler.values return handler.values
} }
let values = await task.value let values = try await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match") XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], value, "expected value to match") XCTAssertEqual(values[0], value, "expected value to match")
} }
do { do {
let name = "meter-\(NSUUID().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
meter.set(value) meter.set(value)
let handler = meter._handler as! TestMeter let handler = try metrics.expectMeter(meter)
return handler.values return handler.values
} }
let values = await task.value let values = try await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match") XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], value, "expected value to match") XCTAssertEqual(values[0], value, "expected value to match")
} }
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
timer.recordNanoseconds(value) timer.recordNanoseconds(value)
let handler = timer._handler as! TestTimer let handler = try metrics.expectTimer(timer)
return handler.values return handler.values
} }
let values = await task.value let values = try await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match") XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], value, "expected value to match") XCTAssertEqual(values[0], value, "expected value to match")
} }
} }
#endif
} }

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,16 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:18.04-5.0
build:
args:
ubuntu_version: "bionic"
swift_version: "5.0"
test:
image: swift-metrics:18.04-5.0
shell:
image: swift-metrics:18.04-5.0

View File

@ -1,18 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:18.04-5.1
build:
args:
ubuntu_version: "bionic"
swift_version: "5.1"
test:
image: swift-metrics:18.04-5.1
environment: []
#- SANITIZER_ARG=--sanitize=thread
shell:
image: swift-metrics:18.04-5.1

View File

@ -1,18 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:18.04-5.2
build:
args:
ubuntu_version: "bionic"
swift_version: "5.2"
test:
image: swift-metrics:18.04-5.2
environment: []
#- SANITIZER_ARG=--sanitize=thread
shell:
image: swift-metrics:18.04-5.2

View File

@ -1,18 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:18.04-5.3
build:
args:
ubuntu_version: "bionic"
swift_version: "5.3"
test:
image: swift-metrics:18.04-5.3
environment: []
#- SANITIZER_ARG=--sanitize=thread
shell:
image: swift-metrics:18.04-5.3

View File

@ -1,18 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:20.04-5.4
build:
args:
ubuntu_version: "focal"
swift_version: "5.4"
test:
image: swift-metrics:20.04-5.4
environment: []
#- SANITIZER_ARG=--sanitize=thread
shell:
image: swift-metrics:20.04-5.4

View File

@ -1,19 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:20.04-5.5
build:
args:
ubuntu_version: "focal"
swift_version: "5.5"
test:
image: swift-metrics:20.04-5.5
environment:
- FORCE_TEST_DISCOVERY=--enable-test-discovery
#- SANITIZER_ARG=--sanitize=thread
shell:
image: swift-metrics:20.04-5.5

View File

@ -1,19 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:20.04-5.6
build:
args:
ubuntu_version: "focal"
swift_version: "5.6"
test:
image: swift-metrics:20.04-5.6
environment:
- FORCE_TEST_DISCOVERY=--enable-test-discovery
#- SANITIZER_ARG=--sanitize=thread
shell:
image: swift-metrics:20.04-5.6

View File

@ -1,18 +0,0 @@
version: "3"
services:
runtime-setup:
image: swift-metrics:20.04-5.7
build:
args:
ubuntu_version: "focal"
swift_version: "5.7"
test:
image: swift-metrics:20.04-5.7
environment: []
#- SANITIZER_ARG=--sanitize=thread
shell:
image: swift-metrics:20.04-5.7

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.9
build:
args:
base_image: "swiftlang/swift:nightly-5.9-jammy"
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,43 +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"
docs:
<<: *common
environment:
- CI
command: /bin/bash -xcl "./scripts/generate_docs.sh"
test:
<<: *common
command: /bin/bash -xcl "swift test -Xswiftc -warnings-as-errors $${FORCE_TEST_DISCOVERY-} $${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,136 +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
# repodir
function all_modules() {
local repodir="$1"
(
set -eu
cd "$repodir"
swift package dump-package | jq '.products |
map(select(.type | has("library") )) |
map(.name) | .[]' | tr -d '"'
)
}
# repodir tag output
function build_and_do() {
local repodir=$1
local tag=$2
local output=$3
(
cd "$repodir"
git checkout -q "$tag"
swift build
while read -r module; do
swift api-digester -sdk "$sdk" -dump-sdk -module "$module" \
-o "$output/$module.json" -I "$repodir/.build/debug"
done < <(all_modules "$repodir")
)
}
function usage() {
echo >&2 "Usage: $0 REPO-GITHUB-URL NEW-VERSION OLD-VERSIONS..."
echo >&2
echo >&2 "This script requires a Swift 5.1+ toolchain."
echo >&2
echo >&2 "Examples:"
echo >&2
echo >&2 "Check between master and tag 2.1.1 of swift-nio:"
echo >&2 " $0 https://github.com/apple/swift-nio master 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
sdk=/
if [[ "$(uname -s)" == Darwin ]]; then
sdk=$(xcrun --show-sdk-path)
fi
hash jq 2> /dev/null || { echo >&2 "ERROR: jq must be installed"; exit 1; }
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/*'
errors=0
for old_tag in "$@"; do
mkdir "$tmpdir/api-old"
mkdir "$tmpdir/api-new"
echo "Checking public API breakages from $old_tag to $new_tag"
build_and_do "$repodir" "$new_tag" "$tmpdir/api-new/"
build_and_do "$repodir" "$old_tag" "$tmpdir/api-old/"
for f in "$tmpdir/api-new"/*; do
f=$(basename "$f")
report="$tmpdir/$f.report"
if [[ ! -f "$tmpdir/api-old/$f" ]]; then
echo "NOTICE: NEW MODULE $f"
continue
fi
echo -n "Checking $f... "
swift api-digester -sdk "$sdk" -diagnose-sdk \
--input-paths "$tmpdir/api-old/$f" -input-paths "$tmpdir/api-new/$f" 2>&1 \
> "$report" 2>&1
if ! shasum "$report" | grep -q cefc4ee5bb7bcdb7cb5a7747efa178dab3c794d5; then
echo ERROR
echo >&2 "=============================="
echo >&2 "ERROR: public API change in $f"
echo >&2 "=============================="
cat >&2 "$report"
errors=$(( errors + 1 ))
else
echo OK
fi
done
rm -rf "$tmpdir/api-new" "$tmpdir/api-old"
done
if [[ "$errors" == 0 ]]; then
echo "OK, all seems good"
fi
echo done
exit "$errors"

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,122 +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 -e
my_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
root_path="$my_path/.."
version=$(git describe --abbrev=0 --tags || echo "0.0.0")
modules=(CoreMetrics Metrics)
if [[ "$(uname -s)" == "Linux" ]]; then
# build code if required
if [[ ! -d "$root_path/.build/x86_64-unknown-linux" ]]; then
swift build
fi
# setup source-kitten if required
mkdir -p "$root_path/.build/sourcekitten"
source_kitten_source_path="$root_path/.build/sourcekitten/source"
if [[ ! -d "$source_kitten_source_path" ]]; then
git clone https://github.com/jpsim/SourceKitten.git "$source_kitten_source_path"
fi
source_kitten_path="$source_kitten_source_path/.build/debug"
if [[ ! -d "$source_kitten_path" ]]; then
rm -rf "$source_kitten_source_path/.swift-version"
cd "$source_kitten_source_path" && swift build && cd "$root_path"
fi
# generate
for module in "${modules[@]}"; do
if [[ ! -f "$root_path/.build/sourcekitten/$module.json" ]]; then
"$source_kitten_path/sourcekitten" doc --spm --module-name $module > "$root_path/.build/sourcekitten/$module.json"
fi
done
fi
[[ -d docs/$version ]] || mkdir -p docs/$version
[[ -d swift-metrics.xcodeproj ]] || swift package generate-xcodeproj
# run jazzy
if ! command -v jazzy > /dev/null; then
gem install jazzy --no-ri --no-rdoc
fi
jazzy_dir="$root_path/.build/jazzy"
rm -rf "$jazzy_dir"
mkdir -p "$jazzy_dir"
module_switcher="$jazzy_dir/README.md"
jazzy_args=(--clean
--author 'SwiftMetrics team'
--readme "$module_switcher"
--author_url https://github.com/apple/swift-metrics
--github_url https://github.com/apple/swift-metrics
--github-file-prefix https://github.com/apple/swift-metrics/tree/$version
--theme fullwidth
--xcodebuild-arguments -scheme,swift-metrics-Package)
cat > "$module_switcher" <<"EOF"
# SwiftMetrics Docs
SwiftMetrics is a Swift metrics API package.
To get started with SwiftMetrics, [`import Metrics`](../CoreMetrics/index.html). The most important types are:
* [`Counter`](https://apple.github.io/swift-metrics/docs/current/CoreMetrics/Classes/Counter.html)
* [`Timer`](https://apple.github.io/swift-metrics/docs/current/CoreMetrics/Classes/Timer.html)
* [`Recorder`](https://apple.github.io/swift-metrics/docs/current/CoreMetrics/Classes/Recorder.html)
* [`Gauge`](https://apple.github.io/swift-metrics/docs/current/CoreMetrics/Classes/Gauge.html)
SwiftMetrics contains multiple modules:
EOF
for module in "${modules[@]}"; do
echo " - [$module](../$module/index.html)" >> "$module_switcher"
done
for module in "${modules[@]}"; do
echo "processing $module"
args=("${jazzy_args[@]}" --output "$jazzy_dir/docs/$version/$module" --docset-path "$jazzy_dir/docset/$version/$module"
--module "$module" --module-version $version
--root-url "https://apple.github.io/swift-metrics/docs/$version/$module/")
if [[ -f "$root_path/.build/sourcekitten/$module.json" ]]; then
args+=(--sourcekitten-sourcefile "$root_path/.build/sourcekitten/$module.json")
fi
jazzy "${args[@]}"
done
# push to github pages
if [[ $PUSH == true ]]; then
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
GIT_AUTHOR=$(git --no-pager show -s --format='%an <%ae>' HEAD)
git fetch origin +gh-pages:gh-pages
git checkout gh-pages
rm -rf "docs/$version"
rm -rf "docs/current"
cp -r "$jazzy_dir/docs/$version" docs/
cp -r "docs/$version" docs/current
git add --all docs
echo '<html><head><meta http-equiv="refresh" content="0; url=docs/current/CoreMetrics/index.html" /></head></html>' > index.html
git add index.html
touch .nojekyll
git add .nojekyll
changes=$(git diff-index --name-only HEAD)
if [[ -n "$changes" ]]; then
echo -e "changes detected\n$changes"
git commit --author="$GIT_AUTHOR" -m "publish $version docs"
git push origin gh-pages
else
echo "no changes detected"
fi
git checkout -f $BRANCH_NAME
fi

View File

@ -1,231 +0,0 @@
#!/usr/bin/env ruby
#
# process_test_files.rb
#
# Copyright 2016 Tony Stone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Created by Tony Stone on 5/4/16.
#
require 'getoptlong'
require 'fileutils'
require 'pathname'
include FileUtils
#
# This ruby script will auto generate LinuxMain.swift and the +XCTest.swift extension files for Swift Package Manager on Linux platforms.
#
# See https://github.com/apple/swift-corelibs-xctest/blob/master/Documentation/Linux.md
#
def header(fileName)
string = <<-eos
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// <FileName>
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
eos
string
.sub('<FileName>', File.basename(fileName))
.sub('<Date>', Time.now.to_s)
end
def createExtensionFile(fileName, classes)
extensionFile = fileName.sub! '.swift', '+XCTest.swift'
print 'Creating file: ' + extensionFile + "\n"
File.open(extensionFile, 'w') do |file|
file.write header(extensionFile)
file.write "\n"
for classArray in classes
file.write 'extension ' + classArray[0] + " {\n"
file.write ' static var allTests: [(String, (' + classArray[0] + ") -> () throws -> Void)] {\n"
file.write " return [\n"
for funcName in classArray[1]
file.write ' ("' + funcName + '", ' + funcName + "),\n"
end
file.write " ]\n"
file.write " }\n"
file.write "}\n"
end
end
end
def createLinuxMain(testsDirectory, allTestSubDirectories, files)
fileName = testsDirectory + '/LinuxMain.swift'
print 'Creating file: ' + fileName + "\n"
File.open(fileName, 'w') do |file|
file.write header(fileName)
file.write "\n"
file.write "#if os(Linux) || os(FreeBSD)\n"
for testSubDirectory in allTestSubDirectories.sort { |x, y| x <=> y }
file.write '@testable import ' + testSubDirectory + "\n"
end
file.write "\n"
file.write "XCTMain([\n"
testCases = []
for classes in files
for classArray in classes
testCases << classArray[0]
end
end
for testCase in testCases.sort { |x, y| x <=> y }
file.write ' testCase(' + testCase + ".allTests),\n"
end
file.write "])\n"
file.write "#endif\n"
end
end
def parseSourceFile(fileName)
puts 'Parsing file: ' + fileName + "\n"
classes = []
currentClass = nil
inIfLinux = false
inElse = false
ignore = false
#
# Read the file line by line
# and parse to find the class
# names and func names
#
File.readlines(fileName).each do |line|
if inIfLinux
if /\#else/.match(line)
inElse = true
ignore = true
else
if /\#end/.match(line)
inElse = false
inIfLinux = false
ignore = false
end
end
else
if /\#if[ \t]+os\(Linux\)/.match(line)
inIfLinux = true
ignore = false
end
end
next if ignore
# Match class or func
match = line[/class[ \t]+[a-zA-Z0-9_]*(?=[ \t]*:[ \t]*XCTestCase)|func[ \t]+test[a-zA-Z0-9_]*(?=[ \t]*\(\))/, 0]
if match
if match[/class/, 0] == 'class'
className = match.sub(/^class[ \t]+/, '')
#
# Create a new class / func structure
# and add it to the classes array.
#
currentClass = [className, []]
classes << currentClass
else # Must be a func
funcName = match.sub(/^func[ \t]+/, '')
#
# Add each func name the the class / func
# structure created above.
#
currentClass[1] << funcName
end
end
end
classes
end
#
# Main routine
#
#
testsDirectory = 'Tests'
options = GetoptLong.new(['--tests-dir', GetoptLong::OPTIONAL_ARGUMENT])
options.quiet = true
begin
options.each do |option, value|
case option
when '--tests-dir'
testsDirectory = value
end
end
rescue GetoptLong::InvalidOption
end
allTestSubDirectories = []
allFiles = []
Dir[testsDirectory + '/*'].each do |subDirectory|
next unless File.directory?(subDirectory)
directoryHasClasses = false
Dir[subDirectory + '/*Test{s,}.swift'].each do |fileName|
next unless File.file? fileName
fileClasses = parseSourceFile(fileName)
#
# If there are classes in the
# test source file, create an extension
# file for it.
#
next unless fileClasses.count > 0
createExtensionFile(fileName, fileClasses)
directoryHasClasses = true
allFiles << fileClasses
end
if directoryHasClasses
allTestSubDirectories << Pathname.new(subDirectory).split.last.to_s
end
end
#
# Last step is the create a LinuxMain.swift file that
# references all the classes and funcs in the source files.
#
if allFiles.count > 0
createLinuxMain(testsDirectory, allTestSubDirectories, allFiles)
end
# 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,169 +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 linux tests... "
FIRST_OUT="$(git status --porcelain)"
ruby "$here/../scripts/generate_linux_tests.rb" > /dev/null
SECOND_OUT="$(git status --porcelain)"
if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then
printf "\033[0;31mmissing changes!\033[0m\n"
git --no-pager diff
exit 1
else
printf "\033[0;32mokay.\033[0m\n"
fi
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... "
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"