Skip to content

Commit 5a53a14

Browse files
committed
update readme
1 parent c56f6c2 commit 5a53a14

File tree

1 file changed

+19
-264
lines changed

1 file changed

+19
-264
lines changed

README.md

Lines changed: 19 additions & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -1,288 +1,43 @@
1-
# SwiftMetrics
1+
# SwiftMetricsExtras
22

3-
A Metrics API package for Swift.
3+
Extra packages complementing the core [SwiftMetrics](https://github.com/apple/swift-metrics) API.
44

55
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](https://prometheus.io/), [Graphite](https://graphiteapp.org), publish over [statsd](https://github.com/statsd/statsd), write to disk, etc.
66

77
This is the beginning of a community-driven open-source project actively seeking contributions, be it code, documentation, or ideas. Apart from contributing to SwiftMetrics itself, we need metrics compatible libraries which send the metrics over to backend such as the ones mentioned above. What SwiftMetrics provides today is covered in the [API docs](https://apple.github.io/swift-metrics/), but it will continue to evolve with community input.
88

9-
## Getting started
9+
## What makes a good contribution to Metrics Extras?
1010

11-
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.
11+
Not good:
1212

13-
### Adding the dependency
14-
15-
To add a dependency on the metrics API package, you need to declare it in your `Package.swift`:
16-
17-
```swift
18-
// swift-metrics 1.x and 2.x are almost API compatible, so most clients should use
19-
.package(url: "https://github.com/apple/swift-metrics.git", "1.0.0" ..< "3.0.0"),
20-
```
21-
22-
and to your application/library target, add "Metrics" to your dependencies:
23-
24-
```swift
25-
.target(name: "BestExampleApp", dependencies: ["Metrics"]),
26-
```
27-
28-
### Emitting metrics information
29-
30-
```swift
31-
// 1) let's import the metrics API package
32-
import Metrics
33-
34-
// 2) we need to create a concrete metric object, the label works similarly to a `DispatchQueue` label
35-
let counter = Counter(label: "com.example.BestExampleApp.numberOfRequests")
36-
37-
// 3) we're now ready to use it
38-
counter.increment()
39-
```
40-
41-
### Selecting a metrics backend implementation (applications only)
42-
43-
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.
44-
45-
SwiftMetrics 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.
46-
47-
Selecting a backend is done by adding a dependency on the desired backend client implementation and invoking the `MetricsSystem.bootstrap` function at the beginning of the program:
48-
49-
```swift
50-
MetricsSystem.bootstrap(SelectedMetricsImplementation())
51-
```
52-
53-
This instructs the `MetricsSystem` to install `SelectedMetricsImplementation` (actual name will differ) as the metrics backend to use.
54-
55-
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 SwiftMetrics API compatible libraries:
56-
57-
- [SwiftPrometheus](https://github.com/MrLotU/SwiftPrometheus), support for [Prometheus](https://prometheus.io)
58-
- [StatsD Client](https://github.com/apple/swift-statsd-client), support for StatsD
59-
- Your library? [Get in touch!](https://forums.swift.org/c/server)
60-
61-
## Detailed design
62-
63-
### Architecture
64-
65-
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 backends mentioned above or wherever else the application owner may choose.
66-
67-
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 to persist them. We think it's not feasible to wait for one metrics package to support everything that a specific deployment needs while still being simple enough to use and remain performant. That's why we decided to split the problem into two:
68-
69-
1. a metrics API
70-
2. a metrics backend implementation
71-
72-
This package only provides the metrics API itself, and therefore, SwiftMetrics is a "metrics API package." SwiftMetrics can be configured (using `MetricsSystem.bootstrap`) 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.
73-
74-
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.
75-
76-
[pitch](https://forums.swift.org/t/metrics/19353) |
77-
[discussion](https://forums.swift.org/t/discussion-server-metrics-api/) |
78-
[feedback](https://forums.swift.org/t/feedback-server-metrics-api/)
79-
80-
### Metric types
81-
82-
The API supports four metric types:
83-
84-
`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.
85-
86-
```swift
87-
counter.increment(by: 100)
88-
```
89-
90-
`Recorder`: 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.
91-
92-
```swift
93-
recorder.record(100)
94-
```
95-
96-
`Gauge`: A Gauge is a metric that represents a single numerical value that can arbitrarily go up and down. Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, like the number of active threads. Gauges are modeled as a `Recorder` with a sample size of 1 that does not perform any aggregation.
97-
98-
```swift
99-
gauge.record(100)
100-
```
101-
102-
`Timer`: A timer collects observations within a time window (usually things like request duration) 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.
103-
104-
```swift
105-
timer.recordMilliseconds(100)
106-
```
107-
108-
### Implementing a metrics backend (e.g. Prometheus client library)
109-
110-
Note: Unless you need to implement a custom metrics backend, everything in this section is likely not relevant, so please feel free to skip.
111-
112-
As seen above, each constructor for `Counter`, `Timer`, `Recorder` and `Gauge` provides a metric object. This uncertainty obscures the selected metrics backend calling these constructors by design. _Each application_ can select and configure its desired backend. The application sets up the metrics backend it wishes to use. Configuring the metrics backend is straightforward:
113-
114-
```swift
115-
let metricsImplementation = MyFavoriteMetricsImplementation()
116-
MetricsSystem.bootstrap(metricsImplementation)
117-
```
13+
- Most metrics contributions depend or implement some specific metrics backend–such implementations should have their own repository and are not good candidates for this repository.
11814

119-
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.
15+
Good:
12016

121-
Given the above, an implementation of a metric backend needs to conform to `protocol MetricsFactory`:
17+
- However, if you have some useful metrics helpers, such as e.g. gathering cloud provider specific metrics independent of actual metrics backend which they would be emitted to,
18+
or other such metric system agnostic metrics additions–such additions are perfect examples of contributions very welcome to this package.
12219

123-
```swift
124-
public protocol MetricsFactory {
125-
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler
126-
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler
127-
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler
128-
129-
func destroyCounter(_ handler: CounterHandler)
130-
func destroyRecorder(_ handler: RecorderHandler)
131-
func destroyTimer(_ handler: TimerHandler)
132-
}
133-
```
134-
135-
The `MetricsFactory` is responsible for instantiating the concrete metrics classes that capture the metrics and perform aggregation and calculation of various quantiles as needed.
136-
137-
**Counter**
138-
139-
```swift
140-
public protocol CounterHandler: AnyObject {
141-
func increment(by: Int64)
142-
func reset()
143-
}
144-
```
145-
146-
**Timer**
147-
148-
```swift
149-
public protocol TimerHandler: AnyObject {
150-
func recordNanoseconds(_ duration: Int64)
151-
}
152-
```
20+
### Adding the dependency
15321

154-
**Recorder**
22+
To add a dependency on the extras package, you need to declare it in your `Package.swift`:
15523

15624
```swift
157-
public protocol RecorderHandler: AnyObject {
158-
func record(_ value: Int64)
159-
func record(_ value: Double)
160-
}
25+
.package(url: "https://github.com/apple/swift-metrics-metrics.git", "1.0.0" ..< "3.0.0"),
16126
```
16227

163-
#### Dealing with Overflows
164-
165-
Implementaton of metric objects that deal with integers, like `Counter` and `Timer` should be careful with overflow. The expected behavior is to cap at `.max`, and never crash the program due to overflow . For example:
28+
and to your application/library target, add the specific module you would like to depend on to your dependencies:
16629

16730
```swift
168-
class ExampleCounter: CounterHandler {
169-
var value: Int64 = 0
170-
func increment(by amount: Int64) {
171-
let result = self.value.addingReportingOverflow(amount)
172-
if result.overflow {
173-
self.value = Int64.max
174-
} else {
175-
self.value = result.partialValue
176-
}
177-
}
178-
}
31+
.target(name: "BestExampleApp", dependencies: ["ExampleExtraMetrics"]),
17932
```
18033

181-
#### Full example
182-
183-
Here is a full, but contrived, example of an in-memory implementation:
184-
185-
```swift
186-
class SimpleMetricsLibrary: MetricsFactory {
187-
init() {}
188-
189-
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
190-
return ExampleCounter(label, dimensions)
191-
}
192-
193-
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
194-
let maker: (String, [(String, String)]) -> RecorderHandler = aggregate ? ExampleRecorder.init : ExampleGauge.init
195-
return maker(label, dimensions)
196-
}
197-
198-
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
199-
return ExampleTimer(label, dimensions)
200-
}
201-
202-
// implementation is stateless, so nothing to do on destroy calls
203-
func destroyCounter(_ handler: CounterHandler) {}
204-
func destroyRecorder(_ handler: RecorderHandler) {}
205-
func destroyTimer(_ handler: TimerHandler) {}
206-
207-
private class ExampleCounter: CounterHandler {
208-
init(_: String, _: [(String, String)]) {}
34+
## Modules
20935

210-
let lock = NSLock()
211-
var value: Int64 = 0
212-
func increment(by amount: Int64) {
213-
self.lock.withLock {
214-
self.value += amount
215-
}
216-
}
36+
Swift Metrics Extras ships the following extra modules:
21737

218-
func reset() {
219-
self.lock.withLock {
220-
self.value = 0
221-
}
222-
}
223-
}
38+
- [System Metrics](#system-metrics)
39+
- ...
22440

225-
private class ExampleRecorder: RecorderHandler {
226-
init(_: String, _: [(String, String)]) {}
227-
228-
private let lock = NSLock()
229-
var values = [(Int64, Double)]()
230-
func record(_ value: Int64) {
231-
self.record(Double(value))
232-
}
233-
234-
func record(_ value: Double) {
235-
// TODO: sliding window
236-
lock.withLock {
237-
values.append((Date().nanoSince1970, value))
238-
self._count += 1
239-
self._sum += value
240-
self._min = Swift.min(self._min, value)
241-
self._max = Swift.max(self._max, value)
242-
}
243-
}
244-
245-
var _sum: Double = 0
246-
var sum: Double {
247-
return self.lock.withLock { _sum }
248-
}
249-
250-
private var _count: Int = 0
251-
var count: Int {
252-
return self.lock.withLock { _count }
253-
}
254-
255-
private var _min: Double = 0
256-
var min: Double {
257-
return self.lock.withLock { _min }
258-
}
259-
260-
private var _max: Double = 0
261-
var max: Double {
262-
return self.lock.withLock { _max }
263-
}
264-
}
265-
266-
private class ExampleGauge: RecorderHandler {
267-
init(_: String, _: [(String, String)]) {}
268-
269-
let lock = NSLock()
270-
var _value: Double = 0
271-
func record(_ value: Int64) {
272-
self.record(Double(value))
273-
}
274-
275-
func record(_ value: Double) {
276-
self.lock.withLock { _value = value }
277-
}
278-
}
279-
280-
private class ExampleTimer: ExampleRecorder, TimerHandler {
281-
func recordNanoseconds(_ duration: Int64) {
282-
super.record(duration)
283-
}
284-
}
285-
}
286-
```
41+
### System Metrics
28742

288-
Do not hesitate to get in touch as well, over on https://forums.swift.org/c/server
43+
TODO: some docs about the system metrics.

0 commit comments

Comments
 (0)