feat: metrics instrumentation via typed EventEmitter#1751
Conversation
|
This appears very targeted for Prometheus use (it is also not a no-op). We should also be very careful about possible performance degradation (be it small but widespread, or very large). Since this is "extra", it should have a very minimal impact overall. |
|
Hi @Nerivec! Thank you for your feedback - I mostly just opened this PR to see if you were open to this and get feedback on the direction to go.
I tried to make it as non-prometheus specific as I could - e.g. not encoding counter, gauge or histogram specifics, but instead specific events to measure. I'll admit I'm only familiar with Prometheus and OpenTelemetry, but if you have any other suggestions please let me know.
I tried to make the calling sites as simple as possible (e.g. not having each one check if the metrics received is null but instead of having a no-op receiver) - but I could change that? WDYT? I could also change the instrumentation of the adapter so that it only gets added if there is a metrics callback?
100%. By "the usual ZH events API" do you mean e.g. making the Metrics interface follow the same pattern as ControllerEventMap and emit? |
| requestQueueDuration: [data: RequestQueueDurationPayload]; | ||
| } | ||
|
|
||
| export const metrics = new EventEmitter<MetricsEventMap>(); |
There was a problem hiding this comment.
Use of this in e.g. Z2M would require to import {metrics} from "zigbee-herdsman";, i.e. entirely separate from Controller (main entrypoint). I think that's fine for this purpose though.
Note: need to be sure we can easily add a mock in Z2M tests (similar to Controller), no matter the design we end up with.
| durationSeconds: number; | ||
| } | ||
|
|
||
| export interface MetricsEventMap { |
There was a problem hiding this comment.
I'm not sure about this one.
Is it better to have one event, with a discriminator type in the payload?
Current would require a lot of .on("...", ...), not sure that's ideal for metrics (same "end logic").
There was a problem hiding this comment.
Should also identify available adapter's protocol-specific metrics.
I'm not sure if any other than ember currently have any, but currently we have a 3rd party tool to parse this from logs. Would be much better integrated with metrics directly.
|
Thanks for the review feedback! I'll work through it and integrate the changes into Koenkk/zigbee2mqtt#31645. I've got a build of the HA add on for testing in my local setup too, and need to build some dashboards etc.. so probably a few more days before I've got it working end to end. |
Adds a Prometheus-style Metrics interface (src/utils/metrics.ts) with one method per observable event, following the existing Logger pattern. Instrumentation covers: - Adapter sends (ZCL unicast/group/broadcast, ZDO): success/failure status and duration, via a Proxy-based wrapper (src/adapter/metricsAdapter.ts) that requires no changes to any concrete adapter implementation. - RequestQueue: queue depth gauge and time-in-queue histogram per device endpoint, tracked with a non-enumerable WeakMap to avoid serialisation side-effects. - Retries: per-adapter counters with reason labels in z-stack, ember, zigate, zboss, and zoh adapters (deconz has no retry logic). Consumers inject an implementation via setMetrics(); the default is a no-op so there is no overhead when metrics are not configured. Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
- Update biome.json schema to 2.4.13 - Fix import sort order in metricsAdapter.ts, controller.ts, index.ts - Replace banned Function type with (...args: never) => unknown - Reformat instrumentSend signature to single line per biome style Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
…rapping opt-in Replaces the injectable `Metrics` interface and `setMetrics`/`noopMetrics` pattern with a typed `EventEmitter<MetricsEventMap>` singleton. Adapter metrics wrapping is now opt-in via `enableMetrics: true` in Controller options (defaults to false). Also instruments ZDO responses and ZCL payload receives, and adds tests for the new EventEmitter-based metrics events. Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
Simpler and more cohesive to store enqueuedAt on the Request itself, alongside the existing expires timestamp, rather than in a parallel non-enumerable WeakMap on RequestQueue. Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
…controller Replace the Proxy-based adapter wrapper and opt-in enableMetrics option with direct instrumentSend calls at each adapter send site in the controller, consistent with how receive/queue/retry metrics are already emitted. Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
94c8e6d to
ee1dc06
Compare
Summary
Metricsinterface /setMetrics/noopMetricspattern with a typedEventEmitter<MetricsEventMap>singleton exported asmetricssendZclFrameToEndpoint,sendZdo,sendZclFrameToGroup,sendZclFrameToAll) directly in the controller via a sharedinstrumentSendhelper, with timing and success/failure statusenqueuedAt) stored directly onRequestalongsideexpires, replacing a parallelWeakMaponRequestQueuemetrics.on("adapterSendZclUnicast", (data) => ...)etc. — metrics are always emitted unconditionallyTest plan
npm test— all 1744 tests passmetrics.on(...)events and verify they fire on adapter sends🤖 Generated with Claude Code