@zio.dev/zio-blocks 0.0.51 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +6 -0
- package/guides/getting-started-with-mux.md +0 -112
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +395 -1
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +131 -70
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +200 -559
- package/package.json +1 -1
- package/reference/async.md +1379 -531
- package/reference/chunk.md +3 -3
- package/reference/codegen/index.md +1 -1
- package/reference/combinators.md +4 -4
- package/reference/config/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +6 -49
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +2 -2
- package/reference/docs.md +2 -2
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/endpoint.md +1 -0
- package/reference/endpoint/index.md +9 -89
- package/reference/endpoint/path-codec.md +12 -24
- package/reference/endpoint/route-pattern.md +4 -6
- package/reference/endpoint/segment-codec.md +19 -32
- package/reference/html.md +313 -9
- package/reference/htmx/index.md +4 -52
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +3 -1
- package/reference/http-model/model.md +107 -71
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +6 -3
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +128 -11
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +7 -2
- package/reference/openapi.md +3 -3
- package/reference/projection.md +654 -0
- package/reference/resource-management/index.md +1 -1
- package/reference/resource-management/resource.md +2 -98
- package/reference/resource-management/scope.md +1 -209
- package/reference/resource-management/wire.md +4 -50
- package/reference/ringbuffer/advanced.mdx +1 -1
- package/reference/ringbuffer/index.mdx +3 -3
- package/reference/ringbuffer/mpmc.mdx +38 -4
- package/reference/ringbuffer/mpsc.mdx +36 -4
- package/reference/ringbuffer/spmc.mdx +1 -1
- package/reference/ringbuffer/spsc.mdx +87 -15
- package/reference/schema/allows.md +0 -96
- package/reference/schema/binding.md +2 -2
- package/reference/schema/built-in-codecs/avro.md +2 -2
- package/reference/schema/built-in-codecs/bson.md +50 -20
- package/reference/schema/built-in-codecs/csv.md +2 -2
- package/reference/schema/built-in-codecs/index.md +3 -3
- package/reference/schema/built-in-codecs/json/index.md +2 -2
- package/reference/schema/built-in-codecs/json/json.md +1 -0
- package/reference/schema/built-in-codecs/messagepack.md +3 -3
- package/reference/schema/built-in-codecs/thrift.md +2 -2
- package/reference/schema/built-in-codecs/toon.md +3 -3
- package/reference/schema/built-in-codecs/yaml.md +2 -2
- package/reference/schema/codec.md +11 -11
- package/reference/schema/dynamic-optic.md +48 -3
- package/reference/schema/dynamic-schema.md +3 -3
- package/reference/schema/index.md +2 -0
- package/reference/schema/path-interpolator.md +2 -0
- package/reference/schema/reflect-transformer.md +140 -0
- package/reference/schema/schema-evolution/as.md +4 -4
- package/reference/schema/schema-evolution/into.md +2 -2
- package/reference/schema/schema-expr.md +2 -2
- package/reference/schema/schema-search.md +263 -0
- package/reference/schema/schema.md +10 -2
- package/reference/schema/type-class-derivation.md +1 -1
- package/reference/smithy.md +502 -3
- package/reference/sql/db-codec-deriver.md +3 -3
- package/reference/sql/db-codec.md +22 -22
- package/reference/sql/db-con.md +4 -4
- package/reference/sql/db-connection.md +1 -1
- package/reference/sql/db-param.md +1 -1
- package/reference/sql/db-result-reader.md +4 -2
- package/reference/sql/db-tx.md +46 -14
- package/reference/sql/ddl.md +1 -1
- package/reference/sql/frag.md +44 -10
- package/reference/sql/index.md +7 -7
- package/reference/sql/repo.md +15 -15
- package/reference/sql/sql-dialect.md +1 -1
- package/reference/sql/sql-logger.md +1 -1
- package/reference/sql/sql-name-mapper.md +3 -3
- package/reference/sql/table-metadata.md +3 -3
- package/reference/sql/table.md +10 -10
- package/reference/sql/transactor-zio.md +1 -1
- package/reference/sql/transactor.md +21 -11
- package/reference/sql-zio.md +2 -2
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
- package/reference/streams/{sink.md → core/sink.md} +331 -353
- package/reference/streams/{stream.md → core/stream.md} +919 -209
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +140 -67
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/{writer.md → primitives/writer.md} +254 -98
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +0 -64
- package/sidebars.js +365 -185
- package/undocumented-report.md +528 -270
- package/reference/config.md +0 -158
- package/reference/streams/concurrent-operators.md +0 -106
- package/reference/streams/reader.md +0 -1284
- package/reference/streams/scala-2-compatibility.md +0 -55
- package/reference/streams/zero-boxing.md +0 -275
- package/reference/telemetry.md +0 -693
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: index
|
|
3
|
+
title: "Metrics"
|
|
4
|
+
description: "Record how your application behaves over time — request rates, latencies, queue depths — as labeled counters, histograms, and gauges."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Application Metrics"
|
|
7
|
+
- "Dimensional Metrics"
|
|
8
|
+
- "Metrics Overview"
|
|
9
|
+
- "MeterProvider"
|
|
10
|
+
sidebar_label: "Metrics"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Metrics are the running numbers that tell you how your application is behaving over time — request rate, error count, latency, queue depth — the data you put on dashboards and alerts. They're **dimensional**: each measurement carries labels (like `method=GET`, `status=200`), and a metric splits into a separate series per label combination, so you can break "requests" down by endpoint or status. You record through four [instruments](./instruments.md), each for a different shape of number — a `Counter` for a total that only climbs, an `UpDownCounter` for one that rises and falls, a `Histogram` for a distribution of values, and a `Gauge` for the latest reading.
|
|
14
|
+
|
|
15
|
+
You record through the `metric` object: `metric.counter("http.requests").add(1)` works immediately, with no setup — it creates the named instrument and records the measurement. Pick the factory that matches what you're measuring — `metric.counter`, `metric.upDownCounter`, `metric.histogram`, or `metric.gauge` — and pass dimension labels as you record.
|
|
16
|
+
|
|
17
|
+
Measurements accumulate in memory, and reading them out is a pull: `metric.reader.collectAllMetrics()` returns [`MetricData`](./metric-data.md) snapshots — handy for tests and assertions, and the same call an exporter makes. Metrics have no push pipeline the way tracing and logging do: there is no processor hook on `MeterProvider`, so shipping them means scheduling that pull yourself and handing the snapshots to your backend. `metric.install(provider)` swaps in a provider configured with your own `Resource`.
|
|
18
|
+
|
|
19
|
+
Day to day you only need `metric.*`, but it helps to know what sits underneath, because each piece is where you go when you want more control. A [`MeterProvider`](./meter-provider.md) is what you build at startup: it holds your service identity as a `Resource` and vends everything else. (Unlike spans and log records, collected metric data does not carry that `Resource`, so an exporter has to attach the service identity itself.) A [`Meter`](./meter.md) is a named handle you take with `metric.get("com.example.orders")` when measurements should be attributed to one component, or when you want to declare an instrument's unit and description, or pre-declare label names for a hot path.
|
|
20
|
+
|
|
21
|
+
The other two are the recording and reading ends. The four [instruments](./instruments.md) are what you actually record through, however you obtained them. And the `MetricReader` behind `metric.reader` is what turns everything recorded so far into `MetricData` snapshots — the thing you call in tests, and the thing an exporter pulls from in production.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Metrics' core job is to **record and export application metrics**. Create instruments from `metric`, record measurements with dimension labels as work happens, then read aggregated snapshots (or `metric.install(...)` a provider to export them):
|
|
26
|
+
|
|
27
|
+
```scala
|
|
28
|
+
import zio.blocks.telemetry._
|
|
29
|
+
|
|
30
|
+
val requests = metric.counter("http.requests")
|
|
31
|
+
requests.add(1, "method" -> "GET", "status" -> "200")
|
|
32
|
+
|
|
33
|
+
val latency = metric.histogram("http.latency.ms")
|
|
34
|
+
latency.record(12.5, "route" -> "/orders")
|
|
35
|
+
|
|
36
|
+
val snapshots = metric.reader.collectAllMetrics()
|
|
37
|
+
snapshots.foreach {
|
|
38
|
+
case MetricData.SumData(points) => points.foreach(p => println(p.value))
|
|
39
|
+
case MetricData.HistogramData(points) => points.foreach(p => println(p.count))
|
|
40
|
+
case MetricData.GaugeData(points) => points.foreach(p => println(p.value))
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Record Measurements with the Four Instruments
|
|
45
|
+
|
|
46
|
+
Each factory creates a new instrument on the default meter. Nothing is cached by name, so build each one once and hold it — calling `metric.counter("http.requests")` twice gives you two independent counters and two entries in the collected snapshot.
|
|
47
|
+
|
|
48
|
+
```scala
|
|
49
|
+
object metric {
|
|
50
|
+
def counter(name: String): Counter
|
|
51
|
+
def upDownCounter(name: String): UpDownCounter
|
|
52
|
+
def histogram(name: String): Histogram
|
|
53
|
+
def gauge(name: String): Gauge
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The four instruments differ by what they measure: a `Counter` sums a monotonic total, an `UpDownCounter` tracks a value that rises and falls, a `Histogram` builds a distribution of recorded values, and a `Gauge` holds the latest instantaneous reading. Every recording call accepts dimension labels as `(String, Any)*` pairs, which split the metric into separately-aggregated series.
|
|
58
|
+
|
|
59
|
+
```scala
|
|
60
|
+
import zio.blocks.telemetry._
|
|
61
|
+
|
|
62
|
+
metric.counter("http.requests").add(1, "method" -> "GET", "status" -> "200")
|
|
63
|
+
metric.upDownCounter("queue.depth").add(-1, "queue" -> "orders")
|
|
64
|
+
metric.histogram("http.latency.ms").record(12.5, "route" -> "/orders")
|
|
65
|
+
metric.gauge("cache.entries").record(4096.0, "cache" -> "sessions")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Note the two verbs. `add(delta: Long)` takes a *change* and the instrument keeps the running total — a `Counter` ignores a negative delta, while an `UpDownCounter` accepts one, so `add(1)`/`add(-1)` tracks queue depth. `record(value: Double)` takes the *measurement itself*: a `Histogram` files each value into buckets, and a `Gauge` keeps only the newest.
|
|
69
|
+
|
|
70
|
+
## Scope a Meter and Pre-Declare Labels
|
|
71
|
+
|
|
72
|
+
`metric.get(name)` returns a `Meter` bound to a named instrumentation scope.
|
|
73
|
+
|
|
74
|
+
```scala
|
|
75
|
+
object metric {
|
|
76
|
+
def get(name: String): Meter
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Its builders attach a description and unit, and its labeled variants pre-declare label names so a hot path records positionally instead of naming a key at every call.
|
|
81
|
+
|
|
82
|
+
```scala
|
|
83
|
+
import zio.blocks.telemetry._
|
|
84
|
+
|
|
85
|
+
val meter: Meter = metric.get("com.example.orders")
|
|
86
|
+
|
|
87
|
+
val payments: Counter =
|
|
88
|
+
meter.counterBuilder("payments.total").setUnit("{payment}").setDescription("Payments processed").build()
|
|
89
|
+
payments.add(1, "currency" -> "USD")
|
|
90
|
+
|
|
91
|
+
val byRoute: LabeledCounter = meter.labeledCounter("http.requests", "method", "status")
|
|
92
|
+
byRoute.add(1, "GET", "200")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`setUnit` labels what the numbers mean, so a dashboard can axis-label `http.latency` as milliseconds rather than guessing. Use the UCUM codes OpenTelemetry expects — `"ms"`, `"s"`, `"By"` for bytes — and for a plain count of things, the thing in braces: `"{payment}"`, `"{request}"`. It's metadata attached to the instrument, not part of any measurement, and it stays on the instrument: the snapshots `metric.reader` returns carry only data points, so a unit reaches a backend through an exporter, not through `MetricData`.
|
|
96
|
+
|
|
97
|
+
## Read Aggregated Snapshots
|
|
98
|
+
|
|
99
|
+
Read every registered instrument as a `MetricData` snapshot — a fresh value each call, though a histogram point hands you its `boundaries` and `bucketCounts` arrays directly, so treat them as read-only.
|
|
100
|
+
|
|
101
|
+
```scala
|
|
102
|
+
object metric {
|
|
103
|
+
def reader: MetricReader
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`metric.reader.collectAllMetrics()` aggregates them — `SumData` for counters, `HistogramData` for histograms, `GaugeData` for gauges — for inspection in tests or manual export.
|
|
108
|
+
|
|
109
|
+
```scala
|
|
110
|
+
import zio.blocks.telemetry._
|
|
111
|
+
|
|
112
|
+
val snapshots: Seq[MetricData] = metric.reader.collectAllMetrics()
|
|
113
|
+
snapshots.foreach {
|
|
114
|
+
case MetricData.SumData(points) => points.foreach(p => println(p.value))
|
|
115
|
+
case MetricData.HistogramData(points) => points.foreach(p => println(p.count))
|
|
116
|
+
case MetricData.GaugeData(points) => points.foreach(p => println(p.value))
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Install a Provider and Reset
|
|
121
|
+
|
|
122
|
+
`metric.install(provider)` swaps in a `MeterProvider` configured with your own [`Resource`](../common/resource.md). The builder takes nothing else — there is no exporter or reader hook — so instruments created after the swap register on the new provider's meters and are read back through `metric.reader`.
|
|
123
|
+
|
|
124
|
+
```scala
|
|
125
|
+
object metric {
|
|
126
|
+
def install(provider: MeterProvider): Unit
|
|
127
|
+
def removeAll(): Unit
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Install before creating any instruments. Both calls replace the provider along with its registry, so an instrument built beforehand stays registered with the old one and won't appear in `metric.reader` snapshots. `metric.removeAll()` restores a fresh default provider.
|
|
132
|
+
|
|
133
|
+
```scala
|
|
134
|
+
import zio.blocks.telemetry._
|
|
135
|
+
|
|
136
|
+
metric.install(
|
|
137
|
+
MeterProvider.builder
|
|
138
|
+
.setResource(Resource.create(Attributes.of(Attributes.ServiceName, "order-service")))
|
|
139
|
+
.build()
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// later, at shutdown
|
|
143
|
+
metric.removeAll()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## See Also
|
|
147
|
+
|
|
148
|
+
- [Telemetry Guide](../../../guides/telemetry-guide.md) — metrics data flow and labeled instrument patterns
|
|
149
|
+
- [Telemetry Reference](../index.md) — module overview and all three pillars
|
|
150
|
+
- [Common Types](../common/index.md) — `Attributes` used as metric dimension labels
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: instruments
|
|
3
|
+
title: "Metric Instruments"
|
|
4
|
+
description: "The four synchronous metric instruments: monotonic Counter, bidirectional UpDownCounter, bucketed Histogram, and last-value Gauge."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Application Metrics"
|
|
7
|
+
- "Synchronous Instruments"
|
|
8
|
+
- "Measurement Recording"
|
|
9
|
+
- "Metric Instruments"
|
|
10
|
+
sidebar_label: "Instruments"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
The four synchronous instruments are what application code actually calls to record measurements. They differ by what they measure: a `Counter` sums a monotonic total, an `UpDownCounter` tracks a value that rises and falls, a `Histogram` builds a distribution of observations, and a `Gauge` holds the latest reading. Reach for them through [`metric`](./index.md) (`metric.counter("…")`) or a [`Meter`](./meter.md) builder — either path registers the instrument so `MetricReader.collectAllMetrics()` collects it into a [`MetricData`](./metric-data.md) snapshot.
|
|
14
|
+
|
|
15
|
+
All four share the same recording shape: `add` (counters) or `record` (histogram, gauge) takes a value plus optional dimension labels, either as an [`Attributes`](../common/attributes.md) set or as `(String, Any)*` tuples — a label value may be a `String`, `Long`, `Int` (widened to `Long`), `Double`, or `Boolean`, and anything else is recorded as its `toString`; `bind` pre-attaches a label set for hot-path reuse; and `collect` snapshots the accumulated data into a [`MetricData`](./metric-data.md) variant.
|
|
16
|
+
|
|
17
|
+
## Counter
|
|
18
|
+
|
|
19
|
+
A `Counter` records monotonically increasing values — negative deltas are ignored, so it only ever climbs. Use it for totals like requests served, errors, or bytes sent.
|
|
20
|
+
|
|
21
|
+
```scala
|
|
22
|
+
final class Counter private[telemetry] (
|
|
23
|
+
val name: String, val description: String, val unit: String
|
|
24
|
+
) {
|
|
25
|
+
def add(value: Long, attributes: Attributes): Unit
|
|
26
|
+
def add(value: Long, attrs: (String, Any)*): Unit // convenience vararg overload
|
|
27
|
+
def bind(attributes: Attributes): BoundCounter // pre-attributed for hot-path reuse
|
|
28
|
+
def collect(): MetricData // snapshot → MetricData.SumData
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Record with labels, then read the per-label totals from the collected `SumData`:
|
|
33
|
+
|
|
34
|
+
```scala
|
|
35
|
+
import zio.blocks.telemetry._
|
|
36
|
+
|
|
37
|
+
val calls = metric.counter("db.calls")
|
|
38
|
+
calls.add(1L, "table" -> "orders")
|
|
39
|
+
calls.add(2L, "table" -> "items")
|
|
40
|
+
|
|
41
|
+
val tableKey = AttributeKey.string("table")
|
|
42
|
+
metric.reader.collectAllMetrics().foreach {
|
|
43
|
+
case MetricData.SumData(points) =>
|
|
44
|
+
points.foreach(p => println(s"${p.attributes.get(tableKey)}: ${p.value}"))
|
|
45
|
+
case _ => ()
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## UpDownCounter
|
|
50
|
+
|
|
51
|
+
An `UpDownCounter` records bidirectional deltas — the same API as `Counter`, but negative values count. Use it for a running total that both rises and falls, such as active connections or queue depth.
|
|
52
|
+
|
|
53
|
+
```scala
|
|
54
|
+
final class UpDownCounter private[telemetry] (
|
|
55
|
+
val name: String, val description: String, val unit: String
|
|
56
|
+
) {
|
|
57
|
+
def add(value: Long, attributes: Attributes): Unit
|
|
58
|
+
def add(value: Long, attrs: (String, Any)*): Unit
|
|
59
|
+
def bind(attributes: Attributes): BoundUpDownCounter
|
|
60
|
+
def collect(): MetricData
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Add positive and negative deltas; the running total nets out:
|
|
65
|
+
|
|
66
|
+
```scala
|
|
67
|
+
import zio.blocks.telemetry._
|
|
68
|
+
|
|
69
|
+
val active = metric.upDownCounter("active.connections")
|
|
70
|
+
active.add(1L) // new connection
|
|
71
|
+
active.add(-1L) // connection closed
|
|
72
|
+
|
|
73
|
+
metric.reader.collectAllMetrics().foreach {
|
|
74
|
+
case MetricData.SumData(points) => println(points.head.value) // 0
|
|
75
|
+
case _ => ()
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Histogram
|
|
80
|
+
|
|
81
|
+
A `Histogram` distributes `Double` observations into buckets and accumulates their count, sum, min, and max per label set. Use it for value distributions like request latency or payload size. Observations fall into a fixed set of bucket boundaries, defaulting to `[0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000]`.
|
|
82
|
+
|
|
83
|
+
Each boundary is the **inclusive** upper bound of its bucket, so with boundaries `[5, 10]` a value of exactly `5` lands in the first bucket, not the second, and anything above the last boundary falls into one final overflow bucket. Note that the default set starts at `0`, so its first bucket holds only values at or below zero.
|
|
84
|
+
|
|
85
|
+
For different boundaries, construct the histogram directly — the builder has no setter for them:
|
|
86
|
+
|
|
87
|
+
```scala
|
|
88
|
+
import zio.blocks.telemetry._
|
|
89
|
+
|
|
90
|
+
val latency = Histogram("http.latency.ms", "Request latency", "ms", Array(10.0, 50.0, 100.0, 500.0))
|
|
91
|
+
latency.record(42.0, "route" -> "/orders")
|
|
92
|
+
|
|
93
|
+
val snapshot = latency.collect()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That path trades away registration: an instrument you build yourself belongs to no meter, so `collectAllMetrics()` never returns it and you have to call `collect()` on the instrument yourself.
|
|
97
|
+
|
|
98
|
+
```scala
|
|
99
|
+
final class Histogram private[telemetry] (
|
|
100
|
+
val name: String, val description: String, val unit: String,
|
|
101
|
+
val boundaries: Array[Double]
|
|
102
|
+
) {
|
|
103
|
+
def record(value: Double, attributes: Attributes): Unit
|
|
104
|
+
def record(value: Double, attrs: (String, Any)*): Unit
|
|
105
|
+
def bind(attributes: Attributes): BoundHistogram
|
|
106
|
+
def collect(): MetricData // snapshot → MetricData.HistogramData
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Record observations, then read `count` and `sum` from the collected `HistogramData`:
|
|
111
|
+
|
|
112
|
+
```scala
|
|
113
|
+
import zio.blocks.telemetry._
|
|
114
|
+
|
|
115
|
+
val latency = metric.histogram("request.latency")
|
|
116
|
+
latency.record(42.5, "endpoint" -> "/api/orders")
|
|
117
|
+
latency.record(1500.0, "endpoint" -> "/api/reports")
|
|
118
|
+
|
|
119
|
+
metric.reader.collectAllMetrics().foreach {
|
|
120
|
+
case MetricData.HistogramData(points) =>
|
|
121
|
+
points.foreach(p => println(s"count=${p.count} sum=${p.sum}"))
|
|
122
|
+
case _ => ()
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Gauge
|
|
127
|
+
|
|
128
|
+
A `Gauge` holds the most recent `Double` value per label set — each `record` overwrites the previous one. Use it for an instantaneous reading like CPU temperature or a current queue-depth snapshot.
|
|
129
|
+
|
|
130
|
+
```scala
|
|
131
|
+
final class Gauge private[telemetry] (
|
|
132
|
+
val name: String, val description: String, val unit: String
|
|
133
|
+
) {
|
|
134
|
+
def record(value: Double, attributes: Attributes): Unit
|
|
135
|
+
def record(value: Double, attrs: (String, Any)*): Unit
|
|
136
|
+
def bind(attributes: Attributes): BoundGauge
|
|
137
|
+
def collect(): MetricData // snapshot → MetricData.GaugeData
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Each `record` overwrites the last; the snapshot holds the latest value:
|
|
142
|
+
|
|
143
|
+
```scala
|
|
144
|
+
import zio.blocks.telemetry._
|
|
145
|
+
|
|
146
|
+
val temp = metric.gauge("cpu.temperature")
|
|
147
|
+
temp.record(72.5)
|
|
148
|
+
temp.record(74.1) // overwrites the previous value
|
|
149
|
+
|
|
150
|
+
metric.reader.collectAllMetrics().foreach {
|
|
151
|
+
case MetricData.GaugeData(points) => println(points.head.value) // 74.1
|
|
152
|
+
case _ => ()
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Choosing an Instrument
|
|
157
|
+
|
|
158
|
+
| Instrument | Delta constraint | Use when |
|
|
159
|
+
|-----------------|------------------|-----------------------------------------|
|
|
160
|
+
| `Counter` | Non-negative | Counting requests, errors, events |
|
|
161
|
+
| `UpDownCounter` | Any | Active connections, queue-depth changes |
|
|
162
|
+
| `Histogram` | Any `Double` | Latency, payload-size distributions |
|
|
163
|
+
| `Gauge` | Any `Double` | CPU temperature, queue-depth snapshot |
|
|
164
|
+
|
|
165
|
+
## Bound Instruments
|
|
166
|
+
|
|
167
|
+
When one label combination is recorded repeatedly on a hot path, `bind(attrs)` returns a `Bound*` instrument pre-associated with that label set, so recording skips rebuilding `Attributes` on every call:
|
|
168
|
+
|
|
169
|
+
```scala
|
|
170
|
+
import zio.blocks.telemetry._
|
|
171
|
+
|
|
172
|
+
val bound = metric.counter("rpc.calls").bind(Attributes.of(AttributeKey.string("method"), "OrderService.place"))
|
|
173
|
+
bound.add(1L)
|
|
174
|
+
bound.add(1L) // no Attributes construction per call
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
One caveat on a histogram: `bind` creates the per-label state immediately, so a label set you bind but never record collects as `count = 0` with `min` and `max` at their sentinel extremes (`Double.MaxValue` and `Double.MinValue`). Skip zero-count points when plotting, or bind only where you will record.
|
|
178
|
+
|
|
179
|
+
For a name-based label API — declare label names once, then pass values positionally — see [Labeled Instruments](./labeled-instruments.md).
|
|
180
|
+
|
|
181
|
+
## Collection
|
|
182
|
+
|
|
183
|
+
`MetricReader.collectAllMetrics()` calls each registered instrument's `collect()`, producing one `MetricData` per instrument: `SumData` for `Counter` and `UpDownCounter`, `HistogramData` for `Histogram`, and `GaugeData` for `Gauge`. Pattern-match to read the data points — see [MetricData](./metric-data.md) for the point structure. Only instruments obtained from `metric.*` or a `Meter` builder are registered; do not construct an instrument directly, as an unregistered one never reaches `collectAllMetrics()`.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: labeled-instruments
|
|
3
|
+
title: "Labeled Instruments"
|
|
4
|
+
description: "Metric instruments with pre-declared label names. Record values positionally; runtime arity validation prevents label mismatches."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Application Metrics"
|
|
7
|
+
- "Dimensional Metrics"
|
|
8
|
+
- "Prometheus-Style Labels"
|
|
9
|
+
- "Labeled Instruments"
|
|
10
|
+
sidebar_label: "Labeled Instruments"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A labeled instrument is an ordinary counter, histogram, or gauge that already knows its label *names*. You declare them once — `"method"`, `"status"` — and then pass only values: `add(1, "GET", "200")`.
|
|
14
|
+
|
|
15
|
+
It exists to keep call sites short and consistent: the names live in one place, so every recording site is guaranteed to use the same label keys in the same order, and reading `add(1, "GET", "200")` is quicker than reading a line of repeated pairs.
|
|
16
|
+
|
|
17
|
+
Do not reach for it as an optimization. The pair form (`add(1, "method" -> "GET", …)`) builds its `Attributes` through a pooled, thread-local builder, while the labeled form allocates a fresh builder and a fresh `Attributes` on every call — so labeled recording allocates more, not less. What you give up is safety: passing the wrong *number* of values throws right at the recording call, but passing them in the wrong *order* records silently — swap two and you get a plausible-looking series that's simply wrong. When a label combination really is hot, `bind` is the answer: it resolves the attribute set once and hands back a `Bound*` that writes straight to the accumulator. A [`Meter`](./meter.md) creates labeled instruments, through `labeledCounter`, `labeledHistogram`, and `labeledGauge`.
|
|
18
|
+
|
|
19
|
+
```scala
|
|
20
|
+
final class LabeledCounter private[telemetry] (...) {
|
|
21
|
+
val labelNames: Array[String]
|
|
22
|
+
def add(value: Long, labelValues: Any*): Unit
|
|
23
|
+
def bind(labelValues: Any*): BoundCounter
|
|
24
|
+
def collect(): MetricData
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
final class LabeledHistogram private[telemetry] (...) {
|
|
28
|
+
val labelNames: Array[String]
|
|
29
|
+
def record(value: Double, labelValues: Any*): Unit
|
|
30
|
+
def bind(labelValues: Any*): BoundHistogram
|
|
31
|
+
def collect(): MetricData
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
final class LabeledGauge private[telemetry] (...) {
|
|
35
|
+
val labelNames: Array[String]
|
|
36
|
+
def record(value: Double, labelValues: Any*): Unit
|
|
37
|
+
def bind(labelValues: Any*): BoundGauge
|
|
38
|
+
def collect(): MetricData
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
Declare the label names once, then supply values in that same order on each call. The count of `labelValues` must match the declared `labelNames`; a mismatch throws `IllegalArgumentException` at the recording call site (not at construction), so a wrong arity surfaces immediately in tests.
|
|
45
|
+
|
|
46
|
+
```scala
|
|
47
|
+
import zio.blocks.telemetry._
|
|
48
|
+
|
|
49
|
+
val meter = MeterProvider.builder.build().get("com.example")
|
|
50
|
+
|
|
51
|
+
// "GET" → "method", "200" → "status"
|
|
52
|
+
val reqs = meter.labeledCounter("http.requests", "method", "status")
|
|
53
|
+
reqs.add(1L, "GET", "200")
|
|
54
|
+
reqs.add(1L, "POST", "500")
|
|
55
|
+
|
|
56
|
+
val lat = meter.labeledHistogram("request.latency", "route")
|
|
57
|
+
lat.record(15.0, "/orders")
|
|
58
|
+
|
|
59
|
+
val depth = meter.labeledGauge("queue.depth", "queue")
|
|
60
|
+
depth.record(7.0, "orders")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For a set of label values recorded over and over, `bind(labelValues*)` builds the `Attributes` once and returns a bound instrument that skips that work on each later call:
|
|
64
|
+
|
|
65
|
+
```scala
|
|
66
|
+
import zio.blocks.telemetry._
|
|
67
|
+
|
|
68
|
+
val reqs = MeterProvider.builder.build().get("com.example").labeledCounter("rpc.calls", "service", "method")
|
|
69
|
+
val bound = reqs.bind("OrderService", "place")
|
|
70
|
+
bound.add(1L)
|
|
71
|
+
bound.add(1L)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Values flow through the wrapped `Counter`, `Histogram`, or `Gauge` and are collected by `MetricReader.collectAllMetrics()` like any other instrument — see [MetricData](./metric-data.md).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: meter-provider
|
|
3
|
+
title: "MeterProvider"
|
|
4
|
+
description: "Configure metrics once for a service: name what produced them, hand out meters per component, and read every measurement back through one reader."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Application Metrics"
|
|
7
|
+
- "Metrics Configuration"
|
|
8
|
+
- "Meter Factory"
|
|
9
|
+
- "MeterProvider"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `MeterProvider` is where metrics get configured, once, at startup. You build one, and it hands out the [`Meter`](./meter.md)s your components take.
|
|
13
|
+
|
|
14
|
+
It exists because two things can't be decided per instrument. The first is **identity**: an exported measurement has to say what produced it, or a backend shows a number with no owner — and repeating that on every counter would be both tedious and inconsistent. You set it once as a [`Resource`](../common/resource.md) on the provider. Unlike spans and log records, collected metric data does not carry it, so read `provider.resource` when you export and attach it there.
|
|
15
|
+
|
|
16
|
+
The second is **one place to read from**. Instruments end up scattered across components, and nothing useful happens until something collects them all together. The provider keeps the registry every meter joins, so a single `reader.collectAllMetrics()` returns every measurement from every scope beneath it — you never assemble a list of instruments yourself.
|
|
17
|
+
|
|
18
|
+
Most applications never hold one: they hand a provider to `metric.install(...)` at startup and use the global [`metric`](./index.md) everywhere after. Build one yourself when you want a `Resource` of your own, or a scope you can collect and shut down independently of the global one.
|
|
19
|
+
|
|
20
|
+
## Configuring Metrics for Your Service
|
|
21
|
+
|
|
22
|
+
Build the provider with a `Resource` naming your service:
|
|
23
|
+
|
|
24
|
+
```scala
|
|
25
|
+
import zio.blocks.telemetry._
|
|
26
|
+
|
|
27
|
+
val provider = MeterProvider.builder
|
|
28
|
+
.setResource(Resource.create(Attributes.of(Attributes.ServiceName, "payments")))
|
|
29
|
+
.build()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Do set the name. Skipping it doesn't fail — the default `Resource` supplies `service.name = "unknown_service"` alongside SDK version attributes — but that placeholder is what a backend will show for every measurement you export.
|
|
33
|
+
|
|
34
|
+
## Getting a Meter per Component
|
|
35
|
+
|
|
36
|
+
Ask for a meter by the component's name; that name is what attributes its measurements:
|
|
37
|
+
|
|
38
|
+
```scala
|
|
39
|
+
import zio.blocks.telemetry._
|
|
40
|
+
|
|
41
|
+
val provider = MeterProvider.builder.build()
|
|
42
|
+
|
|
43
|
+
val meter: Meter = provider.get("com.example.payments")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The full signature is `get(name: String, version: String = "")`; passing a version records it on the scope, which is how you tell measurements from two releases of the same library apart. Asking twice for the same name *and* version returns the same meter, so components take theirs independently without coordinating. The provider also exposes the `Resource` you built it with as `provider.resource`. See [Meter](./meter.md) for what to do with a meter.
|
|
47
|
+
|
|
48
|
+
## Reading Everything Back
|
|
49
|
+
|
|
50
|
+
`reader` is a single [`MetricReader`](./metric-data.md) for the provider's whole lifetime, created at `build()` — there is nothing to register, and no second reader to keep in sync:
|
|
51
|
+
|
|
52
|
+
```scala
|
|
53
|
+
import zio.blocks.telemetry._
|
|
54
|
+
|
|
55
|
+
val provider = MeterProvider.builder.build()
|
|
56
|
+
provider.get("com.example").counterBuilder("ops").build().add(1L)
|
|
57
|
+
|
|
58
|
+
val snapshots: Seq[MetricData] = provider.reader.collectAllMetrics()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Each call aggregates what has accumulated so far into [`MetricData`](./metric-data.md) — one value per registered instrument, across every meter. This is a pull: nothing is sent anywhere until you or an exporter asks.
|
|
62
|
+
|
|
63
|
+
## Shutting Down
|
|
64
|
+
|
|
65
|
+
A provider is `AutoCloseable`, with `close()` calling `shutdown()`, so it fits `scala.util.Using.resource`:
|
|
66
|
+
|
|
67
|
+
```scala
|
|
68
|
+
import zio.blocks.telemetry._
|
|
69
|
+
import scala.util.Using
|
|
70
|
+
|
|
71
|
+
Using.resource(MeterProvider.builder.build()) { provider =>
|
|
72
|
+
provider.reader.collectAllMetrics()
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Unlike logging, there is nothing buffered here waiting to be flushed — metrics are read on demand, so the built-in reader's shutdown does no work today. Call it anyway at exit: it costs nothing, and it's what an export pipeline would hook into.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: meter
|
|
3
|
+
title: "Meter"
|
|
4
|
+
description: "Create the counters, histograms, and gauges one component records through — scoped to that component, and registered so collection finds them."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Application Metrics"
|
|
7
|
+
- "Instrument Registration"
|
|
8
|
+
- "Metric Instruments"
|
|
9
|
+
- "Meter"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `Meter` is where one component's [instruments](./instruments.md) come from. You take a meter under your component's name, create counters and histograms from it, and record through those.
|
|
13
|
+
|
|
14
|
+
It exists to answer a question a bare instrument name can't: *whose measurement is this?* A meter carries a scope name, so the instruments one component builds are grouped under it, and a reader can tell your `requests` counter from the one a library you depend on registered. (The two were never merged into one series — each instrument aggregates on its own — but without scopes there is nothing in the code that says which is which.)
|
|
15
|
+
|
|
16
|
+
The second problem it solves is reachability. Recording a number is useless if nothing can read it back, and an instrument only reaches collection if something registered it. A meter is that something: it is registered with its [`MeterProvider`](./meter-provider.md) when you obtain it, and it registers every instrument you build from it, so a measurement's path to `reader.collectAllMetrics()` is complete the moment you call `build()` — with no wiring step you could forget.
|
|
17
|
+
|
|
18
|
+
## Taking a Meter
|
|
19
|
+
|
|
20
|
+
Ask a provider for one by name, or `metric.get(name)` in application code. Use the component's package as the name:
|
|
21
|
+
|
|
22
|
+
```scala
|
|
23
|
+
import zio.blocks.telemetry._
|
|
24
|
+
|
|
25
|
+
val meter: Meter = MeterProvider.builder.build().get("com.example.server")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Asking twice for the same name gives you back the *same* meter, not a second one — meters are cached per scope. So separate call sites in one component can each take their meter without coordinating, and their instruments still land under a single scope.
|
|
29
|
+
|
|
30
|
+
## Creating an Instrument
|
|
31
|
+
|
|
32
|
+
Each of the four instrument kinds has a builder. Name it, optionally describe it and give it a unit, then `build()`:
|
|
33
|
+
|
|
34
|
+
```scala
|
|
35
|
+
import zio.blocks.telemetry._
|
|
36
|
+
|
|
37
|
+
val meter = MeterProvider.builder.build().get("com.example.server")
|
|
38
|
+
|
|
39
|
+
val requests = meter.counterBuilder("http.requests")
|
|
40
|
+
.setDescription("Total HTTP requests")
|
|
41
|
+
.setUnit("1")
|
|
42
|
+
.build()
|
|
43
|
+
requests.add(1L, "method" -> "GET", "status" -> "200")
|
|
44
|
+
|
|
45
|
+
val latency = meter.histogramBuilder("request.latency").setUnit("ms").build()
|
|
46
|
+
latency.record(42.5, "route" -> "/api/orders")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The description and unit stay on the instrument — `collect()` returns data points only — so they reach a dashboard through whatever exporter reads the instrument, which is what lets it label an axis in milliseconds instead of showing bare numbers. Build each instrument once and hold the result — a `val` on the component that records through it. Building the same name twice gives you two registered instruments, which splits one logical metric into two series that no consumer can merge back together.
|
|
50
|
+
|
|
51
|
+
## Recording on a Hot Path
|
|
52
|
+
|
|
53
|
+
When the same label *names* repeat on every call, declaring them once keeps every call site consistent and short. `labeledCounter`, `labeledHistogram`, and `labeledGauge` fix the names at construction so callers pass just the values, positionally:
|
|
54
|
+
|
|
55
|
+
```scala
|
|
56
|
+
import zio.blocks.telemetry._
|
|
57
|
+
|
|
58
|
+
val meter = MeterProvider.builder.build().get("com.example")
|
|
59
|
+
|
|
60
|
+
val byRoute = meter.labeledCounter("http.requests", "method", "status")
|
|
61
|
+
byRoute.add(1L, "GET", "200")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
See [Labeled Instruments](./labeled-instruments.md) for the trade-offs; for ordinary recording the tuple form above is simpler.
|
|
65
|
+
|
|
66
|
+
## Reporting a Value You Don't Push
|
|
67
|
+
|
|
68
|
+
Some numbers aren't events you count — they're state you can read at any time, like a cache's size or a pool's idle connections. Instead of pushing an update whenever it changes, `buildWithCallback` on the counter, up-down counter, and gauge builders makes an instrument that asks *you* for the value at collection time:
|
|
69
|
+
|
|
70
|
+
```scala
|
|
71
|
+
import zio.blocks.telemetry._
|
|
72
|
+
|
|
73
|
+
val meter = MeterProvider.builder.build().get("com.example")
|
|
74
|
+
val cache = scala.collection.mutable.Map("k" -> "v")
|
|
75
|
+
|
|
76
|
+
meter.gaugeBuilder("cache.entries").buildWithCallback { observer =>
|
|
77
|
+
observer.record(cache.size.toDouble, Attributes.empty)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The block doesn't run when you build it — it runs once per collection, reading `cache.size` fresh each time, so the value can't go stale and you need no hook at every mutation. Keep it cheap and side-effect-free; it runs on the collecting thread. You can discard the returned instrument — the meter registered it, so collection finds it — and hold it only if you want to call its own `collect()` directly.
|
|
82
|
+
|
|
83
|
+
`buildWithCallback` returns an `ObservableCounter`, `ObservableUpDownCounter`, or `ObservableGauge` depending on the builder, and hands your block an `ObservableCallback` to record through. Histograms have no callback form, since a distribution has to see every observation as it happens.
|
|
84
|
+
|
|
85
|
+
Watch the type on the counters: both observable counters round what you report to a whole number, so a callback recording `1.5` is collected as `2`. Only `ObservableGauge` keeps the `Double` as given.
|
|
86
|
+
|
|
87
|
+
## Two Ways to Lose Measurements
|
|
88
|
+
|
|
89
|
+
Both come from an instrument that records into nothing:
|
|
90
|
+
|
|
91
|
+
1. **Constructing an instrument directly.** `Counter("http.requests", "", "")` compiles, because the companion `apply` is public. It records perfectly well into an object no meter registered, so `collectAllMetrics()` never sees it. Go through a meter or `metric.*` — the one case that justifies direct construction is a [histogram with custom bucket boundaries](./instruments.md), where you accept the loss of registration and call `collect()` yourself.
|
|
92
|
+
2. **Crossing providers.** An instrument reaches only the reader of the provider whose meter built it. `metric.install(...)` swaps in a provider with an empty registry, so take your meters and build your instruments *after* installing.
|
|
93
|
+
|
|
94
|
+
## See Also
|
|
95
|
+
|
|
96
|
+
- [Instruments](./instruments.md) — the recording API of each instrument kind
|
|
97
|
+
- [MeterProvider](./meter-provider.md) — where meters come from, and what configures them
|
|
98
|
+
- [MetricData](./metric-data.md) — what collection hands back
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: metric-data
|
|
3
|
+
title: "MetricData"
|
|
4
|
+
description: "Aggregated metric snapshot produced by MetricReader.collectAllMetrics(). Three variants: SumData, HistogramData, GaugeData."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Application Metrics"
|
|
7
|
+
- "Metric Export"
|
|
8
|
+
- "Aggregated Snapshot"
|
|
9
|
+
- "MetricData"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`MetricData` is an aggregated snapshot of one instrument at one collect cycle — its accumulated points, keyed by label set. You rarely build it directly: it is what `MetricReader.collectAllMetrics()` returns, one value per registered instrument, for a test to assert on or an export pipeline to serialize. Each collection builds fresh points, though a histogram point exposes its `boundaries` and `bucketCounts` arrays as-is, so read them and don't write to them.
|
|
13
|
+
|
|
14
|
+
One thing it does *not* carry is the instrument's name, description, or unit — a snapshot is only points and their label sets. So `collectAllMetrics()` hands back a `Seq[MetricData]` with no way to tell which instrument each entry came from, which is why an export pipeline pairs them with names of its own before serializing. In a test, collect from a single instrument with its own `collect()` when you need to know the source.
|
|
15
|
+
|
|
16
|
+
Its three variants line up with the instrument that produced them:
|
|
17
|
+
|
|
18
|
+
- `SumData` — from a [`Counter`](./instruments.md) or an `UpDownCounter`
|
|
19
|
+
- `HistogramData` — from a `Histogram`
|
|
20
|
+
- `GaugeData` — from a `Gauge`
|
|
21
|
+
|
|
22
|
+
So a `match` on `MetricData` tells you both the shape of the numbers and the kind of instrument they came from.
|
|
23
|
+
|
|
24
|
+
```scala
|
|
25
|
+
sealed trait MetricData
|
|
26
|
+
|
|
27
|
+
object MetricData {
|
|
28
|
+
final case class SumData(points: List[SumDataPoint]) extends MetricData
|
|
29
|
+
final case class HistogramData(points: List[HistogramDataPoint]) extends MetricData
|
|
30
|
+
final case class GaugeData(points: List[GaugeDataPoint]) extends MetricData
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
final case class SumDataPoint(
|
|
34
|
+
attributes: Attributes,
|
|
35
|
+
startTimeNanos: Long,
|
|
36
|
+
timeNanos: Long,
|
|
37
|
+
value: Long // cumulative sum
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
final case class HistogramDataPoint(
|
|
41
|
+
attributes: Attributes,
|
|
42
|
+
startTimeNanos: Long,
|
|
43
|
+
timeNanos: Long,
|
|
44
|
+
count: Long, // total number of observations
|
|
45
|
+
sum: Double, // sum of all observations
|
|
46
|
+
min: Double, // smallest observation
|
|
47
|
+
max: Double, // largest observation
|
|
48
|
+
bucketCounts: Array[Long], // count per bucket (length == boundaries.length + 1)
|
|
49
|
+
boundaries: Array[Double] // the bucket upper boundaries
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
final case class GaugeDataPoint(
|
|
53
|
+
attributes: Attributes,
|
|
54
|
+
timeNanos: Long,
|
|
55
|
+
value: Double // most recently recorded value
|
|
56
|
+
)
|
|
57
|
+
```
|