prepare to release (#1)
* prepare to release motivation: the sswg voted to adopt the API. this is to prepare to a release changes: * rewrite readme * add API docs * add utilitiy scripts and docker setup for CI * adjust linux tests
This commit is contained in:
parent
5c1c739969
commit
585a41d684
|
|
@ -3,3 +3,4 @@
|
|||
/Packages
|
||||
/*.xcodeproj
|
||||
.xcode
|
||||
.SourceKitten
|
||||
|
|
|
|||
2
.mailmap
2
.mailmap
|
|
@ -1,2 +1,2 @@
|
|||
Tomer Doron <tomer@apple.com> <tomerd@apple.com> <tomer.doron@gmail.com>
|
||||
tomer doron <tomer@apple.com> <tomerd@apple.com> <tomer.doron@gmail.com>
|
||||
Konrad `ktoso` Malawski <ktoso@apple.com> <konrad.malawski@project13.pl>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ needs to be listed here.
|
|||
### Contributors
|
||||
|
||||
- Cory Benfield <lukasa@apple.com>
|
||||
- Jari (LotU) <j.koopman@jarict.nl>
|
||||
- Konrad `ktoso` Malawski <ktoso@apple.com>
|
||||
- Tomer Doron <tomer@apple.com>
|
||||
- tomer doron <tomer@apple.com>
|
||||
|
||||
**Updating this list**
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ let package = Package(
|
|||
name: "Metrics",
|
||||
dependencies: ["CoreMetrics"]
|
||||
),
|
||||
.target(
|
||||
name: "Examples",
|
||||
dependencies: ["Metrics"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "MetricsTests",
|
||||
dependencies: ["Metrics"]
|
||||
|
|
|
|||
130
README.md
130
README.md
|
|
@ -1,21 +1,80 @@
|
|||
# SSWG Metrics api
|
||||
# swift-metrics
|
||||
|
||||
* Proposal: SSWG-xxxx
|
||||
* Authors: [Tomer Doron](https://github.com/tomerd)
|
||||
* Status: **Implemented**
|
||||
* Pitch: [Server: Pitches/Metrics](https://forums.swift.org/t/metrics)
|
||||
A Metrics API package for Swift.
|
||||
|
||||
## Introduction
|
||||
Almost all production server software needs to emit metrics information for observability. Because it's unlikely that all parties can agree on one specific metrics backend implementation, this API is designed to establish a standard that can be implemented by various metrics libraries which then post the metrics data to backends like [Prometheus](http://prometheus.io/), [Grafana](http://grafana.com/), publish over [statsd](https://github.com/statsd/statsd), write to disk, etc.
|
||||
|
||||
Almost all production server software needs to emit metrics information for observability. The SSWG aims to provide a number of packages that can be shared across the whole Swift on Server ecosystem so we need some amount of standardisation. Because it's unlikely that all parties can agree on one full metrics implementation, this proposal is attempting to establish a metrics API that can be implemented by various metrics backends which then post the metrics data to backends like prometheus, graphite, publish over statsd, write to disk, etc.
|
||||
This is the beginning of a community-driven open-source project actively seeking contributions, be it code, documentation, or ideas. Apart from contributing to swift-metrics itself, we need metrics compatible libraries which send the metrics over to backend such as the ones mentioned above.
|
||||
|
||||
## Motivation
|
||||
What swift-metrics provides today is covered in the [API docs](https://apple.github.io/swift-metrics/). At this moment, we have not tagged a version for swift-metrics, but we will do so soon.
|
||||
|
||||
As outlined above we should standardise on an API that if well adopted would allow application owners to mix and match libraries from different vendors with a consistent metrics solution.
|
||||
## Getting started
|
||||
|
||||
## Proposed solution
|
||||
If you have a server-side Swift application, or maybe a cross-platform (e.g. Linux, macOS) application or library, and you would like to emit metrics, targeting this metrics API package is a great idea. Below you'll find all you need to know to get started.
|
||||
|
||||
The proposed solution is to introduce the following types that encapsulate metrics data:
|
||||
### Adding the dependency
|
||||
|
||||
To add a dependency on the metrics API package, you need to declare it in your `Package.swift`:
|
||||
|
||||
```swift
|
||||
// it's early days here so we haven't tagged a version yet, but will soon
|
||||
.package(url: "https://github.com/apple/swift-metrics.git", .branch("master")),
|
||||
```
|
||||
|
||||
and to your application/library target, add "Metrics" to your dependencies:
|
||||
|
||||
```swift
|
||||
.target(name: "BestExampleApp", dependencies: ["Metrics"]),
|
||||
```
|
||||
|
||||
### Emitting metrics information
|
||||
|
||||
```swift
|
||||
// 1) let's import the metrics API package
|
||||
|
||||
import Metrics
|
||||
|
||||
// 2) we need to create a concrete metric object, the label works similarly to a `DispatchQueue` label
|
||||
let counter = Counter(label: "com.example.BestExampleApp.numberOfRequests")
|
||||
|
||||
// 3) we're now ready to use it
|
||||
counter.increment()
|
||||
```
|
||||
|
||||
### Selecting a metrics backend implementation (applications only)
|
||||
|
||||
Note: If you are building a library, you don't need to concern yourself with this section. It is the end users of your library (the applications) who will decide which metrics backend to use. Libraries should never change the metrics implementation as that is something owned by the application.
|
||||
|
||||
swift-metrics only provides the metrics system API. As an application owner, you need to select a metrics backend (such as the ones mentioned above) to make the metrics information useful.
|
||||
|
||||
Selecting a backend is done by adding a dependency on the desired backend client implementation and invoking `MetricsSystem.bootstrap(SelectedMetricsImplementation.init)` at the beginning of the program. This instructs the `MetricsSystem` to install `SelectedMetricsImplementation` (actual name will differ) as the metrics backend to use.
|
||||
|
||||
As the API has just launched, not many implementations exist yet. If you are interested in implementing one see the "Implementing a metrics backend" section below explaining how to do so. List of existing swift-metrics API compatible libraries:
|
||||
|
||||
- Your library? [Get in touch!](https://forums.swift.org/c/server)
|
||||
|
||||
## Detailed design
|
||||
|
||||
### Architecture
|
||||
|
||||
We believe that for the Swift on Server ecosystem, it's crucial to have a metrics API that can be adopted by anybody so a multitude of libraries from different parties can all provide metrics information. More concretely this means that we believe all the metrics events from all libraries should end up in the same place, be one of the backend mentioned above or wherever else the application owner may choose.
|
||||
|
||||
In the real-world there are so many opinions over how exactly a metrics system should behave, how metrics should be aggregated and calculated, and where/how they should be persisted. We think it's not feasible to wait for one metrics package to support everything that a specific deployment needs whilst still being easy enough to use and remain performant. That's why we decided to split the problem into two:
|
||||
|
||||
1. a metrics API
|
||||
2. a metrics backend implementation
|
||||
|
||||
This package only provides the metrics API itself and therefore swift-metrics is a "metrics API package". swift-metrics (using `MetricsSystem.bootstrap`) can be configured to choose any compatible metrics backend implementation. This way packages can adopt the API and the application can choose any compatible metrics backend implementation without requiring any changes from any of the libraries.
|
||||
|
||||
This API was designed with the contributors to the Swift on Server community and approved by the SSWG (Swift Server Work Group) to the "sandbox level" of the SSWG's incubation process.
|
||||
|
||||
[pitch](https://forums.swift.org/t/metrics/19353) |
|
||||
[discussion](https://forums.swift.org/t/discussion-server-metrics-api/) |
|
||||
[feedback](https://forums.swift.org/t/feedback-server-metrics-api/)
|
||||
|
||||
### Metric types
|
||||
|
||||
The API supports four 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.
|
||||
|
||||
|
|
@ -41,36 +100,17 @@ gauge.record(100)
|
|||
timer.recordMilliseconds(100)
|
||||
```
|
||||
|
||||
How would you use `counter`, `recorder`, `gauge` and `timer` in you application or library? Here is a contrived example for request processing code that emits metrics for: total request count per url, request size and duration and response size:
|
||||
### Implementing a metrics backend (e.g. Prometheus client library)
|
||||
|
||||
Note: Unless you need to implement a custom metrics backend, everything in this section is likely not relevant, so please feel free to skip.
|
||||
|
||||
As seen above, each of `Counter`, `Timer`, `Recorder` and `Gauge` constructors provides a metric object. This raises the question of which metrics backend is actually be used when calling these constructors? The answer is that it's configurable _per application_. The application sets up the metrics backend it wishes to use. Configuring the metrics backend is straightforward:
|
||||
|
||||
```swift
|
||||
func processRequest(request: Request) -> Response {
|
||||
let requestCounter = Counter("request.count", ["url": request.url])
|
||||
let requestTimer = Timer("request.duration", ["url": request.url])
|
||||
let requestSizeRecorder = Recorder("request.size", ["url": request.url])
|
||||
let responseSizeRecorder = Recorder("response.size", ["url": request.url])
|
||||
|
||||
requestCounter.increment()
|
||||
requestSizeRecorder.record(request.size)
|
||||
|
||||
let start = Date()
|
||||
let response = ...
|
||||
requestTimer.record(Date().timeIntervalSince(start))
|
||||
responseSizeRecorder.record(response.size)
|
||||
}
|
||||
MetricsSystem.bootstrap(MyFavoriteMetricsImplementation.init)
|
||||
```
|
||||
|
||||
## Detailed design
|
||||
|
||||
### Implementing a metrics backend (e.g. prometheus client library)
|
||||
|
||||
As seen above, the constructors `Counter`, `Timer`, `Recorder` and `Gauge` provides a metric object. This raises the question of what metrics backend I will actually get when calling these constructors? The answer is that it's configurable _per application_. The application sets up the metrics backend it wishes the whole application to use. Libraries should never change the metrics implementation as that is something owned by the application. Configuring the metrics backend is straightforward:
|
||||
|
||||
```swift
|
||||
MetricsSystem.bootstrap(MyFavouriteMetricsImplementation.init)
|
||||
```
|
||||
|
||||
This instructs the `MetricsSystem` to install `MyFavouriteMetricsImplementation` as the metrics backend (`MetricsFactory`) to use. This should only be done once at the beginning of the program.
|
||||
This instructs the `MetricsSystem` to install `MyFavoriteMetricsImplementation` as the metrics backend (`MetricsFactory`) to use. This should only be done once at the beginning of the program.
|
||||
|
||||
Given the above, an implementation of a metric backend needs to conform to `protocol MetricsFactory`:
|
||||
|
||||
|
|
@ -209,18 +249,4 @@ class SimpleMetricsLibrary: MetricsFactory {
|
|||
}
|
||||
```
|
||||
|
||||
## State
|
||||
|
||||
This is an early proposal so there are still plenty of things to decide and tweak and I'd invite everybody to participate.
|
||||
|
||||
### Feedback Wishes
|
||||
|
||||
Feedback that would really be great is:
|
||||
|
||||
- if anything, what does this proposal *not cover* that you will definitely need
|
||||
- if anything, what could we remove from this and still be happy?
|
||||
- API-wise: what do you like, what don't you like?
|
||||
|
||||
Feel free to post this as message on the SSWG forum and/or github issues in this repo.
|
||||
|
||||
### Open Questions
|
||||
Do not hesitate to get in touch as well, over on https://forums.swift.org/c/server
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ internal final class Lock {
|
|||
|
||||
/// Release the lock.
|
||||
///
|
||||
/// Whenver possible, consider using `withLock` instead of this method and
|
||||
/// Whenever possible, consider using `withLock` instead of this method and
|
||||
/// `lock`, to simplify lock handling.
|
||||
public func unlock() {
|
||||
let err = pthread_mutex_unlock(self.mutex)
|
||||
|
|
@ -67,7 +67,7 @@ extension Lock {
|
|||
/// - Parameter body: The block to execute while holding the lock.
|
||||
/// - Returns: The value returned by the block.
|
||||
@inlinable
|
||||
public func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
internal func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
self.lock()
|
||||
defer {
|
||||
self.unlock()
|
||||
|
|
@ -77,7 +77,7 @@ extension Lock {
|
|||
|
||||
// specialise Void return (for performance)
|
||||
@inlinable
|
||||
public func withLockVoid(_ body: () throws -> Void) rethrows {
|
||||
internal func withLockVoid(_ body: () throws -> Void) rethrows {
|
||||
try self.withLock(body)
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ internal final class ReadWriteLock {
|
|||
|
||||
/// Release the lock.
|
||||
///
|
||||
/// Whenver possible, consider using `withLock` instead of this method and
|
||||
/// Whenever possible, consider using `withLock` instead of this method and
|
||||
/// `lock`, to simplify lock handling.
|
||||
public func unlock() {
|
||||
let err = pthread_rwlock_unlock(self.rwlock)
|
||||
|
|
@ -140,7 +140,7 @@ extension ReadWriteLock {
|
|||
/// - Parameter body: The block to execute while holding the lock.
|
||||
/// - Returns: The value returned by the block.
|
||||
@inlinable
|
||||
public func withReaderLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
internal func withReaderLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
self.lockRead()
|
||||
defer {
|
||||
self.unlock()
|
||||
|
|
@ -157,7 +157,7 @@ extension ReadWriteLock {
|
|||
/// - Parameter body: The block to execute while holding the lock.
|
||||
/// - Returns: The value returned by the block.
|
||||
@inlinable
|
||||
public func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
internal func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
self.lockWrite()
|
||||
defer {
|
||||
self.unlock()
|
||||
|
|
@ -167,13 +167,13 @@ extension ReadWriteLock {
|
|||
|
||||
// specialise Void return (for performance)
|
||||
@inlinable
|
||||
public func withReaderLockVoid(_ body: () throws -> Void) rethrows {
|
||||
internal func withReaderLockVoid(_ body: () throws -> Void) rethrows {
|
||||
try self.withReaderLock(body)
|
||||
}
|
||||
|
||||
// specialise Void return (for performance)
|
||||
@inlinable
|
||||
public func withWriterLockVoid(_ body: () throws -> Void) rethrows {
|
||||
internal func withWriterLockVoid(_ body: () throws -> Void) rethrows {
|
||||
try self.withWriterLock(body)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,37 +12,67 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// This is the Counter protocol a metrics library implements. It must have reference semantics
|
||||
/// A `CounterHandler` represents a backend implementation of a `Counter`.
|
||||
///
|
||||
/// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
|
||||
/// To use the swift-metrics API, please refer to the documentation of `Counter`.
|
||||
///
|
||||
/// # Implementation requirements
|
||||
///
|
||||
/// To implement your own `CounterHandler` you should respect a few requirements that are necessary so applications work
|
||||
/// as expected regardless of the selected `CounterHandler` implementation.
|
||||
///
|
||||
/// - The `CounterHandler` must be a `class`.
|
||||
public protocol CounterHandler: AnyObject {
|
||||
/// Increment the counter.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Amount to increment by.
|
||||
func increment(_ value: Int64)
|
||||
/// Reset the counter back to zero.
|
||||
func reset()
|
||||
}
|
||||
|
||||
// This is the user facing Counter API. Its behavior depends on the `CounterHandler` implementation
|
||||
/// A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero.
|
||||
/// For example, you can use a counter to represent the number of requests served, tasks completed, or errors.
|
||||
/// This is the user facing Counter API. Its behavior depends on the `CounterHandler` implementation
|
||||
public class Counter {
|
||||
@usableFromInline
|
||||
var handler: CounterHandler
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
// this method is public to provide an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason
|
||||
/// Create a new Counter.
|
||||
///
|
||||
/// This initializer provides an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
/// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Counter.
|
||||
/// - dimensions: The dimensions for the Counter.
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], handler: CounterHandler) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
/// Increment the counter.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Amount to increment by.
|
||||
@inlinable
|
||||
public func increment<DataType: BinaryInteger>(_ value: DataType) {
|
||||
self.handler.increment(Int64(value))
|
||||
}
|
||||
|
||||
/// Increment the counter by one.
|
||||
@inlinable
|
||||
public func increment() {
|
||||
self.increment(1)
|
||||
}
|
||||
|
||||
/// Reset the counter back to zero.
|
||||
@inlinable
|
||||
public func reset() {
|
||||
self.handler.reset()
|
||||
|
|
@ -50,19 +80,43 @@ public class Counter {
|
|||
}
|
||||
|
||||
public extension Counter {
|
||||
/// Create a new Counter.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Counter.
|
||||
/// - dimensions: The dimensions for the Counter.
|
||||
convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeCounter(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the Recorder protocol a metrics library implements. It must have reference semantics
|
||||
/// A `RecorderHandler` represents a backend implementation of a `Recorder`.
|
||||
///
|
||||
/// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
|
||||
/// To use the swift-metrics API, please refer to the documentation of `Recorder`.
|
||||
///
|
||||
/// # Implementation requirements
|
||||
///
|
||||
/// To implement your own `RecorderHandler` you should respect a few requirements that are necessary so applications work
|
||||
/// as expected regardless of the selected `RecorderHandler` implementation.
|
||||
///
|
||||
/// - The `RecorderHandler` must be a `class`.
|
||||
public protocol RecorderHandler: AnyObject {
|
||||
/// Record a value.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Value to record.
|
||||
func record(_ value: Int64)
|
||||
/// Record a value.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Value to record.
|
||||
func record(_ value: Double)
|
||||
}
|
||||
|
||||
// This is the user facing Recorder API. Its behavior depends on the `RecorderHandler` implementation
|
||||
/// A recorder collects observations within a time window (usually things like response sizes) and *can* provide aggregated information about the data sample, for example count, sum, min, max and various quantiles.
|
||||
/// This is the user facing Recorder API. Its behavior depends on the `RecorderHandler` implementation
|
||||
public class Recorder {
|
||||
@usableFromInline
|
||||
var handler: RecorderHandler
|
||||
|
|
@ -70,8 +124,15 @@ public class Recorder {
|
|||
public let dimensions: [(String, String)]
|
||||
public let aggregate: Bool
|
||||
|
||||
// this method is public to provide an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason
|
||||
/// Create a new Recorder.
|
||||
///
|
||||
/// This initializer provides an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
/// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Recorder.
|
||||
/// - dimensions: The dimensions for the Recorder.
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], aggregate: Bool, handler: RecorderHandler) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
|
|
@ -79,11 +140,19 @@ public class Recorder {
|
|||
self.handler = handler
|
||||
}
|
||||
|
||||
/// Record a value.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Value to record.
|
||||
@inlinable
|
||||
public func record<DataType: BinaryInteger>(_ value: DataType) {
|
||||
self.handler.record(Int64(value))
|
||||
}
|
||||
|
||||
/// Record a value.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Value to record.
|
||||
@inlinable
|
||||
public func record<DataType: BinaryFloatingPoint>(_ value: DataType) {
|
||||
self.handler.record(Double(value))
|
||||
|
|
@ -91,69 +160,132 @@ public class Recorder {
|
|||
}
|
||||
|
||||
public extension Recorder {
|
||||
/// Create a new Recorder.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Recorder.
|
||||
/// - dimensions: The dimensions for the Recorder.
|
||||
convenience init(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true) {
|
||||
let handler = MetricsSystem.factory.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
|
||||
self.init(label: label, dimensions: dimensions, aggregate: aggregate, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
// A Gauge is a convenience for non-aggregating Recorder
|
||||
/// 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 `Recorder` with a sample size of 1 and that does not perform any aggregation.
|
||||
public class Gauge: Recorder {
|
||||
/// Create a new Gauge.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Gauge.
|
||||
/// - dimensions: The dimensions for the Gauge.
|
||||
public convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
self.init(label: label, dimensions: dimensions, aggregate: false)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the Timer protocol a metrics library implements. It must have reference semantics
|
||||
/// A `TimerHandler` represents a backend implementation of a `Timer`.
|
||||
///
|
||||
/// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
|
||||
/// To use the swift-metrics API, please refer to the documentation of `Timer`.
|
||||
///
|
||||
/// # Implementation requirements
|
||||
///
|
||||
/// To implement your own `TimerHandler` you should respect a few requirements that are necessary so applications work
|
||||
/// as expected regardless of the selected `TimerHandler` implementation.
|
||||
///
|
||||
/// - The `TimerHandler` must be a `class`.
|
||||
public protocol TimerHandler: AnyObject {
|
||||
/// Record a duration in nanoseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
func recordNanoseconds(_ duration: Int64)
|
||||
}
|
||||
|
||||
// This is the user facing Timer API. Its behavior depends on the `TimerHandler` implementation
|
||||
/// A timer collects observations within a time window (usually things like request durations) and provides aggregated information about the data sample.
|
||||
/// For example min, max and various quantiles. It is similar to a `Recorder` but specialized for values that represent durations.
|
||||
/// This is the user facing Timer API. Its behavior depends on the `TimerHandler` implementation
|
||||
public class Timer {
|
||||
@usableFromInline
|
||||
var handler: TimerHandler
|
||||
public let label: String
|
||||
public let dimensions: [(String, String)]
|
||||
|
||||
// this method is public to provide an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason
|
||||
/// Create a new Timer.
|
||||
///
|
||||
/// This initializer provides an escape hatch for situations one must use a custom factory instead of the gloabl one
|
||||
/// we do not expect this API to be used in normal circumstances, so if you find yourself using it make sure its for a good reason.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Timer.
|
||||
/// - dimensions: The dimensions for the Timer.
|
||||
/// - handler: The custom backend.
|
||||
public init(label: String, dimensions: [(String, String)], handler: TimerHandler) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
/// Record a duration in nanoseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
@inlinable
|
||||
public func recordNanoseconds(_ duration: Int64) {
|
||||
self.handler.recordNanoseconds(duration)
|
||||
}
|
||||
|
||||
/// Record a duration in microseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
@inlinable
|
||||
public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration) * 1000)
|
||||
self.recordNanoseconds(Int64(duration * 1000))
|
||||
}
|
||||
|
||||
/// Record a duration in microseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
@inlinable
|
||||
public func recordMicroseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration * 1000))
|
||||
}
|
||||
|
||||
/// Record a duration in milliseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
@inlinable
|
||||
public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration) * 1_000_000)
|
||||
self.recordNanoseconds(Int64(duration * 1_000_000))
|
||||
}
|
||||
|
||||
/// Record a duration in milliseconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
@inlinable
|
||||
public func recordMilliseconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration * 1_000_000))
|
||||
}
|
||||
|
||||
/// Record a duration in seconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
@inlinable
|
||||
public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration) * 1_000_000_000)
|
||||
self.recordNanoseconds(Int64(duration * 1_000_000_000))
|
||||
}
|
||||
|
||||
/// Record a duration in seconds.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - value: Duration to record.
|
||||
@inlinable
|
||||
public func recordSeconds<DataType: BinaryFloatingPoint>(_ duration: DataType) {
|
||||
self.recordNanoseconds(Int64(duration * 1_000_000_000))
|
||||
|
|
@ -161,25 +293,61 @@ public class Timer {
|
|||
}
|
||||
|
||||
public extension Timer {
|
||||
/// Create a new Timer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Timer.
|
||||
/// - dimensions: The dimensions for the Timer.
|
||||
convenience init(label: String, dimensions: [(String, String)] = []) {
|
||||
let handler = MetricsSystem.factory.makeTimer(label: label, dimensions: dimensions)
|
||||
self.init(label: label, dimensions: dimensions, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `MetricsFactory` is the bridge between the `MetricsSystem` and the metrics backend implementation.
|
||||
/// `MetricsFactory` role is to initialize concrete implementations of the various metric types:
|
||||
/// * `Counter` -> `CounterHandler`
|
||||
/// * `Recorder` -> `RecorderHandler`
|
||||
/// * `Timer` -> `TimerHandler`
|
||||
///
|
||||
/// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
|
||||
/// To use the swift-metrics API, please refer to the documentation of `MetricsSystem`.
|
||||
public protocol MetricsFactory {
|
||||
/// Create a backing `CounterHandler`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the CounterHandler.
|
||||
/// - dimensions: The dimensions for the CounterHandler.
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler
|
||||
/// Create a backing `RecorderHandler`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the RecorderHandler.
|
||||
/// - dimensions: The dimensions for the RecorderHandler.
|
||||
/// - aggregate: Is data aggregation expected.
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler
|
||||
/// Create a backing `TimerHandler`.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the TimerHandler.
|
||||
/// - dimensions: The dimensions for the TimerHandler.
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler
|
||||
}
|
||||
|
||||
// This is the metrics system itself, it's mostly used set the type of the `MetricsFactory` implementation
|
||||
/// The `MetricsSystem` is a global facility where the default metrics backend implementation (`MetricsFactory`) can be
|
||||
/// configured. `MetricsSystem` is set up just once in a given program to set up the desired metrics backend
|
||||
/// implementation.
|
||||
public enum MetricsSystem {
|
||||
fileprivate static let lock = ReadWriteLock()
|
||||
fileprivate static var _factory: MetricsFactory = NOOPMetricsHandler.instance
|
||||
fileprivate static var initialized = false
|
||||
|
||||
// Configures which `LogHandler` to use in the application.
|
||||
/// `bootstrap` is a one-time configuration function which globally selects the desired metrics backend
|
||||
/// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will
|
||||
/// lead to undefined behaviour, most likely a crash.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - factory: A factory that given an identifier produces instances of metrics handlers such as `CounterHandler`, `RecorderHandler` and `TimerHandler`.
|
||||
public static func bootstrap(_ factory: MetricsFactory) {
|
||||
self.lock.withWriterLock {
|
||||
precondition(!self.initialized, "metrics system can only be initialized once per process. currently used factory: \(self.factory)")
|
||||
|
|
@ -195,12 +363,13 @@ public enum MetricsSystem {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a refernece to the configured factory.
|
||||
public static var factory: MetricsFactory {
|
||||
return self.lock.withReaderLock { self._factory }
|
||||
}
|
||||
}
|
||||
|
||||
/// Ships with the metrics module, used to multiplex to multiple metrics handlers
|
||||
/// A pseudo-metrics handler that can be used to send messages to multiple other metrics handlers.
|
||||
public final class MultiplexMetricsHandler: MetricsFactory {
|
||||
private let factories: [MetricsFactory]
|
||||
public init(factories: [MetricsFactory]) {
|
||||
|
|
@ -261,6 +430,7 @@ public final class MultiplexMetricsHandler: MetricsFactory {
|
|||
}
|
||||
}
|
||||
|
||||
/// Ships with the metrics module, used for initial bootstraping.
|
||||
public final class NOOPMetricsHandler: MetricsFactory, CounterHandler, RecorderHandler, TimerHandler {
|
||||
public static let instance = NOOPMetricsHandler()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,106 +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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// THIS IS NOT PART OF THE PITCH, JUST AN EXAMPLE
|
||||
//
|
||||
|
||||
import Metrics
|
||||
|
||||
enum Example1 {
|
||||
static func main() {
|
||||
// bootstrap with our example metrics library
|
||||
let metrics = ExampleMetricsLibrary()
|
||||
MetricsSystem.bootstrap(metrics)
|
||||
|
||||
let server = Server()
|
||||
let client = Client(server: server)
|
||||
client.run(iterations: Int.random(in: 10 ... 50))
|
||||
|
||||
print("-----> counters")
|
||||
metrics.counters.forEach { print(" \($0)") }
|
||||
print("-----> recorders")
|
||||
metrics.recorders.forEach { print(" \($0)") }
|
||||
print("-----> timers")
|
||||
metrics.timers.forEach { print(" \($0)") }
|
||||
print("-----> gauges")
|
||||
metrics.gauges.forEach { print(" \($0)") }
|
||||
}
|
||||
|
||||
class Client {
|
||||
private let activeRequestsGauge = Gauge(label: "Client::ActiveRequests")
|
||||
private let server: Server
|
||||
init(server: Server) {
|
||||
self.server = server
|
||||
}
|
||||
|
||||
func run(iterations: Int) {
|
||||
let group = DispatchGroup()
|
||||
let requestsCounter = Counter(label: "Client::TotalRequests")
|
||||
let requestTimer = Timer(label: "Client::doSomethig")
|
||||
let resultRecorder = Recorder(label: "Client::doSomethig::result")
|
||||
for _ in 0 ... iterations {
|
||||
group.enter()
|
||||
let start = Date()
|
||||
requestsCounter.increment()
|
||||
self.activeRequests += 1
|
||||
server.doSomethig { result in
|
||||
requestTimer.record(Date().timeIntervalSince(start))
|
||||
resultRecorder.record(result)
|
||||
self.activeRequests -= 1
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
group.wait()
|
||||
}
|
||||
|
||||
private let lock = NSLock()
|
||||
private var _activeRequests = 0
|
||||
var activeRequests: Int {
|
||||
get {
|
||||
return self.lock.withLock { _activeRequests }
|
||||
} set {
|
||||
self.lock.withLock { _activeRequests = newValue }
|
||||
self.activeRequestsGauge.record(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Server {
|
||||
let library = RandomLibrary()
|
||||
let requestsCounter = Counter(label: "Server::TotalRequests")
|
||||
|
||||
func doSomethig(callback: @escaping (Int64) -> Void) {
|
||||
let timer = Timer(label: "Server::doSomethig")
|
||||
let start = Date()
|
||||
requestsCounter.increment()
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(Int.random(in: 5 ... 500))) {
|
||||
self.library.doSomething()
|
||||
self.library.doSomethingSlow {
|
||||
timer.record(Date().timeIntervalSince(start))
|
||||
callback(Int64.random(in: 0 ... 1000))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension NSLock {
|
||||
func withLock<T>(_ body: () -> T) -> T {
|
||||
self.lock()
|
||||
defer {
|
||||
self.unlock()
|
||||
}
|
||||
return body()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,291 +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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// THIS IS NOT PART OF THE PITCH, JUST AN EXAMPLE
|
||||
//
|
||||
|
||||
import Metrics
|
||||
|
||||
class ExampleMetricsLibrary: MetricsFactory {
|
||||
private let config: Config
|
||||
private let lock = NSLock()
|
||||
var counters = [ExampleCounter]()
|
||||
var recorders = [ExampleRecorder]()
|
||||
var gauges = [ExampleGauge]()
|
||||
var timers = [ExampleTimer]()
|
||||
|
||||
init(config: Config = Config()) {
|
||||
self.config = config
|
||||
}
|
||||
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return self.register(label: label, dimensions: dimensions, registry: &self.counters, maker: ExampleCounter.init)
|
||||
}
|
||||
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
let options = aggregate ? self.config.recorder.aggregationOptions : nil
|
||||
return self.makeRecorder(label: label, dimensions: dimensions, options: options)
|
||||
}
|
||||
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], options: [AggregationOption]?) -> RecorderHandler {
|
||||
guard let options = options else {
|
||||
return self.register(label: label, dimensions: dimensions, registry: &self.gauges, maker: ExampleGauge.init)
|
||||
}
|
||||
let maker = { (label: String, dimensions: [(String, String)]) -> ExampleRecorder in
|
||||
ExampleRecorder(label: label, dimensions: dimensions, options: options)
|
||||
}
|
||||
return self.register(label: label, dimensions: dimensions, registry: &self.recorders, maker: maker)
|
||||
}
|
||||
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return self.makeTimer(label: label, dimensions: dimensions, options: self.config.timer.aggregationOptions)
|
||||
}
|
||||
|
||||
func makeTimer(label: String, dimensions: [(String, String)], options: [AggregationOption]) -> TimerHandler {
|
||||
let maker = { (label: String, dimensions: [(String, String)]) -> ExampleTimer in
|
||||
ExampleTimer(label: label, dimensions: dimensions, options: options)
|
||||
}
|
||||
return self.register(label: label, dimensions: dimensions, registry: &self.timers, maker: maker)
|
||||
}
|
||||
|
||||
func register<Item>(label: String, dimensions: [(String, String)], registry: inout [Item], maker: (String, [(String, String)]) -> Item) -> Item {
|
||||
let item = maker(label, dimensions)
|
||||
lock.withLock {
|
||||
registry.append(item)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
class Config {
|
||||
let recorder: RecorderConfig
|
||||
let timer: TimerConfig
|
||||
init(recorder: RecorderConfig = RecorderConfig(), timer: TimerConfig = TimerConfig()) {
|
||||
self.recorder = recorder
|
||||
self.timer = timer
|
||||
}
|
||||
}
|
||||
|
||||
class RecorderConfig {
|
||||
let aggregationOptions: [AggregationOption]
|
||||
init(aggregationOptions: [AggregationOption]) {
|
||||
self.aggregationOptions = aggregationOptions
|
||||
}
|
||||
|
||||
init() {
|
||||
self.aggregationOptions = AggregationOption.defaults
|
||||
}
|
||||
}
|
||||
|
||||
class TimerConfig {
|
||||
let aggregationOptions: [AggregationOption]
|
||||
init(aggregationOptions: [AggregationOption]) {
|
||||
self.aggregationOptions = aggregationOptions
|
||||
}
|
||||
|
||||
init() {
|
||||
self.aggregationOptions = AggregationOption.defaults
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleCounter: CounterHandler, CustomStringConvertible {
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
init(label: String, dimensions: [(String, String)]) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
}
|
||||
|
||||
let lock = NSLock()
|
||||
var value: Int64 = 0
|
||||
func increment<DataType: BinaryInteger>(_ value: DataType) {
|
||||
self.lock.withLock {
|
||||
self.value += Int64(value)
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
self.lock.withLock {
|
||||
self.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "counter [label: \(self.label) dimensions:\(self.dimensions) values:\(self.value)]"
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleRecorder: RecorderHandler, CustomStringConvertible {
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
let options: [AggregationOption]
|
||||
init(label: String, dimensions: [(String, String)], options: [AggregationOption]) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
self.options = options
|
||||
}
|
||||
|
||||
private let lock = NSLock()
|
||||
var values = [(Int64, Double)]()
|
||||
func record(_ value: Int64) {
|
||||
self.record(Double(value))
|
||||
}
|
||||
|
||||
func record(_ value: Double) {
|
||||
// TODO: sliding window
|
||||
self.lock.withLock {
|
||||
values.append((Date().nanoSince1970, value))
|
||||
}
|
||||
self.options.forEach { option in
|
||||
switch option {
|
||||
case .count:
|
||||
self.count += 1
|
||||
case .sum:
|
||||
self.sum += value
|
||||
case .min:
|
||||
self.min = Swift.min(self.min, value)
|
||||
case .max:
|
||||
self.max = Swift.max(self.max, value)
|
||||
case .quantiles(let items):
|
||||
self.computeQuantiles(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _sum: Double = 0
|
||||
var sum: Double {
|
||||
get {
|
||||
return self.lock.withLock { _sum }
|
||||
}
|
||||
set {
|
||||
self.lock.withLock { _sum = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
private var _count: Int = 0
|
||||
var count: Int {
|
||||
get {
|
||||
return self.lock.withLock { _count }
|
||||
}
|
||||
set {
|
||||
self.lock.withLock { _count = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
private var _min: Double = 0
|
||||
var min: Double {
|
||||
get {
|
||||
return self.lock.withLock { _min }
|
||||
}
|
||||
set {
|
||||
self.lock.withLock { _min = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
private var _max: Double = 0
|
||||
var max: Double {
|
||||
get {
|
||||
return self.lock.withLock { _max }
|
||||
}
|
||||
set {
|
||||
self.lock.withLock { _max = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
private var _quantiels = [Float: Double]()
|
||||
var quantiels: [Float: Double] {
|
||||
get {
|
||||
return self.lock.withLock { _quantiels }
|
||||
}
|
||||
set {
|
||||
self.lock.withLock { _quantiels = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "recorder [label: \(self.label) dimensions:\(self.dimensions) count:\(self.count) sum:\(self.sum) min:\(self.min) max:\(self.max) quantiels:\(self.quantiels) values:\(self.values)]"
|
||||
}
|
||||
|
||||
// TODO: offload calcs to queue
|
||||
private func computeQuantiles(_ items: [Float]) {
|
||||
self.lock.withLock {
|
||||
self._quantiels.removeAll()
|
||||
items.forEach { item in
|
||||
if let result = Sigma.quantiles.method1(self.values.map { $0.1 }, probability: Double(item)) {
|
||||
self._quantiels[item] = result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleGauge: RecorderHandler, CustomStringConvertible {
|
||||
let label: String
|
||||
let dimensions: [(String, String)]
|
||||
init(label: String, dimensions: [(String, String)]) {
|
||||
self.label = label
|
||||
self.dimensions = dimensions
|
||||
}
|
||||
|
||||
let lock = NSLock()
|
||||
var _value: Double = 0
|
||||
func record(_ value: Int64) {
|
||||
self.record(Double(value))
|
||||
}
|
||||
|
||||
func record(_ value: Double) {
|
||||
self.lock.withLock { _value = value }
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "gauge [label: \(self.label) dimensions:\(self.dimensions) value:\(self._value)]"
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleTimer: ExampleRecorder, TimerHandler {
|
||||
func recordNanoseconds(_ duration: Int64) {
|
||||
super.record(duration)
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "timer [label: \(self.label) dimensions:\(self.dimensions) count:\(self.count) sum:\(self.sum) min:\(self.min) max:\(self.max) quantiels:\(self.quantiels) values:\(self.values)]"
|
||||
}
|
||||
}
|
||||
|
||||
enum AggregationOption {
|
||||
case count
|
||||
case sum
|
||||
case min
|
||||
case max
|
||||
case quantiles(_ items: [Float])
|
||||
|
||||
public static let defaults: [AggregationOption] = [.count, .sum, .min, .max, .quantiles(defaultQuantiles)]
|
||||
public static let defaultQuantiles: [Float] = [0.25, 0.5, 0.75, 0.9, 0.95, 0.99]
|
||||
}
|
||||
|
||||
private extension Foundation.Date {
|
||||
var nanoSince1970: Int64 {
|
||||
return Int64(self.timeIntervalSince1970 * 1_000_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Foundation.NSLock {
|
||||
func withLock<T>(_ body: () -> T) -> T {
|
||||
self.lock()
|
||||
defer {
|
||||
self.unlock()
|
||||
}
|
||||
return body()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
//
|
||||
// THIS IS NOT PART OF THE PITCH, JUST AN EXAMPLE
|
||||
//
|
||||
|
||||
// copied from https://github.com/evgenyneu/SigmaSwiftStatistics/blob/master/SigmaSwiftStatistics/Quantiles.swift
|
||||
|
||||
//
|
||||
// Created by Alan James Salmoni on 21/12/2016.
|
||||
// Copyright © 2016 Thought Into Design Ltd. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum Sigma {
|
||||
/**
|
||||
|
||||
The class contains nine functions that calculate sample quantiles corresponding to the given probability. The implementation is the same as in R. This is an implementation of the algorithms described in the Hyndman and Fan paper, 1996:
|
||||
|
||||
https://www.jstor.org/stable/2684934
|
||||
https://www.amherst.edu/media/view/129116/original/Sample+Quantiles.pdf
|
||||
|
||||
The documentation of the functions is based on R and Wikipedia:
|
||||
|
||||
https://en.wikipedia.org/wiki/Quantile
|
||||
http://stat.ethz.ch/R-manual/R-devel/library/stats/html/quantile.html
|
||||
|
||||
*/
|
||||
public static let quantiles = SigmaQuantiles()
|
||||
}
|
||||
|
||||
public class SigmaQuantiles {
|
||||
/*
|
||||
|
||||
This method calculates quantiles using the inverse of the empirical distribution function.
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method1(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let k = Int((probability * count))
|
||||
let g = (probability * count) - Double(k)
|
||||
var new_probability = 1.0
|
||||
if g == 0.0 { new_probability = 0.0 }
|
||||
return self.qDef(data, k: k, probability: new_probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
This method uses inverted empirical distribution function with averaging.
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method2(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let k = Int(probability * count)
|
||||
let g = (probability * count) - Double(k)
|
||||
var new_probability = 1.0
|
||||
if g == 0.0 { new_probability = 0.5 }
|
||||
return self.qDef(data, k: k, probability: new_probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
The 3rd sample quantile method from Hyndman and Fan paper (1996).
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method3(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let m = -0.5
|
||||
let k = Int((probability * count) + m)
|
||||
let g = (probability * count) + m - Double(k)
|
||||
var new_probability = 1.0
|
||||
if g <= 0, k % 2 == 0 { new_probability = 0.0 }
|
||||
return self.qDef(data, k: k, probability: new_probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
It uses linear interpolation of the empirical distribution function.
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method4(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let m = 0.0
|
||||
let k = Int((probability * count) + m)
|
||||
let probability = (probability * count) + m - Double(k)
|
||||
return self.qDef(data, k: k, probability: probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
This method uses a piecewise linear function where the knots are the values midway through the steps of the empirical distribution function.
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method5(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let m = 0.5
|
||||
let k = Int((probability * count) + m)
|
||||
let probability = (probability * count) + m - Double(k)
|
||||
return self.qDef(data, k: k, probability: probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
This method is implemented in Microsoft Excel (PERCENTILE.EXC), Minitab and SPSS. It uses linear interpolation of the expectations for the order statistics for the uniform distribution on [0,1].
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method6(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let m = probability
|
||||
let k = Int((probability * count) + m)
|
||||
let probability = (probability * count) + m - Double(k)
|
||||
return self.qDef(data, k: k, probability: probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
This method is implemented in S, Microsoft Excel (PERCENTILE or PERCENTILE.INC) and Google Docs Sheets (PERCENTILE). It uses linear interpolation of the modes for the order statistics for the uniform distribution on [0, 1].
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method7(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let m = 1.0 - probability
|
||||
let k = Int((probability * count) + m)
|
||||
let probability = (probability * count) + m - Double(k)
|
||||
return self.qDef(data, k: k, probability: probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
The quantiles returned by the method are approximately median-unbiased regardless of the distribution of x.
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method8(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let m = (probability + 1.0) / 3.0
|
||||
let k = Int((probability * count) + m)
|
||||
let probability = (probability * count) + m - Double(k)
|
||||
return self.qDef(data, k: k, probability: probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
The quantiles returned by this method are approximately unbiased for the expected order statistics if x is normally distributed.
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
public func method9(_ data: [Double], probability: Double) -> Double? {
|
||||
if probability < 0 || probability > 1 { return nil }
|
||||
let data = data.sorted(by: <)
|
||||
let count = Double(data.count)
|
||||
let m = (0.25 * probability) + (3.0 / 8.0)
|
||||
let k = Int((probability * count) + m)
|
||||
let probability = (probability * count) + m - Double(k)
|
||||
return self.qDef(data, k: k, probability: probability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Shared function for all quantile methods.
|
||||
|
||||
- parameter data: Array of decimal numbers.
|
||||
- parameter k: the position of the element in the dataset.
|
||||
- parameter probability: the probability value between 0 and 1, inclusive.
|
||||
- returns: sample quantile.
|
||||
|
||||
*/
|
||||
private func qDef(_ data: [Double], k: Int, probability: Double) -> Double? {
|
||||
if data.isEmpty { return nil }
|
||||
if k < 1 { return data[0] }
|
||||
if k >= data.count { return data.last }
|
||||
return ((1.0 - probability) * data[k - 1]) + (probability * data[k])
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// THIS IS NOT PART OF THE PITCH, JUST AN EXAMPLE
|
||||
//
|
||||
|
||||
import Metrics
|
||||
|
||||
class RandomLibrary {
|
||||
let methodCallsCounter = Counter(label: "RandomLibrary::TotalMethodCalls")
|
||||
|
||||
func doSomething() {
|
||||
self.methodCallsCounter.increment()
|
||||
}
|
||||
|
||||
func doSomethingSlow(callback: @escaping () -> Void) {
|
||||
self.methodCallsCounter.increment()
|
||||
let timer = Timer(label: "RandomLibrary::doSomethingSlow")
|
||||
let start = Date()
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(Int.random(in: 5 ... 500))) {
|
||||
timer.record(Date().timeIntervalSince(start))
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,130 +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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// THIS IS NOT PART OF THE PITCH, JUST AN EXAMPLE
|
||||
//
|
||||
|
||||
import Metrics
|
||||
|
||||
class SimpleMetricsLibrary: MetricsFactory {
|
||||
init() {}
|
||||
|
||||
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
|
||||
return ExampleCounter(label, dimensions)
|
||||
}
|
||||
|
||||
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
|
||||
let maker: (String, [(String, String)]) -> RecorderHandler = aggregate ? ExampleRecorder.init : ExampleGauge.init
|
||||
return maker(label, dimensions)
|
||||
}
|
||||
|
||||
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
|
||||
return ExampleTimer(label, dimensions)
|
||||
}
|
||||
|
||||
private class ExampleCounter: CounterHandler {
|
||||
init(_: String, _: [(String, String)]) {}
|
||||
|
||||
let lock = NSLock()
|
||||
var value: Int64 = 0
|
||||
func increment(_ value: Int64) {
|
||||
self.lock.withLock {
|
||||
self.value += value
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
self.lock.withLock {
|
||||
self.value = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ExampleRecorder: RecorderHandler {
|
||||
init(_: String, _: [(String, String)]) {}
|
||||
|
||||
private let lock = NSLock()
|
||||
var values = [(Int64, Double)]()
|
||||
func record(_ value: Int64) {
|
||||
self.record(Double(value))
|
||||
}
|
||||
|
||||
func record(_ value: Double) {
|
||||
// TODO: sliding window
|
||||
self.lock.withLock {
|
||||
values.append((Date().nanoSince1970, value))
|
||||
self._count += 1
|
||||
self._sum += value
|
||||
self._min = Swift.min(self._min, value)
|
||||
self._max = Swift.max(self._max, value)
|
||||
}
|
||||
}
|
||||
|
||||
var _sum: Double = 0
|
||||
var sum: Double {
|
||||
return self.lock.withLock { _sum }
|
||||
}
|
||||
|
||||
private var _count: Int = 0
|
||||
var count: Int {
|
||||
return self.lock.withLock { _count }
|
||||
}
|
||||
|
||||
private var _min: Double = 0
|
||||
var min: Double {
|
||||
return self.lock.withLock { _min }
|
||||
}
|
||||
|
||||
private var _max: Double = 0
|
||||
var max: Double {
|
||||
return self.lock.withLock { _max }
|
||||
}
|
||||
}
|
||||
|
||||
private class ExampleGauge: RecorderHandler {
|
||||
init(_: String, _: [(String, String)]) {}
|
||||
|
||||
let lock = NSLock()
|
||||
var _value: Double = 0
|
||||
func record(_ value: Int64) {
|
||||
self.record(Double(value))
|
||||
}
|
||||
|
||||
func record(_ value: Double) {
|
||||
self.lock.withLock { _value = value }
|
||||
}
|
||||
}
|
||||
|
||||
private class ExampleTimer: ExampleRecorder, TimerHandler {
|
||||
func recordNanoseconds(_ duration: Int64) {
|
||||
super.record(duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Foundation.Date {
|
||||
var nanoSince1970: Int64 {
|
||||
return Int64(self.timeIntervalSince1970 * 1_000_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Foundation.NSLock {
|
||||
func withLock<T>(_ body: () -> T) -> T {
|
||||
self.lock()
|
||||
defer {
|
||||
self.unlock()
|
||||
}
|
||||
return body()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,28 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@_exported import CoreMetrics
|
||||
@_exported import class CoreMetrics.Timer
|
||||
@_exported import Foundation
|
||||
|
||||
// Convenience for measuring duration of a closure
|
||||
public extension Timer {
|
||||
/// Convenience for measuring duration of a closure.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - label: The label for the Timer.
|
||||
/// - dimensions: The dimensions for the Timer.
|
||||
/// - body: Closure to run & record.
|
||||
@inlinable
|
||||
static func measure<T>(label: String, dimensions: [(String, String)] = [], body: @escaping () throws -> T) rethrows -> T {
|
||||
let timer = Timer(label: label, dimensions: dimensions)
|
||||
|
|
@ -15,13 +34,20 @@ public extension Timer {
|
|||
}
|
||||
}
|
||||
|
||||
// Convenience for using Foundation and Dispatch
|
||||
public extension Timer {
|
||||
/// Convenience for recording a duration based on TimeInterval.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - duration: The duration to record.
|
||||
@inlinable
|
||||
func record(_ duration: TimeInterval) {
|
||||
self.recordSeconds(duration)
|
||||
}
|
||||
|
||||
/// Convenience for recording a duration based on DispatchTimeInterval.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - duration: The duration to record.
|
||||
@inlinable
|
||||
func record(_ duration: DispatchTimeInterval) {
|
||||
switch duration {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,21 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// THIS IS NOT PART OF THE PITCH, JUST AN EXAMPLE
|
||||
// LinuxMain.swift
|
||||
//
|
||||
import XCTest
|
||||
|
||||
print("##### example 1 #####")
|
||||
Example1.main()
|
||||
///
|
||||
/// 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
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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),
|
||||
("testRecorders", testRecorders),
|
||||
("testRecordersInt", testRecordersInt),
|
||||
("testRecordersFloat", testRecordersFloat),
|
||||
("testRecorderBlock", testRecorderBlock),
|
||||
("testTimers", testTimers),
|
||||
("testTimerBlock", testTimerBlock),
|
||||
("testTimerVariants", testTimerVariants),
|
||||
("testGauge", testGauge),
|
||||
("testGaugeBlock", testGaugeBlock),
|
||||
("testMUX", testMUX),
|
||||
("testCustomFactory", testCustomFactory),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,25 +56,43 @@ class MetricsExtensionsTests: XCTestCase {
|
|||
let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1 ... 500))
|
||||
timer.record(nano)
|
||||
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[0].1)), nano, "expected value to match")
|
||||
XCTAssertEqual(Int(testTimer.values[0].1), nano.nano(), "expected value to match")
|
||||
// micro
|
||||
let micro = DispatchTimeInterval.microseconds(Int.random(in: 1 ... 500))
|
||||
timer.record(micro)
|
||||
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[1].1)), micro, "expected value to match")
|
||||
XCTAssertEqual(Int(testTimer.values[1].1), micro.nano(), "expected value to match")
|
||||
// milli
|
||||
let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1 ... 500))
|
||||
timer.record(milli)
|
||||
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[2].1)), milli, "expected value to match")
|
||||
XCTAssertEqual(Int(testTimer.values[2].1), milli.nano(), "expected value to match")
|
||||
// seconds
|
||||
let sec = DispatchTimeInterval.seconds(Int.random(in: 1 ... 500))
|
||||
timer.record(sec)
|
||||
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
|
||||
XCTAssertEqual(.nanoseconds(Int(testTimer.values[3].1)), sec, "expected value to match")
|
||||
XCTAssertEqual(Int(testTimer.values[3].1), sec.nano(), "expected value to match")
|
||||
// never
|
||||
timer.record(DispatchTimeInterval.never)
|
||||
XCTAssertEqual(testTimer.values.count, 5, "expected number of entries to match")
|
||||
XCTAssertEqual(testTimer.values[4].1, 0, "expected value to match")
|
||||
}
|
||||
}
|
||||
|
||||
// https://bugs.swift.org/browse/SR-6310
|
||||
extension DispatchTimeInterval {
|
||||
func nano() -> Int {
|
||||
switch self {
|
||||
case .nanoseconds(let value):
|
||||
return value
|
||||
case .microseconds(let value):
|
||||
return value * 1000
|
||||
case .milliseconds(let value):
|
||||
return value * 1_000_000
|
||||
case .seconds(let value):
|
||||
return value * 1_000_000_000
|
||||
case .never:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
ARG ubuntu_version=18.04
|
||||
FROM ubuntu:$ubuntu_version
|
||||
# needed to do again after FROM due to docker limitation
|
||||
ARG ubuntu_version
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# do not start services during installation as this will fail and log a warning / error.
|
||||
RUN echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d
|
||||
|
||||
# basic dependencies
|
||||
RUN apt-get update && apt-get install -y wget git build-essential software-properties-common pkg-config locales
|
||||
RUN apt-get update && apt-get install -y libicu-dev libblocksruntime0 curl libcurl4-openssl-dev libz-dev
|
||||
|
||||
# local
|
||||
RUN locale-gen en_US.UTF-8
|
||||
RUN locale-gen en_US en_US.UTF-8
|
||||
RUN dpkg-reconfigure locales
|
||||
RUN echo 'export LANG=en_US.UTF-8' >> $HOME/.profile
|
||||
RUN echo 'export LANGUAGE=en_US:en' >> $HOME/.profile
|
||||
RUN echo 'export LC_ALL=en_US.UTF-8' >> $HOME/.profile
|
||||
|
||||
# known_hosts
|
||||
RUN mkdir -p $HOME/.ssh
|
||||
RUN touch $HOME/.ssh/known_hosts
|
||||
RUN ssh-keyscan github.com 2> /dev/null >> $HOME/.ssh/known_hosts
|
||||
|
||||
# clang
|
||||
RUN apt-get update && apt-get install -y clang-3.9
|
||||
RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-3.9 100
|
||||
RUN update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.9 100
|
||||
|
||||
# ruby and jazzy for docs generation
|
||||
#ARG skip_ruby_from_ppa
|
||||
#RUN [ -n "$skip_ruby_from_ppa" ] || apt-add-repository -y ppa:brightbox/ruby-ng
|
||||
#RUN [ -n "$skip_ruby_from_ppa" ] || { apt-get update && apt-get install -y ruby2.4 ruby2.4-dev; }
|
||||
#RUN [ -z "$skip_ruby_from_ppa" ] || { apt-get update && apt-get install -y ruby ruby-dev; }
|
||||
#RUN apt-get update && apt-get install -y libsqlite3-dev
|
||||
RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev
|
||||
RUN gem install jazzy --no-ri --no-rdoc
|
||||
|
||||
# swift
|
||||
ARG swift_version=4.2.3
|
||||
ARG swift_flavour=RELEASE
|
||||
ARG swift_builds_suffix=release
|
||||
|
||||
RUN mkdir $HOME/.swift
|
||||
RUN wget -q "https://swift.org/builds/swift-${swift_version}-${swift_builds_suffix}/ubuntu$(echo $ubuntu_version | sed 's/\.//g')/swift-${swift_version}-${swift_flavour}/swift-${swift_version}-${swift_flavour}-ubuntu${ubuntu_version}.tar.gz" -O $HOME/swift.tar.gz
|
||||
RUN tar xzf $HOME/swift.tar.gz --directory $HOME/.swift --strip-components=1
|
||||
RUN echo 'export PATH="$HOME/.swift/usr/bin:$PATH"' >> $HOME/.profile
|
||||
RUN echo 'export LINUX_SOURCEKIT_LIB_PATH="$HOME/.swift/usr/lib"' >> $HOME/.profile
|
||||
|
||||
# script to allow mapping framepointers on linux
|
||||
RUN mkdir -p $HOME/.scripts
|
||||
RUN wget -q https://raw.githubusercontent.com/apple/swift/master/utils/symbolicate-linux-fatal -O $HOME/.scripts/symbolicate-linux-fatal
|
||||
RUN chmod 755 $HOME/.scripts/symbolicate-linux-fatal
|
||||
RUN echo 'export PATH="$HOME/.scripts:$PATH"' >> $HOME/.profile
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
runtime-setup:
|
||||
image: swift-metrics:18.04-4.2
|
||||
build:
|
||||
args:
|
||||
ubuntu_version: "18.04"
|
||||
swift_version: "4.2"
|
||||
|
||||
test:
|
||||
image: swift-metrics:18.04-4.2
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
runtime-setup:
|
||||
image: swift-metrics:18.04-5.0
|
||||
build:
|
||||
args:
|
||||
ubuntu_version: "18.04"
|
||||
swift_version: "5.0"
|
||||
|
||||
test:
|
||||
image: swift-metrics:18.04-5.0
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# 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
|
||||
working_dir: /code
|
||||
|
||||
sanity:
|
||||
<<: *common
|
||||
command: /bin/bash -cl "./scripts/sanity.sh"
|
||||
|
||||
docs:
|
||||
<<: *common
|
||||
environment:
|
||||
- CI
|
||||
command: /bin/bash -cl "./scripts/generate_docs.sh"
|
||||
|
||||
test:
|
||||
<<: *common
|
||||
command: /bin/bash -cl "swift test"
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
##
|
||||
## This source file is part of the Swift Metrics API open source project
|
||||
##
|
||||
## Copyright (c) 2017-2018 Apple Inc. and the Swift Metrics API project authors
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
#!/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
|
||||
|
||||
tmp=`mktemp -d`
|
||||
mkdir -p "$tmp/docs/$version"
|
||||
module_switcher="$tmp/docs/$version/README.md"
|
||||
jazzy_args=(--clean
|
||||
--author 'swift-metrics team'
|
||||
--readme "$module_switcher"
|
||||
--author_url https://github.com/apple/swift-metrics
|
||||
--github_url https://github.com/apple/swift-metrics
|
||||
--theme fullwidth
|
||||
--xcodebuild-arguments -scheme,swift-metrics-Package)
|
||||
cat > "$module_switcher" <<"EOF"
|
||||
# swift-metrics docs
|
||||
|
||||
swift-metrics is a Swift metrics API package.
|
||||
|
||||
To get started with swift-metrics, [`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)
|
||||
|
||||
swift-metrics 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 "$tmp/docs/$version/$module" --docset-path "$tmp/docset/$version/$module" --module "$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 [[ $CI == 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"
|
||||
cp -r "$tmp/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
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
#!/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\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\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
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#!/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 )"
|
||||
|
||||
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 license headers... "
|
||||
tmp=$(mktemp /tmp/.swift-metrics-sanity_XXXXXX)
|
||||
|
||||
for language in swift-or-c bash dtrace; do
|
||||
declare -a matching_files
|
||||
declare -a exceptions
|
||||
expections=( )
|
||||
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)
|
||||
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) 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
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) 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
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) 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
|
||||
*
|
||||
*===----------------------------------------------------------------------===*/
|
||||
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" | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then
|
||||
printf "\033[0;31mmissing headers in file '$line'!\033[0m\n"
|
||||
diff -u <(cat "$line" | head -n $expected_lines) "$tmp"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
printf "\033[0;32mokay.\033[0m\n"
|
||||
)
|
||||
done
|
||||
|
||||
rm "$tmp"
|
||||
Loading…
Reference in New Issue