diff --git a/README.md b/README.md index 195418b..b124b5f 100644 --- a/README.md +++ b/README.md @@ -3,36 +3,35 @@ [![sswg:sandbox](https://img.shields.io/badge/sswg-sandbox-yellow.svg)][SSWG-Incubation] [![Documentation](http://img.shields.io/badge/read_the-docs-2196f3.svg)][Documentation] -A Swift client for the [Prometheus](https://github.com/prometheus/prometheus) monitoring system, -supporting counters, gauges and histograms. Swift Prometheus -implements the Swift Metrics API. +A Swift client library for [Prometheus Monitoring System](https://github.com/prometheus/prometheus). -## Security +`swift-prometheus` supports creating `Counter`s, `Gauge`s and `Histogram`s, updating metric values, and exposing their values in the Prometheus text format. -Please see [SECURITY.md](SECURITY.md) for details on the security process. +## Installation and Usage -## Contributing +Please refer to the [Documentation][Documentation] for installation, usage instructions, and implementation details including Prometheus standards compliance. -All contributions are most welcome! +For general Prometheus guidance, see [Prometheus Monitoring System][prometheus-docs]. -If you think of some cool new feature that should be included, please [create an issue](https://github.com/swift-server/swift-prometheus/issues/new). -Or, if you want to implement it yourself, [fork this repo](https://github.com/swift-server/swift-prometheus/fork) and submit a PR! +## Security -If you find a bug or have issues, please [create an issue](https://github.com/swift-server-community/SwiftPrometheus/issues/new) explaining your problems. Please include as much information as possible, so it's easier for us to reproduce (Framework, OS, Swift version, terminal output, etc.) +Please see [SECURITY.md](SECURITY.md) for details on the security process. -[Documentation]: https://swiftpackageindex.com/swift-server/swift-prometheus/documentation/prometheus -[SSWG-Incubation]: https://www.swift.org/sswg/incubation-process.html +## Contributing +We welcome all contributions to `swift-prometheus`! For feature requests or bug reports, please [create an issue](https://github.com/swift-server/swift-prometheus/issues/new) with detailed information including Swift version, platform, and reproduction steps. To contribute code, [fork this repo](https://github.com/swift-server/swift-prometheus/fork) and submit a pull request with tests and documentation updates. ## Benchmarks -Benchmarks for `swift-prometheus` are in a separate Swift Package in the `Benchmarks` subfolder of this repository. -They use the [`package-benchmark`](https://github.com/ordo-one/package-benchmark) plugin. -Benchmarks depends on the [`jemalloc`](https://jemalloc.net) memory allocation library, which is used by `package-benchmark` to capture memory allocation statistics. -An installation guide can be found in the [Getting Started article](https://swiftpackageindex.com/ordo-one/package-benchmark/documentation/benchmark/gettingstarted#Installing-Prerequisites-and-Platform-Support) of `package-benchmark`. -Afterwards you can run the benchmarks from CLI by going to the `Benchmarks` subfolder (e.g. `cd Benchmarks`) and invoking: +Benchmarks are located in the [Benchmarks](/Benchmarks/) subfolder and use the [`package-benchmark`](https://github.com/ordo-one/package-benchmark) plugin. See the [Benchmarks Getting Started]((https://swiftpackageindex.com/ordo-one/package-benchmark/documentation/benchmark/gettingstarted#Installing-Prerequisites-and-Platform-Support)) guide for installation instructions. Run benchmarks by navigating to Benchmarks and executing: + ``` swift package benchmark ``` -For more information please refer to `swift package benchmark --help` or the [documentation of `package-benchmark`](https://swiftpackageindex.com/ordo-one/package-benchmark/documentation/benchmark). +For more information please refer to `swift package benchmark --help` or the [`package-benchmark` Documentation](https://swiftpackageindex.com/ordo-one/package-benchmark/documentation/benchmark). + + +[Documentation]: https://swiftpackageindex.com/swift-server/swift-prometheus/documentation/prometheus +[prometheus-docs]: https://prometheus.io/docs/introduction/overview/ +[SSWG-Incubation]: https://www.swift.org/sswg/incubation-process.html diff --git a/Sources/Prometheus/Docs.docc/index.md b/Sources/Prometheus/Docs.docc/index.md index 24f7f8f..c8f0bf4 100644 --- a/Sources/Prometheus/Docs.docc/index.md +++ b/Sources/Prometheus/Docs.docc/index.md @@ -1,15 +1,19 @@ # ``Prometheus`` -A prometheus client library for Swift. +A Swift client library for the Prometheus Monitoring System. ## Overview -``Prometheus`` supports creating ``Counter``s, ``Gauge``s and ``Histogram``s and exporting their -values in the Prometheus text format. +``Prometheus`` supports creating ``Counter``s, ``Gauge``s and ``Histogram``s, updating metric values, and exposing their values in the Prometheus text format. -``Prometheus`` integrates with [Swift Metrics](doc:swift-metrics). +#### Key Features -For general advice on how to use `Prometheus` make sure to also read the [Prometheus documentation][prometheus-docs]. +- *Standards Compliant*: Follows Prometheus naming conventions and exposition formats, enforces base guarantees. +- *Flexible Metric Labeling*: Supports flexible metric label structures with consistency guarantees. +- *Thread Safe and Type-Safe*. +- *Swift Metrics Compatible*: Use the native Prometheus client API implemented in this library or use [Swift Metrics](doc:swift-metrics) as a backend for this library. + +For general Prometheus guidance, see the [Prometheus Monitoring System Documentation][prometheus-docs]. ## Installation @@ -40,41 +44,61 @@ In your Swift file you must first `import Prometheus`: import Prometheus ``` -Next you need to create a ``PrometheusCollectorRegistry``, which you use to create ``Counter``s, -``Gauge``s and ``Histogram``s. +Create a ``PrometheusCollectorRegistry`` instance and register, for instance, a ``Counter``: ```swift let registry = PrometheusCollectorRegistry() -let myCounter = registry.makeCounter(name: "my_counter") -myCounter.increment() +let httpRequestsDescriptor = MetricNameDescriptor( + namespace: "myapp", + subsystem: "http", + metricName: "requests", + unitName: "total", + helpText: "Total HTTP requests" +) + +let httpRequestsGet = registry.makeCounter( + descriptor: httpRequestsDescriptor, + labels: [("method", "GET"), ("status", "200")] +) -let myGauge = registry.makeGauge(name: "my_gauge") -myGauge.increment() -myGauge.decrement() +httpRequestsGet.increment(by: 5.0) ``` -Lastly, you can use your ``PrometheusCollectorRegistry`` to generate a Prometheus export in the -text representation: +Emit all registered metrics to the Prometheus text exposition format: ```swift -var buffer = [UInt8]() -buffer.reserveCapacity(1024) // potentially smart moves to reduce the number of reallocations -registry.emit(into: &buffer) +let output = registry.emitToString() +print(output) +``` + +```sh +# HELP myapp_http_requests_total Total HTTP requests +# TYPE myapp_http_requests_total counter +myapp_http_requests_total{method="GET",status="200"} 5.0 +``` + +Unregister a ``Counter``: -print(String(decoding: buffer, as: Unicode.UTF8.self)) +```swift +registry.unregisterCounter(httpRequestsGet) ``` +Explore a detailed usage guide at . + + ## Topics -### Getting started +### Getting Started -- - +- + +### Registry + - ``PrometheusCollectorRegistry`` - ``PrometheusMetricsFactory`` - ### Metrics - ``Counter`` @@ -83,4 +107,8 @@ print(String(decoding: buffer, as: Unicode.UTF8.self)) - ``DurationHistogram`` - ``ValueHistogram`` +### Configuration + +- ``MetricNameDescriptor`` + [prometheus-docs]: https://prometheus.io/docs/introduction/overview/ diff --git a/Sources/Prometheus/Docs.docc/labels.md b/Sources/Prometheus/Docs.docc/labels.md index 94e674a..afe0d84 100644 --- a/Sources/Prometheus/Docs.docc/labels.md +++ b/Sources/Prometheus/Docs.docc/labels.md @@ -1,84 +1,231 @@ -# Use Labels in Swift Prometheus +# Practical Example with Labels Deep Dive -Learn how to use Labels in Prometheus, the benefits of doing so, and how to avoid common mistakes. +Create, register, update, and expose metrics for Prometheus. ## Overview -Prometheus Collectors have a name and they may have labels. Labels specify the metrics further. For -example you might have a ``Counter`` with the name `http_responses_total`. You may now add a label -`code` that specifies the http status response code. This way you are able to query how many http -responses were sent. But you can also filter this by response code. +Create multiple collectors that are associated with a specific `PrometheusCollectorRegistry()` instance. -Read more about the benefits of using Labels in the [Prometheus best practices documentation][prometheus-use-labels]. +```swift +// Create an instance of `PrometheusCollectorRegistry` +let registry = PrometheusCollectorRegistry() + +// Create a family of labeled Counters with different label sets +let httpRequestsDescriptor = MetricNameDescriptor( + namespace: "myapp", + subsystem: "http", + metricName: "requests", + unitName: "total", + helpText: "Total HTTP requests" +) -> Note: The naming between Prometheus and Swift-Metrics is a bit confusing. Swift Metrics calls a -> metric's name its label and they call a metric's labels dimensions. In this article, when we -> refer to labels, we mean the additional properties that can be added to a metrics name. -> -> | Framework | Metric name | Additional infos | -> |---------------|-------------|------------------| -> | swift-metrics | `label` | `dimensions` | -> | Prometheus | `name` | `labels` | +let httpRequestsGet = registry.makeCounter( + descriptor: httpRequestsDescriptor, + labels: [("method", "GET"), ("status", "200")] +) -Please be aware that the ``PrometheusCollectorRegistry`` will create a seperate metric for each -unique label pair, even though the metric name might be the same. This means that in the example -below, we will have two independent metrics: +let httpRequestsPost = registry.makeCounter( + descriptor: httpRequestsDescriptor, + labels: [("method", "POST"), ("status", "201")] +) -```swift -let counter200 = registry.makeCounter(name: "http_responses_total", labels: ["code": "200"]) -let counter400 = registry.makeCounter(name: "http_responses_total", labels: ["code": "400"]) - -// handling response code -swift responseCode { -case .ok: - counter200.increment() -case .badRequest: - counter400.increment() -default: - break -} +let httpRequestsError = registry.makeCounter( + descriptor: httpRequestsDescriptor, + labels: [("method", "GET"), ("status", "500"), ("endpoint", "/api/users")] +) + +// Create an unlabeled Counter +let totalErrorsCounter = registry.makeCounter(descriptor: MetricNameDescriptor( + namespace: "myapp", + subsystem: "system", + metricName: "errors", + unitName: "total", + helpText: "Total system errors" +)) + +// Create a DurationHistogram metric +let responseTimeDescriptor = MetricNameDescriptor( + namespace: "myapp", + subsystem: "http", + metricName: "request_duration", + unitName: "seconds", + helpText: "HTTP request duration in seconds" +) + +let responseTime = registry.makeDurationHistogram( + descriptor: responseTimeDescriptor, + buckets: [.milliseconds(10), .milliseconds(100), .seconds(1)] +) + +// Create a Gauge metric +let memoryDescriptor = MetricNameDescriptor( + namespace: "myapp", + subsystem: "system", + metricName: "memory_usage", + unitName: "bytes", + helpText: "Current memory usage in bytes" +) + +let memoryUsage = registry.makeGauge(descriptor: memoryDescriptor) + +// Create a Gauge metric via passing the metric name and help directly +let activeConnections = registry.makeGauge( + name: "myapp_http_connections_active", + help: "Currently active HTTP connections" +) + +// Simulate some metrics +httpRequestsGet.increment(by: 5.0) +httpRequestsPost.increment(by: 2.0) +httpRequestsError.increment() +totalErrorsCounter.increment(by: 3.0) +responseTime.record(.milliseconds(150)) +memoryUsage.set(Double(1024 * 1024 * 128)) // 134,217,728 bytes (128MB) +activeConnections.set(42.0) + +// Emit all metrics (thread-safe option) +let output = registry.emitToString() +print(output) +``` + +```sh +# HELP myapp_http_connections_active Currently active HTTP connections +# TYPE myapp_http_connections_active gauge +myapp_http_connections_active 42.0 +# HELP myapp_http_requests_total Total HTTP requests +# TYPE myapp_http_requests_total counter +myapp_http_requests_total{method="GET",status="200"} 5.0 +myapp_http_requests_total{method="POST",status="201"} 2.0 +myapp_http_requests_total{method="GET",status="500",endpoint="/api/users"} 1 +# HELP myapp_system_errors_total Total system errors +# TYPE myapp_system_errors_total counter +myapp_system_errors_total 3.0 +# HELP myapp_http_request_duration_seconds HTTP request duration in seconds +# TYPE myapp_http_request_duration_seconds histogram +myapp_http_request_duration_seconds_bucket{le="0.01"} 0 +myapp_http_request_duration_seconds_bucket{le="0.1"} 0 +myapp_http_request_duration_seconds_bucket{le="1.0"} 1 +myapp_http_request_duration_seconds_bucket{le="+Inf"} 1 +myapp_http_request_duration_seconds_sum 0.15 +myapp_http_request_duration_seconds_count 1 +# HELP myapp_system_memory_usage_bytes Current memory usage in bytes +# TYPE myapp_system_memory_usage_bytes gauge +myapp_system_memory_usage_bytes 134217728.0 ``` -> Important: Please note, that all metrics with the same name, **must** use the same label names. +### Notice how: -Prometheus requires that for the same metric name all labels names must be the same. Swift -Prometheus enforces this by crashing if the label names or the metric type does not match a -previously registered metric with the same name. +*Metadata Deduplication:* +- Each metric name gets exactly one `# HELP` and `# TYPE` line (before the metric output), regardless of how many label variations exist. -#### Examples: +*Label Flexibility:* +- The same metric name `myapp_http_requests_total` supports different label structures: + - `{method="GET",status="200"}` (2 labels) + - `{method="POST",status="201"}` (2 labels) + - `{method="GET",status="500",endpoint="/api/users"}` (3 labels) +- Each unique combination of metric name and label set (as determined by hashing each key and value together) generates a single time series. -The example below crashes as we try to create a ``Counter`` named `"http_responses_total"` with a -label `"code"` after a ``Counter`` with the same name without labels was created earlier. +*Metric Type Behaviors:* +- *Counter*: `myapp_http_requests_total` and `myapp_system_errors_total` - monotonically increasing values. +- *Histogram*: `myapp_http_request_duration_seconds` - automatically generates multiple time series (`_bucket`, `_sum`, `_count`). +- *Gauge*: `myapp_system_memory_usage_bytes` and `myapp_http_connections_active` - can increase or decrease. + +*Naming Conventions:* +- Proper namespacing: `myapp` prefix identifies the application. +- Descriptive subsystems: `http`, `system` group related metrics. +- Unit suffixes: `total`, `seconds`, `bytes`, `active` clarify what's being measured. + +*Prometheus Compliance:* +- Metric names, label names, and `# HELP` text are validated against Prometheus character allowlists. +- Different label names and structures are allowed for the same metric name; however, cannot mix labeled and unlabeled metrics with the same metric name. +- Must use consistent metric types, help text, and histogram buckets for the same metric name. + +*Known Limitations:* +- Prometheus converts all metrics to floating-point types, which can cause precision loss. For example, Counters designed for `UInt64` values or Gauges capturing nanosecond timestamps will lose precision. In such cases, consider alternative frameworks or solutions. + +*Thread-safe through multiple mechanisms:* +- Metric value updates are based on atomic operations. +- Export functions like `emitToBuffer()` and `emitToString()` use internal locking and conform to Swift [Sendable](https://developer.apple.com/documentation/Swift/Sendable). +- Lower-level export via `emit(into:)` is thread-safe due to Swift's `inout` [exclusivity](https://www.swift.org/blog/swift-5-exclusivity/) guarantees, with the compiler preventing concurrent access through warnings (Swift 5) or errors (Swift 6 [strict concurrency](https://developer.apple.com/documentation/swift/adoptingswift6) mode). + +βœ… *Correct Labels Usage:* ```swift -let counter = registry.makeCounter(name: "http_responses_total") -let counter200 = registry.makeCounter( // πŸ’₯ crash - name: "http_responses_total", - labels: ["code": "200"] -) +// All labeled with same structure +let counter1 = registry.makeCounter(name: "requests", labels: [("method", "GET")]) +let counter2 = registry.makeCounter(name: "requests", labels: [("method", "POST")]) +let counter3 = registry.makeCounter(name: "requests", labels: [("method", "PUT")]) + +// Different label names are also allowed +let counter4 = registry.makeCounter(name: "requests", labels: [("endpoint", "/api")]) +let counter5 = registry.makeCounter(name: "requests", labels: [("status", "200")]) + +// Different numbers of labels are fine too +let counter6 = registry.makeCounter(name: "requests", labels: [("method", "DELETE"), ("endpoint", "/users"), ("region", "us-east")]) ``` -The example below crashes as we try to create a ``Counter`` named `"http_responses_total"` with a -label `"version"` after a ``Counter`` with the same name but different label name `"code"` was -created earlier. +❌ *Incorrect Labels Usage (Will Crash):* ```swift -let counter200 = registry.makeCounter( - name: "http_responses_total", - labels: ["code": "200"] -) -let counterHTTPVersion1 = registry.makeCounter( // πŸ’₯ crash - name: "http_responses_total", - labels: ["version": "1.1"] -) +// This will crash - mixing labeled and unlabeled +let counter1 = registry.makeCounter(name: "requests") // unlabeled +let counter2 = registry.makeCounter(name: "requests", labels: [("method", "GET")]) // πŸ’₯ crash + +// This will crash - different metric types +let counter = registry.makeCounter(name: "requests") +let gauge = registry.makeGauge(name: "requests") // πŸ’₯ crash + +// This will crash - different help text for same metric name +let counter1 = registry.makeCounter(name: "requests", help: "HTTP requests") +let counter2 = registry.makeCounter(name: "requests", help: "API requests") // πŸ’₯ crash + +// This will crash - different buckets for same histogram name +let hist1 = registry.makeDurationHistogram(name: "duration", buckets: [.seconds(1)]) +let hist2 = registry.makeDurationHistogram(name: "duration", buckets: [.seconds(2)]) // πŸ’₯ crash ``` -The example below crashes as we try to create a ``Gauge`` named `http_responses_total` with the -same name as a previously created ``Counter``. +⚠️ *Discouraged Practices (Will Work But Not Recommended):* ```swift -let counter = registry.makeCounter(name: "http_responses_total") -let gauge = registry.makeGauge(name: "http_responses_total") // πŸ’₯ crash +// ❌ Don't use reserved Prometheus label names +let badCounter1 = registry.makeCounter(name: "requests", labels: [("le", "100")]) // "le" is reserved for histograms +let badCounter2 = registry.makeCounter(name: "requests", labels: [("quantile", "0.95")]) // "quantile" is reserved for summaries + +// ❌ Don't use metric suffixes as label values +let badCounter3 = registry.makeCounter(name: "requests", labels: [("type", "total")]) // Use _total suffix instead +let badCounter4 = registry.makeCounter(name: "requests", labels: [("aggregation", "sum")]) // Use _total suffix instead +let badCounter5 = registry.makeCounter(name: "requests", labels: [("aggregation", "count")]) // Use _total suffix instead + +// βœ… Better alternatives +let goodCounter1 = registry.makeCounter(name: "requests_total", labels: [("method", "GET")]) // Descriptive labels +let goodCounter2 = registry.makeCounter(name: "requests_total", labels: [("endpoint", "/api/users")]) // Meaningful dimensions ``` +*Additional Notes*: + +Above, we demonstrated the Prometheus’s proper approach. However, you can also use [Swift Metrics](doc:swift-metrics) as a backend for this library via `PrometheusMetricsFactory`. + +> Note: The naming between Prometheus and Swift-Metrics may be confusing. Swift Metrics calls a +> metric's name its label and they call a metric's labels dimensions. In this article, when we +> refer to labels, we mean the additional properties that can be added to a metrics name. +> +> | Framework | Metric name | Additional infos | +> |---------------|-------------|------------------| +> | swift-metrics | `label` | `dimensions` | +> | Prometheus | `name` | `labels` | + +### References + +- Prometheus [Docs - Overview][prometheus-docs] +- Prometheus [Instrumentation Best Practices - Use Labels][prometheus-use-labels] +- Prometheus [Naming Best Practices][prometheus-naming] +- Prometheus [Client Library Guidelines][prometheus-client-libs] +- Prometheus [Exporter Guidelines][prometheus-exporters] +- Prometheus [Exposition Format][prometheus-exposition] + +[prometheus-docs]: https://prometheus.io/docs/introduction/overview/ [prometheus-use-labels]: https://prometheus.io/docs/practices/instrumentation/#use-labels +[prometheus-naming]: https://prometheus.io/docs/practices/naming/ +[prometheus-client-libs]: https://prometheus.io/docs/instrumenting/writing_clientlibs/ +[prometheus-exporters]: https://prometheus.io/docs/instrumenting/writing_exporters/ +[prometheus-exposition]: https://prometheus.io/docs/instrumenting/exposition_formats/ diff --git a/Sources/Prometheus/Docs.docc/swift-metrics.md b/Sources/Prometheus/Docs.docc/swift-metrics.md index 460429f..d3ea4ef 100644 --- a/Sources/Prometheus/Docs.docc/swift-metrics.md +++ b/Sources/Prometheus/Docs.docc/swift-metrics.md @@ -1,7 +1,6 @@ -# Emit metrics collected by Swift Metrics +# Swift Metrics as Backend -Learn how Swift Prometheus integrates with Swift Metrics – an abstract API that is widely used in -the swift-server ecosystem. +Learn how to use Swift Metrics as a backend for Swift Prometheus. ## Overview @@ -51,12 +50,12 @@ func main() { } ``` -## Modifying the Prometheus Export +### Modifying the Prometheus Export Now that we have setup the Prometheus export, lets discuss which configuration options there are to modify the Swift Metrics export. -### Using a specific Collector Registry as the Export Target +#### Using a specific Collector Registry as the Export Target If you create a `PrometheusMetricsFactory()` without specifying a ``PrometheusCollectorRegistry``, it will use ``PrometheusMetricsFactory/defaultRegistry`` as the underlying collector registry. @@ -78,7 +77,7 @@ factory.registry = registry MetricsSystem.bootstrap(factory) ``` -### Modifying Swift metrics names and labels +#### Modifying Swift metrics names and labels When you create a ``PrometheusMetricsFactory``, you can also set the ``PrometheusMetricsFactory/nameAndLabelSanitizer`` to modify the metric names and labels: @@ -107,9 +106,9 @@ generated in a third party library. > Use the ``PrometheusMetricsFactory/nameAndLabelSanitizer`` to ensure this remains true metrics > that are created in third party libraries. See for more information about this. -### Defining Buckets for Histograms +#### Defining Buckets for Histograms -#### Default buckets +Default buckets: Swift Metric `Timer`s are backed by a Prometheus ``DurationHistogram`` and Swift Metric `Recorder`s that aggregate are backed by a Prometheus ``ValueHistogram``. As a user, you can @@ -141,7 +140,7 @@ Timer(label: "my_timer") // will use the buckets specified in `defaultDurationHi Recorder(label: "my_recorder", aggregate: true) // will use the buckets specified in `defaultValueHistogramBuckets` ``` -#### Buckets by name +Buckets by name: You can also specify the buckets by metric name: diff --git a/Sources/Prometheus/PrometheusCollectorRegistry.swift b/Sources/Prometheus/PrometheusCollectorRegistry.swift index 86f2990..beb59b2 100644 --- a/Sources/Prometheus/PrometheusCollectorRegistry.swift +++ b/Sources/Prometheus/PrometheusCollectorRegistry.swift @@ -943,7 +943,7 @@ public final class PrometheusCollectorRegistry: Sendable { /// This is useful when the registry's metric composition has changed significantly and you want to /// optimize buffer size for the new workload. /// - /// - Note: Thread-safe. Does not affect ``emit(into:)`` calls which use external buffers + /// - Note: Does not affect ``emit(into:)`` calls which use external buffers public func resetInternalBuffer() { bufferBox.withLockedValue { buffer in // Resets capacity to 0, forcing re-calibration @@ -958,7 +958,7 @@ public final class PrometheusCollectorRegistry: Sendable { /// the registry's output requirements increase. /// /// - Returns: The current buffer capacity in bytes - /// - Note: Thread-safe. Primarily useful for testing and monitoring buffer behavior + /// - Note: Primarily useful for testing and monitoring buffer behavior public func internalBufferCapacity() -> Int { return bufferBox.withLockedValue { buffer in buffer.capacity @@ -972,7 +972,7 @@ public final class PrometheusCollectorRegistry: Sendable { /// established capacity, clearing content but preserving the initially allocated memory. /// /// - Returns: A String containing all registered metrics in Prometheus text format - /// - Note: Thread-safe. Use ``resetInternalBuffer()`` to force recalibration + /// - Note: Use ``resetInternalBuffer()`` to force recalibration /// - SeeAlso: ``emitToBuffer()`` for raw UTF-8 bytes, ``emit(into:)`` for custom buffer public func emitToString() -> String { return bufferBox.withLockedValue { buffer in @@ -995,7 +995,7 @@ public final class PrometheusCollectorRegistry: Sendable { /// established capacity, clearing content but preserving the initially allocated memory. Returns a copy. /// /// - Returns: A copy of the UTF-8 encoded byte array containing all registered metrics - /// - Note: Thread-safe. Use ``resetInternalBuffer()`` to force recalibration + /// - Note: Use ``resetInternalBuffer()`` to force recalibration /// - SeeAlso: ``emitToString()`` for String output, ``emit(into:)`` for custom buffer public func emitToBuffer() -> [UInt8] { return bufferBox.withLockedValue { buffer in @@ -1016,8 +1016,6 @@ public final class PrometheusCollectorRegistry: Sendable { /// performance and control but requires manual buffer lifecycle management. /// /// - Parameter buffer: The buffer to write metrics data into. Content will be appended to existing data - /// - Note: Not thread-safe. Caller must handle synchronization and may optimize buffer capacity for - /// maximum performance by reducing reallocations /// - SeeAlso: ``emitToString()`` and ``emitToBuffer()`` for automatic buffer management public func emit(into buffer: inout [UInt8]) { let metrics = self.box.withLockedValue { $0 }