Skip to content
Merged
8 changes: 4 additions & 4 deletions Examples/ServiceIntegration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ let systemMetricsMonitor = SystemMetricsMonitor(

This approach decouples the monitor from global state and allows you to use different metrics backends for different components.

## Running the Example
## Running the example

From the `swift-system-metrics` package root run:
From the `swift-system-metrics` package root, run:

```bash
docker-compose -f Examples/ServiceIntegration/docker-compose.yaml up --build
```

This will build and run 2 containers: `grafana` and `systemmetricsmonitor`.
The command builds and runs 2 containers: `grafana` and `systemmetricsmonitor`.

## Grafana Dashboard

Expand All @@ -42,6 +42,6 @@ The dashboard provides four visualizations that map to the metrics collected by

1. CPU Usage % calculated as `rate(process_cpu_seconds_total)`.

1. Residential Memory (`process_resident_memory_bytes`) and Virtual Memory (`process_virtual_memory_bytes`) consumption.
1. Resident Memory (`process_resident_memory_bytes`) and Virtual Memory (`process_virtual_memory_bytes`) consumption.

1. Open File Descriptors (`process_open_fds`) and Max File Descriptors (`process_max_fds`)
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Automatically collects process-level system metrics (memory, CPU, file descriptors) and reports them through the [SwiftMetrics](https://github.com/apple/swift-metrics) API.

## Collected Metrics
## Collected metrics

The following metrics are collected and reported as gauges:
The monitor collects and reports the following metrics as gauges:

- **Virtual Memory** (`process_virtual_memory_bytes`) - Virtual memory size in bytes
- **Resident Memory** (`process_resident_memory_bytes`) - Resident Set Size (RSS) in bytes
Expand Down Expand Up @@ -49,7 +49,7 @@ dependencies: [
]
```

Then add ``SystemMetrics`` to your target:
Then add `SystemMetrics` to your target:

```swift
.target(
Expand All @@ -60,9 +60,9 @@ Then add ``SystemMetrics`` to your target:
)
```

## Example & Grafana Dashboard
## Example and Grafana dashboard

A complete working example with a pre-built Grafana dashboard is available in [Examples/ServiceIntegration](Examples/ServiceIntegration). The example includes:
[Examples/ServiceIntegration](Examples/ServiceIntegration) provides a complete working example with a pre-built Grafana dashboard. The example includes:

- `SwiftServiceLifecycle` integration.
- `SwiftMetrics` configured to export the metrics.
Expand Down
74 changes: 74 additions & 0 deletions Sources/SystemMetrics/Docs.docc/GettingStarted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Capturing and reporting host system metrics

Use the system metrics monitor in your application to provide metrics from the host system where you run your service.

### Add the project dependency

Add `swift-system-metrics` as a dependency to your app and executable target:

```bash
swift package add-dependency https://github.com/apple/swift-system-metrics --from 1.0.0
```

```bash
swift package add-target-dependency SystemMetrics MyExecutableTarget --package swift-system-metrics
```

### Create a system monitor service

Import the `SystemMetrics` module, then create and add your monitor to a service group.

```swift
import SystemMetrics
// Import and create a logger, or use one of the existing loggers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Import and create a logger, or use one of the existing loggers

import Logging

let logger = Logger(label: "MyService")

// Create the monitor
let systemMetricsMonitor = SystemMetricsMonitor(logger: logger)
```

The monitor collects and reports metrics periodically using the global `MetricsSystem` that Swift Metrics provides.
You can configure the polling interval with your own ``SystemMetricsMonitor/Configuration`` when you create the monitor:

```swift
let systemMetricsMonitor = SystemMetricsMonitor(
configuration: .init(pollInterval: .seconds(5)),
logger: logger
Comment on lines +37 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
configuration: .init(pollInterval: .seconds(5)),
logger: logger
configuration: .init(pollInterval: .seconds(5)),
metricsFactory: myMetricsFactory,
logger: logger

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we want to promote the initializer with the custom factory as a baseline expectation? I got the impression that the one without would be more commonly used/desired here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested the change because the text above it mentions the factory. It was strange to mention it without showing the code.

But we can also remove it and also remove the mention in the text, either is fine.

)
```

### Run the service in your app

Use [Swift Service Lifecycle](https://github.com/swift-server/swift-service-lifecycle) to run the monitor as a background service with support for graceful shutdown and UNIX signal handling.
To do so, include the system metrics monitor you created into a service group and run the group in your application.

The following code bootstraps your own metrics backend, creates a system metrics monitor, and uses service lifecycle to run both:

```swift
import SystemMetrics
import ServiceLifecycle
import Metrics

@main
struct Application {
static func main() async throws {
let logger = Logger(label: "Application")
let metrics = MyMetricsBackendImplementation()
MetricsSystem.bootstrap(metrics)

let service = FooService()
let systemMetricsMonitor = SystemMetricsMonitor(logger: logger)

let serviceGroup = ServiceGroup(
services: [service, systemMetricsMonitor],
gracefulShutdownSignals: [.sigint],
cancellationSignals: [.sigterm],
logger: logger
)

try await serviceGroup.run()
}
}
```
13 changes: 7 additions & 6 deletions Sources/SystemMetrics/Docs.docc/Proposals/Proposals.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ Collaborate on API changes to Swift System Metrics by writing a proposal.

For non-trivial changes that affect the public API, the Swift System Metrics project adopts a lightweight version of the [Swift Evolution](https://github.com/apple/swift-evolution/blob/main/process.md) process.

Writing a proposal first helps discuss multiple possible solutions early, apply useful feedback from other contributors, and avoid reimplementing the same feature multiple times.
Writing a proposal first helps you discuss multiple possible solutions early, apply useful feedback from other contributors, and avoid reimplementing the same feature multiple times.

While it's encouraged to get feedback by opening a pull request with a proposal early in the process, it's also important to consider the complexity of the implementation when evaluating different solutions. For example, this might mean including a link to a branch containing a prototype implementation of the feature in the pull request description.
Get feedback early by opening a pull request with your proposal, but also consider the complexity of the implementation when evaluating different solutions. For example, include a link to a branch containing a prototype implementation of the feature in the pull request description.

> Note: The goal of this process is to help solicit feedback from the whole community around the project, and we will continue to refine the proposal process itself. Use your best judgement, and don't hesitate to propose changes to the proposal structure itself!
> Note: The goal of this process is to solicit feedback from the whole community around the project, and the project continues to refine the proposal process itself. Use your best judgment, and don't hesitate to propose changes to the proposal structure itself.

### Steps

1. Make sure there's a GitHub issue for the feature or change you would like to propose.
2. Duplicate the `SSM-NNNN.md` document and replace `NNNN` with the next available proposal number.
3. Link the GitHub issue from your proposal, and fill in the proposal.
4. Open a pull request with your proposal and solicit feedback from other contributors.
5. Once a maintainer confirms that the proposal is ready for review, the state is updated accordingly. The review period is 7 days, and ends when one of the maintainers marks the proposal as Ready for Implementation, or Deferred.
6. Before the pull request is merged, there should be an implementation ready, either in the same pull request, or a separate one, linked from the proposal.
7. The proposal is considered Approved once the implementation, proposal PRs have been merged, and, if originally disabled by a feature flag, feature flag enabled unconditionally.
5. Once a maintainer confirms that the proposal is ready for review, we update the state accordingly. The review period is 7 days, and ends when one of the maintainers marks the proposal as Ready for Implementation, or Deferred.
6. Before merging the pull request, ensure an implementation is ready, either in the same pull request or in a separate one linked from the proposal.
7. A proposal becomes Approved once you merge the implementation and proposal PRs, and enable any feature flags unconditionally.

If you have any questions, ask in an issue on GitHub.

Expand All @@ -37,3 +37,4 @@ If you have any questions, ask in an issue on GitHub.

- <doc:SSM-NNNN>
- <doc:SSM-0001>
- <doc:SSM-0002>
12 changes: 6 additions & 6 deletions Sources/SystemMetrics/Docs.docc/Proposals/SSM-NNNN.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ A short, one-sentence overview of the feature or change.

### Motivation

Describe the problems that this proposal aims to address, and what workarounds adopters have to employ currently, if any.
Describe the problems that this proposal addresses, and what workarounds adopters currently use, if any.

### Proposed solution

Describe your solution to the problem. Provide examples and describe how they work. Show how your solution is better than current workarounds.

This section should focus on what will change for the _adopters_ of Swift System Metrics.
Focus this section on what will change for the _adopters_ of Swift System Metrics.

### Detailed design

Describe the implementation of the feature, a link to a prototype implementation is encouraged here.
Describe the implementation of the feature. Include a link to a prototype implementation.

This section should focus on what will change for the _contributors_ to Swift System Metrics.
Focus this section on what will change for the _contributors_ to Swift System Metrics.

### API stability

Discuss the API implications, making sure to considering all of:
Discuss the API implications, making sure to consider all of:
- existing metrics data provider implementations
- existing users of `SystemMetrics`

Expand All @@ -46,4 +46,4 @@ Discuss any potential future improvements to the feature.

### Alternatives considered

Discuss the alternative solutions considered, even during the review process itself.
Discuss the alternative solutions you considered, even during the review process itself.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# ``SystemMetricsMonitor/Configuration``

## Topics

### Creating a monitor configuration

- ``init(pollInterval:)``
- ``default``

### Inspecting the configuration

- ``interval``
13 changes: 13 additions & 0 deletions Sources/SystemMetrics/Docs.docc/Reference/SystemMetricsMonitor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# ``SystemMetricsMonitor``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been meaning to ask - what's the docc command to generate these? Or did you hand-write them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol - I hand wrote these. There's not a DocC command to generate them one off, unfortunately- I've just got 'my pattern' of how I do it, which it mostly stub, see what show's up in preview, then iterate to drop the right pieces into place.

That said, there is a curation generation command - although I'm not certain if anyone actually uses it, as it's not detailed anywhere in the docs. The gist of which is:

  • create/get the symbols for the target/module
  • use docc process-catalog to emit a default catalog
mkdir .symbolgraphs
xcrun swift build --target MyProjectExample \
  -Xswiftc -emit-symbol-graph \
  -Xswiftc -emit-symbol-graph-dir \
  -Xswiftc .symbolgraphs
mkdir Sources/MyProjectExample/Documentation.docc

# better generation using --from-symbol
# which constrains the generated content to MyProjectExample
xcrun docc process-catalog \
  emit-generated-curation \
  Sources/MyProjectExample/Documentation.docc \
  --from-symbol MyProjectExample \
  --additional-symbol-graph-dir .symbolgraphs

So it's kind of a pain to use...


## Topics

### Creating a system metrics monitor

- ``init(configuration:logger:)``
- ``init(configuration:metricsFactory:logger:)``
- ``Configuration``

### Running a monitor

- ``run()``
106 changes: 15 additions & 91 deletions Sources/SystemMetrics/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,102 +4,26 @@ Collect and report process-level system metrics in your application.

## Overview

``SystemMetricsMonitor`` automatically collects key process metrics and reports them through the Swift Metrics API.
Create an instance of ``SystemMetricsMonitor`` to automatically collect key process metrics and report them through the Swift Metrics API.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should preserve the short code snippet, including the dependency snippets, here as well. We try to do that in most of our other packages, as that's what most people landing on this page want to see. We should still preserve the new, more detailed getting started guide, though.

So, I think this should show up on this page (the least customized way to run a system metrics monitor):

import SystemMetrics
import ServiceLifecycle
import Metrics
import Logging

@main
struct Application {
    static func main() async throws {
        let logger = Logger(label: "Application")
        let metrics = MyMetricsBackendImplementation()
        MetricsSystem.bootstrap(metrics)

        let service = FooService()
        let systemMetricsMonitor = SystemMetricsMonitor(logger: logger)

        let serviceGroup = ServiceGroup(
            services: [service, systemMetricsMonitor],
            gracefulShutdownSignals: [.sigint],
            cancellationSignals: [.sigterm],
            logger: logger
        )

        try await serviceGroup.run()
    }
}


### Available metrics
The monitor collects the following metrics:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a max window width the reported label is split into two lines, making it hard to read the label. Can a thing inside the ticks marked to be rendered without line breaks?

Image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can maybe move the constants into a nested bullet point under each metric? That way they'll always start on the same column.


The following metrics are collected:
- **Virtual Memory**: Total virtual memory, in bytes, that the process allocates. The monitor reports the metric as `process_virtual_memory_bytes`.
- **Resident Memory**: Physical memory, in bytes, that the process currently uses. The monitor reports the metric as `process_resident_memory_bytes`.
- **Start Time**: Process start time, in seconds, since UNIX epoch. The monitor reports the metric as `process_start_time_seconds`.
- **CPU Time**: Cumulative CPU time the process consumes, in seconds. The monitor reports the metric as `process_cpu_seconds_total`.
- **Max File Descriptors**: The maximum number of file descriptors the process can open. The monitor reports the metric as `process_max_fds`.
- **Open File Descriptors**: The number of file descriptors the process currently has open. The monitor reports the metric as `process_open_fds`.

- **Virtual Memory**: Total virtual memory allocated by the process (in bytes), reported as `process_virtual_memory_bytes`.
- **Resident Memory**: Physical memory currently used by the process (in bytes), reported as `process_resident_memory_bytes`.
- **Start Time**: Process start time since Unix epoch (in seconds), reported as `process_start_time_seconds`.
- **CPU Time**: Cumulative CPU time consumed (in seconds), reported as `process_cpu_seconds_total`.
- **Max File Descriptors**: Maximum number of file descriptors the process can open, reported as `process_max_fds`.
- **Open File Descriptors**: Number of file descriptors currently open, reported as `process_open_fds`.
> Note: The monitor supports these metrics only on Linux and macOS platforms.

> Note: These metrics are currently implemented on Linux and macOS platforms only.
## Topics

## Getting started
### Monitor system metrics
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Monitor system metrics
### Essentials

I think "Essentials" is the usual name of the first section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We see it commonly, but at least in the broader API guides, the last guidance I got here was essentials was meant to be "the things you critically had to know to be able to use the API", which I took as a placeholder to mean explanatory content prefixed into the getting started pieces when needed. This didn't quite seem to hit that bar for me, so I leaned into just giving a functional heading that made it reinforced what this was doing, especially since this is just a simple API surface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's fair - though I'd argue if you don't learn the SystemMetricMonitor API, then there isn't much you can do with the library. So IMO it does raise to the bar of being essential.


### Basic usage
- <doc:GettingStarted>
- ``SystemMetricsMonitor``

After adding `swift-system-metrics` as a dependency, import the `SystemMetrics` module:
### Contribute to the project

```swift
import SystemMetrics
```

Create and start a monitor with default settings and a logger:

```swift
// Import and create a logger, or use one of the existing loggers
import Logging
let logger = Logger(label: "MyService")

// Create the monitor
let monitor = SystemMetricsMonitor(logger: logger)

// Create the service
let serviceGroup = ServiceGroup(
services: [monitor],
gracefulShutdownSignals: [.sigint],
cancellationSignals: [.sigterm],
logger: logger
)

// Start collecting metrics
try await serviceGroup.run()
```

The monitor will collect and report metrics periodically using the global `MetricsSystem`.

## Configuration

Polling interval can be configured through the ``SystemMetricsMonitor/Configuration``:

```swift
let systemMetricsMonitor = SystemMetricsMonitor(
configuration: .init(pollInterval: .seconds(5)),
logger: logger
)
```

## Using custom Metrics Factory

``SystemMetricsMonitor`` can be initialized with a specific metrics factory, so it does not rely on the global `MetricsSystem`:

```swift
let monitor = SystemMetricsMonitor(metricsFactory: myPrometheusMetricsFactory, logger: logger)
```

## Swift Service Lifecycle integration

[Swift Service Lifecycle](https://github.com/swift-server/swift-service-lifecycle) provides a convenient way to manage background service tasks, which is compatible with the `SystemMetricsMonitor`:

```swift
import SystemMetrics
import ServiceLifecycle
import UnixSignals
import Metrics

@main
struct Application {
static func main() async throws {
let logger = Logger(label: "Application")
let metrics = MyMetricsBackendImplementation()
MetricsSystem.bootstrap(metrics)

let service = FooService()
let systemMetricsMonitor = SystemMetricsMonitor(logger: logger)

let serviceGroup = ServiceGroup(
services: [service, systemMetricsMonitor],
gracefulShutdownSignals: [.sigint],
cancellationSignals: [.sigterm],
logger: logger
)

try await serviceGroup.run()
}
}
```
- <doc:Proposals>
7 changes: 3 additions & 4 deletions Sources/SystemMetrics/SystemMetricsMonitor+Darwin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import Darwin
extension SystemMetricsMonitorDataProvider: SystemMetricsProvider {
/// Collect current system metrics data.
///
/// On supported Darwin platforms, this delegates to the static
/// On supported Darwin platforms, this method delegates to the static
/// `darwinSystemMetrics()` function.
///
/// - Note: System metrics collection is not yet implemented for Darwin-based
/// platforms other than macOS. This method always returns `nil` on
/// other platforms.
/// - Note: This method doesn't yet support Darwin-based platforms other than
/// macOS and always returns `nil` on those platforms.
/// - Returns: Current system metrics, or `nil` if collection failed.
package func data() async -> SystemMetricsMonitor.Data? {
#if os(macOS)
Expand Down
Loading