From 408a7a3e34a9a520a18b73331ad4ec1d52c9851a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 2 Aug 2022 20:53:11 +0900 Subject: [PATCH 1/2] =docc initial docc docs setup some initial docc preparation remove jazzy we need ruby still formatting docker cleanups some more docs for the types Apply suggestions from code review Co-authored-by: Yim Lee license fix --- .swiftformat | 2 +- Package.swift | 18 ++- Package@swift-5.0.swift | 41 +++++++ Package@swift-5.1.swift | 41 +++++++ Package@swift-5.2.swift | 48 ++++++++ Package@swift-5.3.swift | 48 ++++++++ Package@swift-5.4.swift | 48 ++++++++ Package@swift-5.5.swift | 48 ++++++++ README.md | 56 +-------- Sources/MetricsTestUtils/Docs.docc/index.md | 78 +++++++++++++ Sources/MetricsTestUtils/TestMetrics.swift | 53 +++++++-- Sources/SystemMetrics/Docs.docc/index.md | 58 ++++++++++ docker/Dockerfile | 5 +- docker/docker-compose.2004.55.yaml | 3 +- docker/docker-compose.2004.56.yaml | 19 +++ docker/docker-compose.2004.57.yaml | 18 +++ docker/docker-compose.2004.main.yaml | 3 +- docker/docker-compose.yaml | 8 +- scripts/docs/preview_docc.sh | 16 +++ scripts/generate_docs.sh | 122 -------------------- scripts/soundness.sh | 4 +- 21 files changed, 529 insertions(+), 208 deletions(-) create mode 100644 Package@swift-5.0.swift create mode 100644 Package@swift-5.1.swift create mode 100644 Package@swift-5.2.swift create mode 100644 Package@swift-5.3.swift create mode 100644 Package@swift-5.4.swift create mode 100644 Package@swift-5.5.swift create mode 100644 Sources/MetricsTestUtils/Docs.docc/index.md create mode 100644 Sources/SystemMetrics/Docs.docc/index.md create mode 100644 docker/docker-compose.2004.56.yaml create mode 100644 docker/docker-compose.2004.57.yaml create mode 100755 scripts/docs/preview_docc.sh delete mode 100755 scripts/generate_docs.sh diff --git a/.swiftformat b/.swiftformat index 53af637..8fc7e14 100644 --- a/.swiftformat +++ b/.swiftformat @@ -12,4 +12,4 @@ --extensionacl on-declarations --disable typeSugar -# rules +# rules \ No newline at end of file diff --git a/Package.swift b/Package.swift index 71274b7..2b6f12c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.6 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Metrics API open source project @@ -23,19 +23,29 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-metrics.git", from: "2.3.2"), + + // ~~~ SwiftPM Plugins ~~~ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ .target( name: "SystemMetrics", - dependencies: ["CoreMetrics"] + dependencies: [ + .product(name: "CoreMetrics", package: "swift-metrics"), + ] ), .target( name: "MetricsTestUtils", - dependencies: ["Metrics", "CoreMetrics"] + dependencies: [ + .product(name: "Metrics", package: "swift-metrics"), + .product(name: "CoreMetrics", package: "swift-metrics"), + ] ), .testTarget( name: "SystemMetricsTests", - dependencies: ["SystemMetrics"] + dependencies: [ + "SystemMetrics", + ] ), ] ) diff --git a/Package@swift-5.0.swift b/Package@swift-5.0.swift new file mode 100644 index 0000000..71274b7 --- /dev/null +++ b/Package@swift-5.0.swift @@ -0,0 +1,41 @@ +// 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-extras", + products: [ + .library(name: "SystemMetrics", targets: ["SystemMetrics"]), + .library(name: "MetricsTestUtils", targets: ["MetricsTestUtils"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-metrics.git", from: "2.3.2"), + ], + targets: [ + .target( + name: "SystemMetrics", + dependencies: ["CoreMetrics"] + ), + .target( + name: "MetricsTestUtils", + dependencies: ["Metrics", "CoreMetrics"] + ), + .testTarget( + name: "SystemMetricsTests", + dependencies: ["SystemMetrics"] + ), + ] +) diff --git a/Package@swift-5.1.swift b/Package@swift-5.1.swift new file mode 100644 index 0000000..02db34c --- /dev/null +++ b/Package@swift-5.1.swift @@ -0,0 +1,41 @@ +// 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-extras", + products: [ + .library(name: "SystemMetrics", targets: ["SystemMetrics"]), + .library(name: "MetricsTestUtils", targets: ["MetricsTestUtils"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-metrics.git", from: "2.3.2"), + ], + targets: [ + .target( + name: "SystemMetrics", + dependencies: ["CoreMetrics"] + ), + .target( + name: "MetricsTestUtils", + dependencies: ["Metrics", "CoreMetrics"] + ), + .testTarget( + name: "SystemMetricsTests", + dependencies: ["SystemMetrics"] + ), + ] +) diff --git a/Package@swift-5.2.swift b/Package@swift-5.2.swift new file mode 100644 index 0000000..9c689f4 --- /dev/null +++ b/Package@swift-5.2.swift @@ -0,0 +1,48 @@ +// 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-extras", + products: [ + .library(name: "SystemMetrics", targets: ["SystemMetrics"]), + .library(name: "MetricsTestUtils", targets: ["MetricsTestUtils"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-metrics.git", from: "2.3.2"), + ], + targets: [ + .target( + name: "SystemMetrics", + dependencies: [ + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .target( + name: "MetricsTestUtils", + dependencies: [ + .product(name: "Metrics", package: "swift-metrics"), + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .testTarget( + name: "SystemMetricsTests", + dependencies: [ + "SystemMetrics", + ] + ), + ] +) diff --git a/Package@swift-5.3.swift b/Package@swift-5.3.swift new file mode 100644 index 0000000..af1a465 --- /dev/null +++ b/Package@swift-5.3.swift @@ -0,0 +1,48 @@ +// 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-extras", + products: [ + .library(name: "SystemMetrics", targets: ["SystemMetrics"]), + .library(name: "MetricsTestUtils", targets: ["MetricsTestUtils"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-metrics.git", from: "2.3.2"), + ], + targets: [ + .target( + name: "SystemMetrics", + dependencies: [ + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .target( + name: "MetricsTestUtils", + dependencies: [ + .product(name: "Metrics", package: "swift-metrics"), + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .testTarget( + name: "SystemMetricsTests", + dependencies: [ + "SystemMetrics", + ] + ), + ] +) diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift new file mode 100644 index 0000000..0e8cfbe --- /dev/null +++ b/Package@swift-5.4.swift @@ -0,0 +1,48 @@ +// 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-extras", + products: [ + .library(name: "SystemMetrics", targets: ["SystemMetrics"]), + .library(name: "MetricsTestUtils", targets: ["MetricsTestUtils"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-metrics.git", from: "2.3.2"), + ], + targets: [ + .target( + name: "SystemMetrics", + dependencies: [ + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .target( + name: "MetricsTestUtils", + dependencies: [ + .product(name: "Metrics", package: "swift-metrics"), + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .testTarget( + name: "SystemMetricsTests", + dependencies: [ + "SystemMetrics", + ] + ), + ] +) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift new file mode 100644 index 0000000..5fa45b6 --- /dev/null +++ b/Package@swift-5.5.swift @@ -0,0 +1,48 @@ +// 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-extras", + products: [ + .library(name: "SystemMetrics", targets: ["SystemMetrics"]), + .library(name: "MetricsTestUtils", targets: ["MetricsTestUtils"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-metrics.git", from: "2.3.2"), + ], + targets: [ + .target( + name: "SystemMetrics", + dependencies: [ + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .target( + name: "MetricsTestUtils", + dependencies: [ + .product(name: "Metrics", package: "swift-metrics"), + .product(name: "CoreMetrics", package: "swift-metrics"), + ] + ), + .testTarget( + name: "SystemMetricsTests", + dependencies: [ + "SystemMetrics", + ] + ), + ] +) diff --git a/README.md b/README.md index dc9023d..90badaf 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ or other such metric system agnostic metrics additions–such additions are perf To add a dependency on the extras package, you need to declare it in your `Package.swift`: ```swift -.package(url: "https://github.com/apple/swift-metrics-extras.git", "1.0.0" ..< "2.0.0"), +.package(url: "https://github.com/apple/swift-metrics-extras.git", from: "0.1.0"), ``` and to your application/library target, add the specific module you would like to depend on to your dependencies: @@ -37,57 +37,3 @@ Swift Metrics Extras ships the following extra modules: - [System Metrics](Sources/SystemMetrics) - [MetricsTestUtils](Sources/MetricsTestUtils) - -### System Metrics - -The System Metrics package provides default process metrics for applications. The following metrics are exposed: - -- Virtual memory in Bytes. -- Resident memory in Bytes. -- Application start time in Seconds. -- Total CPU seconds. -- Maximum number of file descriptors. -- Number of file descriptors currently in use. - -***NOTE:*** Currently these metrics are only implemented on Linux platforms, and not on Darwin or Windows. - -#### Using System Metrics - -After [adding swift-metrics-extras as a dependency](#adding-the-dependency) you can import the `SystemMetrics` module. - -```swift -import SystemMetrics -``` - -This makes the System Metrics API available. This adds a new method to `MetricsSystem` called `bootstrapWithSystemMetrics`. Calling this method will call `MetricsSystem.bootstrap` as well as bootstrapping System Metrics. - -`bootstrapWithSystemMetrics` takes a `SystemMetrics.Configuration` object to configure the system metrics. The config has the following properties: - -- interval: The interval at which SystemMetrics are being calculated & exported. -- dataProvider: A closure returing `SystemMetrics.Data?`. When `nil` no metrics are exported (the default on non-linux platforms). `SystemMetrics.Data` holds all the values mentioned above. -- labels: `SystemMetrics.Labels` hold a string label for each of the above mentioned metrics that will be used for the metric labels, along with a prefix that will be used for all above mentioned metrics. - -Swift Metrics backend implementations are encouraged to provide static extensions to `SystemMetrics.Configuration` that fit the requirements of their specific backends. For example: -```swift -public extension SystemMetrics.Configuration { - /// `SystemMetrics.Configuration` with Prometheus style labels. - /// - /// For more information see `SystemMetrics.Configuration` - static let prometheus = SystemMetrics.Configuration( - labels: .init( - prefix: "process_", - virtualMemoryBytes: "virtual_memory_bytes", - residentMemoryBytes: "resident_memory_bytes", - startTimeSeconds: "start_time_seconds", - cpuSecondsTotal: "cpu_seconds_total", - maxFds: "max_fds", - openFds: "open_fds" - ) - ) -} -``` -This allows end users to add System Metrics like this: - -```swift -MetricsSystem.bootstrapWithSystemMetrics(myPrometheusInstance, config: .prometheus) -``` diff --git a/Sources/MetricsTestUtils/Docs.docc/index.md b/Sources/MetricsTestUtils/Docs.docc/index.md new file mode 100644 index 0000000..006bdf5 --- /dev/null +++ b/Sources/MetricsTestUtils/Docs.docc/index.md @@ -0,0 +1,78 @@ +# ``MetricsTestUtils`` + +The MetricsTestUtils module provides a metrics backend that can be used to test metrics emitted by your application. + +## Overview + +This module allows for writing assertions on existing metrics objects. +First, import the module in your XCTest (or other test runner): + +```swift +import XCTest +import Metrics +import MetricsTestUtils +``` + +Then bootstrap the metrics system in your test using the `bootstrapInternal` method which allows overriding existing bootstraps: + +```swift +final class SWIMNIOMetricsTests: XCTestCase { + var testMetrics: TestMetrics! + + override func setUp() { + super.setUp() + + self.testMetrics = TestMetrics() + MetricsSystem.bootstrapInternal(self.testMetrics) + } + + override func tearDown() { + super.tearDown() + + self.testMetrics.clear() + MetricsSystem.bootstrapInternal(NOOPMetricsHandler.instance) + } +} +``` + +After the test completes, remember to bootstrap with a `Metrics/NOOPMetricsHandler` again. + +### Asserting on metrics + +Next, you'll be able to run some code-under-test as usual, and then assert against metrics it has emitted like this: + +```swift +func test_example() throws { + let lib = SomeLibrary() + lib.doThings() + lib.causeMetrics() + + // Unwrap a Timer into a `TestTimer` + let roundTripTime = try! self.testMetrics.expectTimer(lib.metrics.someTimer) + + // Write assertions against the TestTimer + XCTAssertNotNil(roundTripTime.lastValue) // some roundtrip time should have been reported + for rtt in roundTripTime.values { + print(" ping rtt recorded: \(TimeAmount.nanoseconds(rtt).prettyDescription)") + } +} +``` + +The ``TestMetrics`` factory allows unwrapping any metrics type (`Timer`, `Counter`, etc), +into an equivalent ``TestTimer``, ``TestCounter``, ``TestRecorder``. + +Once such test metric has been obtained, you can inspect any metrics that were reported into it. +Most types offer a useful `lastValue` as well as a sequence of `values` which represents all +metric values reported into this metrics object. + +## Topics + +### Bootstrapping + +- ``TestMetrics`` + +### Test Metrics + +- ``TestCounter`` +- ``TestTimer`` +- ``TestRecorder`` diff --git a/Sources/MetricsTestUtils/TestMetrics.swift b/Sources/MetricsTestUtils/TestMetrics.swift index 9626c8f..a0603d7 100644 --- a/Sources/MetricsTestUtils/TestMetrics.swift +++ b/Sources/MetricsTestUtils/TestMetrics.swift @@ -29,14 +29,12 @@ import Metrics import XCTest -/// Taken directly from `swift-cluster-memberships`'s own test target package, which -/// adopts the `TestMetrics` from `swift-metrics`. +/// A custom `Metrics/MetricsFactory` that allows for later retrieval and +/// testing of created metrics objects. /// -/// Metrics factory which allows inspecting recorded metrics programmatically. -/// Only intended for tests of the Metrics API itself. -/// -/// Created Handlers will store Metrics until they are explicitly destroyed. +/// Created handlers will store Metrics until they are explicitly destroyed. /// +/// > Note: Original implementation taken from `swift-cluster-membership` and `swift-distributed-actors`. public final class TestMetrics: MetricsFactory { private let lock = NSLock() @@ -145,9 +143,9 @@ extension TestMetrics.FullKey: Hashable { extension TestMetrics { // ==== ------------------------------------------------------------------------------------------------------------ - // MARK: Counter + /// Assert that the passed in `metric` is a ``TestCounter`` and return it for further executing assertions. public func expectCounter(_ metric: Counter) throws -> TestCounter { guard let counter = metric._handler as? TestCounter else { throw TestMetricsError.illegalMetricType(metric: metric._handler, expected: "\(TestCounter.self)") @@ -155,6 +153,13 @@ extension TestMetrics { return counter } + /// Locate a ``TestCounter`` created by the ``TestMetrics`` factory identified by the passed in ``label`` and ``dimensions``, and return it for further executing assertions. + /// + /// - Parameters: + /// - label: the expected label the looked for metric should have + /// - dimensions: the expected dimensions the looked for metric should have + /// - Returns: the underlying ``TestCounter`` + /// - Throws: when no such test metric was present public func expectCounter(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestCounter { let maybeItem = self.lock.withLock { self.counters[.init(label: label, dimensions: dimensions)] @@ -169,21 +174,27 @@ extension TestMetrics { } // ==== ------------------------------------------------------------------------------------------------------------ - // MARK: Gauge + /// Assert that the passed in `metric` is a ``TestRecorder`` and return it for further executing assertions. public func expectGauge(_ metric: Gauge) throws -> TestRecorder { return try self.expectRecorder(metric) } - + /// Locate a ``TestRecorder`` created by the ``TestMetrics`` factory identified by the passed in ``label`` and ``dimensions``, and return it for further executing assertions. + /// + /// - Parameters: + /// - label: the expected label the looked for metric should have + /// - dimensions: the expected dimensions the looked for metric should have + /// - Returns: the underlying ``TestRecorder`` + /// - Throws: when no such test metric was present public func expectGauge(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestRecorder { return try self.expectRecorder(label, dimensions) } // ==== ------------------------------------------------------------------------------------------------------------ - // MARK: Recorder + /// Assert that the passed in `metric` is a ``TestRecorder`` and return it for further executing assertions. public func expectRecorder(_ metric: Recorder) throws -> TestRecorder { guard let recorder = metric._handler as? TestRecorder else { throw TestMetricsError.illegalMetricType(metric: metric._handler, expected: "\(TestRecorder.self)") @@ -191,6 +202,13 @@ extension TestMetrics { return recorder } + /// Locate a ``TestRecorder`` created by the ``TestMetrics`` factory identified by the passed in ``label`` and ``dimensions``, and return it for further executing assertions. + /// + /// - Parameters: + /// - label: the expected label the looked for metric should have + /// - dimensions: the expected dimensions the looked for metric should have + /// - Returns: the underlying ``TestRecorder`` + /// - Throws: when no such test metric was present public func expectRecorder(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestRecorder { let maybeItem = self.lock.withLock { self.recorders[.init(label: label, dimensions: dimensions)] @@ -205,9 +223,9 @@ extension TestMetrics { } // ==== ------------------------------------------------------------------------------------------------------------ - // MARK: Timer + /// Assert that the passed in `metric` is a ``TestTimer`` and return it for further executing assertions. public func expectTimer(_ metric: CoreMetrics.Timer) throws -> TestTimer { guard let timer = metric._handler as? TestTimer else { throw TestMetricsError.illegalMetricType(metric: metric._handler, expected: "\(TestTimer.self)") @@ -215,6 +233,13 @@ extension TestMetrics { return timer } + /// Locate a ``TestTimer`` created by the ``TestMetrics`` factory identified by the passed in ``label`` and ``dimensions``, and return it for further executing assertions. + /// + /// - Parameters: + /// - label: the expected label the looked for metric should have + /// - dimensions: the expected dimensions the looked for metric should have + /// - Returns: the underlying ``TestTimer`` + /// - Throws: when no such test metric was present public func expectTimer(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestTimer { let maybeItem = self.lock.withLock { self.timers[.init(label: label, dimensions: dimensions)] @@ -233,12 +258,18 @@ extension TestMetrics { // MARK: Metric type implementations +/// Common protocol for all test metrics, created by the ``TestMetrics`` metrics backend. public protocol TestMetric { associatedtype Value + /// Key used to identify a metric. var key: TestMetrics.FullKey { get } + /// Last metric value that was recorded into this metric. var lastValue: Value? { get } + + /// Sequence of pairs of values reported into this metric, as well as the `Date` at which the metric was emitted. + /// The sequence is ordered from oldest to latest, so you can e.g. assert a counter growing at an expected rate etc. var last: (Date, Value)? { get } } diff --git a/Sources/SystemMetrics/Docs.docc/index.md b/Sources/SystemMetrics/Docs.docc/index.md new file mode 100644 index 0000000..afd9e86 --- /dev/null +++ b/Sources/SystemMetrics/Docs.docc/index.md @@ -0,0 +1,58 @@ +# ``SystemMetrics`` + +The System Metrics module provides default process metrics for applications. + +## Overview + +The following metrics are exposed: + +- Virtual memory in Bytes. +- Resident memory in Bytes. +- Application start time in Seconds. +- Total CPU seconds. +- Maximum number of file descriptors. +- Number of file descriptors currently in use. + +> Note: Currently these metrics are only implemented on Linux platforms, and not on Darwin or Windows. + +## Using System Metrics + +After adding `swift-metrics-extras` as a dependency you can import the `SystemMetrics` module. + +```swift +import SystemMetrics +``` + +This makes the System Metrics API available. This adds a new method to `MetricsSystem` called `bootstrapWithSystemMetrics`. Calling this method will call `MetricsSystem.bootstrap` as well as bootstrapping System Metrics. + +`bootstrapWithSystemMetrics` takes a `SystemMetrics.Configuration` object to configure the system metrics. The config has the following properties: + +- interval: The interval at which `SystemMetrics` are being calculated & exported. +- dataProvider: A closure returning `SystemMetrics.Data?`. When `nil`, no metrics are exported (the default on non-Linux platforms). `SystemMetrics.Data` holds all the values mentioned above. +- labels: `SystemMetrics.Labels` hold a string label for each of the above mentioned metrics that will be used for the metric labels, along with a prefix that will be used for all above mentioned metrics. + +Swift Metrics backend implementations are encouraged to provide static extensions to `SystemMetrics.Configuration` that fit the requirements of their specific backends. For example: +```swift +public extension SystemMetrics.Configuration { + /// `SystemMetrics.Configuration` with Prometheus style labels. + /// + /// For more information see `SystemMetrics.Configuration` + static let prometheus = SystemMetrics.Configuration( + labels: .init( + prefix: "process_", + virtualMemoryBytes: "virtual_memory_bytes", + residentMemoryBytes: "resident_memory_bytes", + startTimeSeconds: "start_time_seconds", + cpuSecondsTotal: "cpu_seconds_total", + maxFds: "max_fds", + openFds: "open_fds" + ) + ) +} +``` + +This allows end users to add System Metrics like this: + +```swift +MetricsSystem.bootstrapWithSystemMetrics(myPrometheusInstance, config: .prometheus) +``` diff --git a/docker/Dockerfile b/docker/Dockerfile index 4452fc7..1b8e98c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,11 +16,8 @@ ENV LANGUAGE en_US.UTF-8 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 +# ruby RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev build-essential -# jazzy no longer works on xenial as ruby is too old. -RUN if [ "${ubuntu_version}" = "focal" ] ; then echo "gem: --no-document" > ~/.gemrc; fi -RUN if [ "${ubuntu_version}" = "focal" ] ; then gem install jazzy; fi # tools RUN mkdir -p $HOME/.tools diff --git a/docker/docker-compose.2004.55.yaml b/docker/docker-compose.2004.55.yaml index 4ffd5ee..6015f3c 100644 --- a/docker/docker-compose.2004.55.yaml +++ b/docker/docker-compose.2004.55.yaml @@ -11,7 +11,8 @@ services: test: image: swift-metrics:20.04-5.5 - environment: [] + environment: + - FORCE_TEST_DISCOVERY=--enable-test-discovery #- SANITIZER_ARG=--sanitize=thread shell: diff --git a/docker/docker-compose.2004.56.yaml b/docker/docker-compose.2004.56.yaml new file mode 100644 index 0000000..1d0baea --- /dev/null +++ b/docker/docker-compose.2004.56.yaml @@ -0,0 +1,19 @@ +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 diff --git a/docker/docker-compose.2004.57.yaml b/docker/docker-compose.2004.57.yaml new file mode 100644 index 0000000..dbcc67b --- /dev/null +++ b/docker/docker-compose.2004.57.yaml @@ -0,0 +1,18 @@ +version: "3" + +services: + + runtime-setup: + image: swift-metrics:20.04-5.7 + build: + args: + base_image: "swiftlang/swift:nightly-5.7-focal" + + test: + image: swift-metrics:20.04-5.7 + environment: + - FORCE_TEST_DISCOVERY=--enable-test-discovery + #- SANITIZER_ARG=--sanitize=thread + + shell: + image: swift-metrics:20.04-5.7 diff --git a/docker/docker-compose.2004.main.yaml b/docker/docker-compose.2004.main.yaml index 5eebdd9..c9252e5 100644 --- a/docker/docker-compose.2004.main.yaml +++ b/docker/docker-compose.2004.main.yaml @@ -11,7 +11,8 @@ services: test: image: swift-metrics:20.04-main - environment: [] + environment: + - FORCE_TEST_DISCOVERY=--enable-test-discovery #- SANITIZER_ARG=--sanitize=thread shell: diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 7bb11fc..fcfde07 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -26,15 +26,9 @@ services: <<: *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 $${SANITIZER_ARG-}" + command: /bin/bash -xcl "swift test -Xswiftc -warnings-as-errors $${FORCE_TEST_DISCOVERY-} $${SANITIZER_ARG-}" # util diff --git a/scripts/docs/preview_docc.sh b/scripts/docs/preview_docc.sh new file mode 100755 index 0000000..f027554 --- /dev/null +++ b/scripts/docs/preview_docc.sh @@ -0,0 +1,16 @@ +#!/bin/bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the Swift Metrics open source project +## +## Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.md for the list of Swift Metrics project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## + +xcrun swift package --disable-sandbox preview-documentation --target $1 diff --git a/scripts/generate_docs.sh b/scripts/generate_docs.sh deleted file mode 100755 index f25e4bf..0000000 --- a/scripts/generate_docs.sh +++ /dev/null @@ -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 - source_kitten_source_path="$root_path/.SourceKitten" - 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/x86_64-unknown-linux/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 - mkdir -p "$root_path/.build/sourcekitten" - for module in "${modules[@]}"; do - if [[ ! -f "$root_path/.build/sourcekitten/$module.json" ]]; then - "$source_kitten_path/sourcekitten" doc --spm-module $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 '' > 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 diff --git a/scripts/soundness.sh b/scripts/soundness.sh index e15df7c..52c943a 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -32,7 +32,7 @@ here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function replace_acceptable_years() { # this needs to replace all acceptable forms with 'YEARS' - sed -e 's/2018-2019/YEARS/' -e 's/2019/YEARS/' -e 's/2018-2020/YEARS/' -e 's/2021/YEARS/' + sed -e 's/2018-2019/YEARS/' -e 's/2019/YEARS/' -e 's/2018-2020/YEARS/' -e 's/2021/YEARS/' -e 's/2022/YEARS/' } printf "=> Checking linux tests... " @@ -88,7 +88,7 @@ for language in swift-or-c bash dtrace; do matching_files=( -name '*' ) case "$language" in swift-or-c) - exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h) + exceptions=( -name Package.swift -o -name Package@*.swift ) matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) cat > "$tmp" <<"EOF" //===----------------------------------------------------------------------===// From f6d000af068504dae88d2d5c0c70973786cbacab Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 23 Aug 2022 14:37:47 +0900 Subject: [PATCH 2/2] fix string termination with 0 --- Sources/MetricsTestUtils/TestMetrics.swift | 5 +++++ Sources/SystemMetrics/SystemMetrics.swift | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/MetricsTestUtils/TestMetrics.swift b/Sources/MetricsTestUtils/TestMetrics.swift index a0603d7..9c6f471 100644 --- a/Sources/MetricsTestUtils/TestMetrics.swift +++ b/Sources/MetricsTestUtils/TestMetrics.swift @@ -143,6 +143,7 @@ extension TestMetrics.FullKey: Hashable { extension TestMetrics { // ==== ------------------------------------------------------------------------------------------------------------ + // MARK: Counter /// Assert that the passed in `metric` is a ``TestCounter`` and return it for further executing assertions. @@ -174,12 +175,14 @@ extension TestMetrics { } // ==== ------------------------------------------------------------------------------------------------------------ + // MARK: Gauge /// Assert that the passed in `metric` is a ``TestRecorder`` and return it for further executing assertions. public func expectGauge(_ metric: Gauge) throws -> TestRecorder { return try self.expectRecorder(metric) } + /// Locate a ``TestRecorder`` created by the ``TestMetrics`` factory identified by the passed in ``label`` and ``dimensions``, and return it for further executing assertions. /// /// - Parameters: @@ -192,6 +195,7 @@ extension TestMetrics { } // ==== ------------------------------------------------------------------------------------------------------------ + // MARK: Recorder /// Assert that the passed in `metric` is a ``TestRecorder`` and return it for further executing assertions. @@ -223,6 +227,7 @@ extension TestMetrics { } // ==== ------------------------------------------------------------------------------------------------------------ + // MARK: Timer /// Assert that the passed in `metric` is a ``TestTimer`` and return it for further executing assertions. diff --git a/Sources/SystemMetrics/SystemMetrics.swift b/Sources/SystemMetrics/SystemMetrics.swift index 17a9277..5e47e4b 100644 --- a/Sources/SystemMetrics/SystemMetrics.swift +++ b/Sources/SystemMetrics/SystemMetrics.swift @@ -188,7 +188,9 @@ public enum SystemMetrics { #if os(Linux) internal static func linuxSystemMetrics() -> SystemMetrics.Data? { - class CFile { + /// Minimal file reading implementation so we don't have to depend on Foundation. + /// Designed only for the narrow use case of this library, reading `/proc/self/stat`. + final class CFile { let path: String private var file: UnsafeMutablePointer? @@ -221,7 +223,7 @@ public enum SystemMetrics { return nil } #if compiler(>=5.1) - let buff: [CChar] = Array(unsafeUninitializedCapacity: 1024) { ptr, size in + var buff: [CChar] = Array(unsafeUninitializedCapacity: 1024) { ptr, size in guard fgets(ptr.baseAddress, Int32(ptr.count), f) != nil else { if feof(f) != 0 { size = 0 @@ -230,9 +232,10 @@ public enum SystemMetrics { preconditionFailure("Error reading line") } } - size = strlen(ptr.baseAddress!) + size = strlen(ptr.baseAddress!) + 1 // the string + NULL to terminate it } if buff.isEmpty { return nil } + buff[buff.index(before: buff.endIndex)] = 0 // ensure the string is null-terminated return String(cString: buff) #else var buff = [CChar](repeating: 0, count: 1024)