Skip to content

Commit f9a1462

Browse files
committed
feat(telemetry): standardize emitted metric names and add messenger metric
- rename metrics to namespaced keys: flow.etl.rows.*, flow.cache.*, flow.filesystem.*, flow.phpunit.*, db.client.* - emit messaging metrics for consumed/sent messages and process duration - add messenger `metrics` and `metrics_duration_unit` config (s|ms) - remove messenger `link_to_worker` and the `flow.messenger.worker` span link - add histogram bucket boundaries to postgresql duration/row metrics - document new metrics and add upgrade notes
1 parent 29a69d6 commit f9a1462

35 files changed

Lines changed: 691 additions & 231 deletions

File tree

documentation/components/bridges/phpunit-telemetry-bridge.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,10 @@ Each span includes attributes like:
269269

270270
When enabled, the extension records:
271271

272-
- `phpunit.suite.duration` - Histogram of suite execution time
273-
- `phpunit.suite.test_count` - Counter of tests per suite
274-
- `phpunit.test.duration` - Histogram of individual test execution time
275-
- `phpunit.test.count` - Counter of tests by status
272+
- `flow.phpunit.suite.duration` - Histogram of suite execution time
273+
- `flow.phpunit.suite.test_count` - Counter of tests per suite
274+
- `flow.phpunit.test.duration` - Histogram of individual test execution time
275+
- `flow.phpunit.test.count` - Counter of tests by status
276276

277277
## Running with Docker Compose
278278

documentation/components/bridges/symfony-telemetry-bundle.md

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ flow_telemetry:
122122
runtime_mode: auto # auto|classic|worker
123123
```
124124

125-
| Mode | Behaviour |
126-
|-----------|----------------------------------------------------------------------------------------------------|
127-
| `auto` | Detect the runtime per request and pick `worker` or `classic` accordingly (default). |
128-
| `classic` | One process per request: shut telemetry down on terminate (full flush + transport close). |
129-
| `worker` | Long-running runtime: flush on terminate, drain async transports, never shut the transport down. |
125+
| Mode | Behaviour |
126+
|-----------|--------------------------------------------------------------------------------------------------|
127+
| `auto` | Detect the runtime per request and pick `worker` or `classic` accordingly (default). |
128+
| `classic` | One process per request: shut telemetry down on terminate (full flush + transport close). |
129+
| `worker` | Long-running runtime: flush on terminate, drain async transports, never shut the transport down. |
130130

131131
`auto` detection looks for the Symfony Runtime worker signal (`APP_RUNTIME_MODE` containing `worker=1`),
132132
FrankenPHP (`FRANKENPHP_WORKER`), and RoadRunner (`RR_MODE`); when none are present it falls back to `classic`.
@@ -1260,9 +1260,10 @@ flow_telemetry:
12601260
instrumentation:
12611261
messenger:
12621262
enabled: true
1263-
context_propagation: true # Propagate context across message boundaries
1264-
propagation_style: link # How the consumer span relates to the producer span
1265-
link_to_worker: true # Link each message trace back to the messenger:consume worker span
1263+
context_propagation: true # Propagate context across message boundaries
1264+
propagation_style: link # How the consumer span relates to the producer span
1265+
metrics: true # Emit messaging metrics (default true)
1266+
metrics_duration_unit: s # process.duration histogram unit: 's' (OTEL semconv, default) or 'ms'
12661267
```
12671268

12681269
When `context_propagation` is enabled, `propagation_style` controls how a consumed message's span
@@ -1279,10 +1280,6 @@ relates to the producing (publishing) span:
12791280
`propagation_style` has no effect when `context_propagation` is `false` (there is nothing to relate to).
12801281
The producer side is identical in both modes — the telemetry stamp is always written on dispatch.
12811282

1282-
`link_to_worker` (default `true`) adds a span link from each consumed message's trace back to the active
1283-
`messenger:consume` worker span, so you can pivot from a message trace to the worker that processed it.
1284-
Set it to `false` to omit the link (e.g. if the consume command is not traced).
1285-
12861283
Buffered telemetry is flushed **after each handled or failed message** while the worker keeps running, so
12871284
consumer-side traces/logs/metrics export promptly instead of only when the `messenger:consume` worker
12881285
stops. (Without it, signals emitted inside handlers sit in the batching processors — default batch size
@@ -1302,6 +1299,17 @@ to keep that sub-millisecond (see [Timeouts](#timeouts)).
13021299
With the `async_curl` transport, the subscriber also pumps each transport's `tick()` on every `WorkerRunningEvent`, so
13031300
in-flight requests complete in the background.
13041301

1302+
**Metrics.**
1303+
1304+
| Metric | Instrument | Unit | Emitted | Attributes |
1305+
|--------------------------------------|------------|---------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
1306+
| `messaging.client.consumed.messages` | Counter | `{message}` | once per **received** message | `messaging.system`, `messaging.operation.name=process`, `messaging.destination.name`, `messaging.consumer.group.name` (transport) |
1307+
| `messaging.process.duration` | Histogram | `s` (or `ms`) | per **received** message | same as above, plus `error.type` when the handler fails |
1308+
| `messaging.client.sent.messages` | Counter | `{message}` | once per **sent** message | `messaging.system`, `messaging.operation.name=send`, `messaging.destination.name` |
1309+
1310+
`metrics_duration_unit` controls the `messaging.process.duration` histogram: `s` (default) uses seconds with the
1311+
OTEL-recommended bucket boundaries; `ms` uses milliseconds with Flow's native histogram buckets.
1312+
13051313
#### Twig
13061314

13071315
Traces Twig template rendering.
@@ -1376,6 +1384,14 @@ flow_telemetry:
13761384
- '/^cache\.validator.*/'
13771385
```
13781386

1387+
Besides spans, the cache instrumentation emits hit/miss counters (a `meter_provider` must be configured for them
1388+
to be exported):
1389+
1390+
| Metric | Instrument | Unit | Emitted |
1391+
|---------------------|------------|--------------|---------------------|
1392+
| `flow.cache.hits` | Counter | `operations` | once per cache hit |
1393+
| `flow.cache.misses` | Counter | `operations` | once per cache miss |
1394+
13791395
### Web Profiler
13801396

13811397
Adds a **Flow Telemetry** panel to the Symfony Web Profiler showing the signals captured during the
@@ -1719,10 +1735,12 @@ request continues the trace automatically (`context_propagation` extracts them):
17191735
const headers = {};
17201736
document
17211737
.querySelectorAll('meta[name="traceparent"], meta[name="tracestate"], meta[name="baggage"]')
1722-
.forEach((meta) => { headers[meta.name] = meta.content; });
1738+
.forEach((meta) => {
1739+
headers[meta.name] = meta.content;
1740+
});
17231741
17241742
// Only send to your own API origins so trace IDs do not leak to third parties.
1725-
fetch('/api/orders', { headers });
1743+
fetch('/api/orders', {headers});
17261744
```
17271745

17281746
**Links / multi-step flows (query string).** A normal navigation cannot send headers, so carry the

documentation/components/libs/postgresql/client-telemetry.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,14 @@ Query text is automatically truncated to `maxQueryLength` (default: 1000 charact
198198

199199
When `collectMetrics` is enabled, two histograms are recorded:
200200

201-
### `operation_duration`
201+
### `db.client.operation.duration`
202202

203203
Duration of database operations in seconds.
204204

205205
- **Unit**: `s` (seconds)
206206
- **Attributes**: `db.system.name`, `db.namespace`, `db.operation.name`, `server.address`
207207

208-
### `response_returned_rows`
208+
### `db.client.response.returned_rows`
209209

210210
Number of rows returned by database operations.
211211

documentation/upgrading.md

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ subsequent spans in the same trace.
7878

7979
`continue` mode still joins the producer's trace.
8080

81-
### 5) `flow-php/telemetry-otlp-bridge`, `flow-php/symfony-telemetry-bundle` - curl is synchronous; async moved to `async_curl`
81+
### 5) `flow-php/telemetry-otlp-bridge`, `flow-php/symfony-telemetry-bundle` - curl is synchronous; async moved to
82+
83+
`async_curl`
8284

8385
| Before | After |
8486
|----------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
@@ -107,12 +109,58 @@ flow_postgresql:
107109
108110
### 7) `flow-php/symfony-telemetry-bundle` - static resource cache file is keyed by kernel environment
109111

110-
| Before | After |
111-
|---------------------------------------------------------|-------------------------------------------------------------------|
112-
| `sys_get_temp_dir()/flow_telemetry_resource.cache` | `sys_get_temp_dir()/flow_telemetry_resource_<kernel.env>.cache` |
112+
| Before | After |
113+
|----------------------------------------------------|-----------------------------------------------------------------|
114+
| `sys_get_temp_dir()/flow_telemetry_resource.cache` | `sys_get_temp_dir()/flow_telemetry_resource_<kernel.env>.cache` |
113115

114116
Delete the orphaned `flow_telemetry_resource.cache` from the temp dir; a per-env file is written on the next run.
115117

118+
### 8) `flow-php/symfony-telemetry-bundle` - removed messenger `link_to_worker` and the `flow.messenger.worker` link
119+
120+
| Before | After |
121+
|--------------------------------------------------|---------|
122+
| `instrumentation.messenger.link_to_worker: true` | removed |
123+
| consumer span link `flow.messenger.worker` | removed |
124+
125+
Remove the `link_to_worker` key from config — there is no replacement. Consumed messages remain their own trace
126+
linked to the producer (`link` mode). To keep a worker's trace list clean, give the Messenger transport a
127+
telemetry-disabled connection and/or exclude `messenger:consume` via `instrumentation.console.exclude_commands`.
128+
129+
### 9) `flow-php/symfony-telemetry-bundle` - messenger instrumentation also emits messaging metrics
130+
131+
| Before | After |
132+
|----------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
133+
| messenger instrumentation emitted spans only | also emits `messaging.client.consumed.messages`, `messaging.client.sent.messages`, `messaging.process.duration` |
134+
135+
Disable with `instrumentation.messenger.metrics: false`; set the `messaging.process.duration` unit via
136+
`instrumentation.messenger.metrics_duration_unit` (`s` default, or `ms`).
137+
138+
### 10) `flow-php/etl`, `flow-php/filesystem`, `flow-php/postgresql`, `flow-php/symfony-telemetry-bundle`,
139+
`flow-php/phpunit-telemetry-bridge` - emitted metric names standardized
140+
141+
| Before | After |
142+
|-----------------------------|------------------------------------|
143+
| `rows_processed` | `flow.etl.rows.processed` |
144+
| `rows_throughput` | `flow.etl.rows.throughput` |
145+
| `cache_hits` | `flow.cache.hits` |
146+
| `cache_misses` | `flow.cache.misses` |
147+
| `cache.hits` | `flow.cache.hits` |
148+
| `cache.misses` | `flow.cache.misses` |
149+
| `write_size` | `flow.filesystem.write.size` |
150+
| `write_operations` | `flow.filesystem.write.operations` |
151+
| `read_size` | `flow.filesystem.read.size` |
152+
| `read_operations` | `flow.filesystem.read.operations` |
153+
| `operation_duration` | `db.client.operation.duration` |
154+
| `response_returned_rows` | `db.client.response.returned_rows` |
155+
| `phpunit.test.duration` | `flow.phpunit.test.duration` |
156+
| `phpunit.test.count` | `flow.phpunit.test.count` |
157+
| `phpunit.test.memory.peak` | `flow.phpunit.test.memory.peak` |
158+
| `phpunit.test.memory.delta` | `flow.phpunit.test.memory.delta` |
159+
| `phpunit.suite.duration` | `flow.phpunit.suite.duration` |
160+
| `phpunit.suite.test_count` | `flow.phpunit.suite.test_count` |
161+
162+
Rename these series in dashboards and alerts.
163+
116164
---
117165

118166
## Upgrading from 0.39.x to 0.40.x

src/bridge/phpunit/telemetry/src/Flow/Bridge/PHPUnit/Telemetry/Subscriber/TestFinishedSubscriber.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ public function notify(Finished $event): void
4343
if ($this->config->emitMetrics) {
4444
$meter = $this->telemetry->meter('phpunit', PackageVersion::get('phpunit/phpunit'));
4545

46-
$meter->createCounter('phpunit.test.count')->add(1, ['test.status' => $status]);
47-
$meter->createHistogram('phpunit.test.memory.peak', 'bytes')->record($peakBytes, [
46+
$meter->createCounter('flow.phpunit.test.count')->add(1, ['test.status' => $status]);
47+
$meter->createHistogram('flow.phpunit.test.memory.peak', 'bytes')->record($peakBytes, [
4848
'test.status' => $status,
4949
]);
5050

5151
if ($deltaBytes !== null) {
52-
$meter->createHistogram('phpunit.test.memory.delta', 'bytes')->record($deltaBytes, [
52+
$meter->createHistogram('flow.phpunit.test.memory.delta', 'bytes')->record($deltaBytes, [
5353
'test.status' => $status,
5454
]);
5555
}
@@ -98,19 +98,19 @@ public function notify(Finished $event): void
9898
$meter = $this->telemetry->meter('phpunit', PackageVersion::get('phpunit/phpunit'));
9999

100100
if ($duration !== null) {
101-
$meter->createHistogram('phpunit.test.duration', 'ms')->record($duration, [
101+
$meter->createHistogram('flow.phpunit.test.duration', 'ms')->record($duration, [
102102
'test.status' => $status,
103103
]);
104104

105-
$meter->createCounter('phpunit.test.count')->add(1, ['test.status' => $status]);
105+
$meter->createCounter('flow.phpunit.test.count')->add(1, ['test.status' => $status]);
106106
}
107107

108-
$meter->createHistogram('phpunit.test.memory.peak', 'bytes')->record($peakBytes, [
108+
$meter->createHistogram('flow.phpunit.test.memory.peak', 'bytes')->record($peakBytes, [
109109
'test.status' => $status,
110110
]);
111111

112112
if ($deltaBytes !== null) {
113-
$meter->createHistogram('phpunit.test.memory.delta', 'bytes')->record($deltaBytes, [
113+
$meter->createHistogram('flow.phpunit.test.memory.delta', 'bytes')->record($deltaBytes, [
114114
'test.status' => $status,
115115
]);
116116
}

src/bridge/phpunit/telemetry/src/Flow/Bridge/PHPUnit/Telemetry/Subscriber/TestSuiteFinishedSubscriber.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ public function notify(Finished $event): void
5555
if ($this->config->emitMetrics && $duration !== null) {
5656
$meter = $this->telemetry->meter('phpunit', $phpunitVersion);
5757

58-
$meter->createHistogram('phpunit.suite.duration', 'ms')->record($duration, [
58+
$meter->createHistogram('flow.phpunit.suite.duration', 'ms')->record($duration, [
5959
'test.suite' => $suiteName,
6060
]);
6161

62-
$meter->createCounter('phpunit.suite.test_count')->add($suite->count(), [
62+
$meter->createCounter('flow.phpunit.suite.test_count')->add($suite->count(), [
6363
'test.suite' => $suiteName,
6464
]);
6565
}

src/bridge/phpunit/telemetry/tests/Flow/Bridge/PHPUnit/Telemetry/Tests/Unit/Subscriber/TestFinishedSubscriberTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ public function test_records_memory_metrics_when_metrics_enabled(): void
123123
$subscriber->notify($event);
124124
$telemetry->flush();
125125

126-
static::assertNotEmpty($metricProcessor->metricsWithName('phpunit.test.memory.peak'));
127-
static::assertNotEmpty($metricProcessor->metricsWithName('phpunit.test.memory.delta'));
126+
static::assertNotEmpty($metricProcessor->metricsWithName('flow.phpunit.test.memory.peak'));
127+
static::assertNotEmpty($metricProcessor->metricsWithName('flow.phpunit.test.memory.delta'));
128128
}
129129

130130
public function test_records_memory_delta_metric_when_only_metrics_enabled(): void
@@ -144,8 +144,8 @@ public function test_records_memory_delta_metric_when_only_metrics_enabled(): vo
144144
$subscriber->notify($event);
145145
$telemetry->flush();
146146

147-
static::assertNotEmpty($metricProcessor->metricsWithName('phpunit.test.memory.peak'));
148-
static::assertNotEmpty($metricProcessor->metricsWithName('phpunit.test.memory.delta'));
147+
static::assertNotEmpty($metricProcessor->metricsWithName('flow.phpunit.test.memory.peak'));
148+
static::assertNotEmpty($metricProcessor->metricsWithName('flow.phpunit.test.memory.delta'));
149149
}
150150

151151
public function test_sets_error_status_and_message_on_span_for_failed_test(): void

0 commit comments

Comments
 (0)