Class LiveSessionMetrics
- Namespace
- DatabentoDotNet.Extensions.Hosting
- Assembly
- DatabentoDotNet.Extensions.Hosting.dll
The four instruments a live session publishes: records received, sessions started, reconnects attempted, and how long each flush took.
public sealed class LiveSessionMetrics : IDisposable
- Inheritance
-
LiveSessionMetrics
- Implements
- Inherited Members
Remarks
Every method here is written as though a listener is always attached, because in the test
that matters one is. Add(T) short-circuits when no
MeterListener is subscribed to the instrument — it checks a flag and returns. An
implementation that allocated per call would therefore measure as costing nothing in any test
that did not attach a listener, and would begin allocating on the first record the moment a
consumer wired up OpenTelemetry. ExtensionsAllocationTests attaches one for exactly that
reason, so the shapes below are not defensive: they are what the guarantee is made of.
The session tag arrives by in and goes straight to the single-tag
overload. That overload is the one that does not allocate. Building a tag at the call site
would, and so would TagList and the params overloads — each of which is the
obvious way to write this and each of which puts an allocation on the one path in this library
that promises none. A string sitting in the tag's object value does
not box, so the pair itself is a plain 16-byte struct copied onto the stack.
What is not here is a per-record call. The runner counts records into a long local inside its drain loop and calls RecordsReceived(long, in KeyValuePair<string, object?>) once after it, which reports the same number for a fraction of the work. Nothing in this type should ever be invited into that loop.
Constructors
LiveSessionMetrics()
Creates the instruments on a Meter this instance owns and disposes.
public LiveSessionMetrics()
Remarks
The constructor for a caller with no host — a test, a console program, a benchmark. A host
that has called AddMetrics gets the IMeterFactory overload instead, and
the container picks it without being told to: it is the constructor with the most
parameters it can satisfy.
LiveSessionMetrics(IMeterFactory)
Creates the instruments on a Meter from meterFactory.
public LiveSessionMetrics(IMeterFactory meterFactory)
Parameters
meterFactoryIMeterFactoryThe host's factory, which owns every meter it hands out.
Remarks
Dispose() does not dispose this one. The factory cached it, may hand the same instance to something else, and disposes it itself when the container is torn down — disposing it here would stop an instrument somebody else is still writing to.
Fields
MeterName
The meter every instrument here is published on:
DatabentoDotNet.Extensions.Hosting.
public const string MeterName = "DatabentoDotNet.Extensions.Hosting"
Field Value
Remarks
The value is load-bearing rather than cosmetic: it is what a consumer passes to
AddMeter when configuring OpenTelemetry, and what a
InstrumentPublished filter matches on. Renaming it silently
stops an operator's dashboards receiving anything.
SessionTagKey
The key of the tag every measurement here carries, naming the session it came from:
databento.session.
public const string SessionTagKey = "databento.session"
Field Value
Remarks
Public because the publish methods below are. Each of them takes a pre-built
KeyValuePair<TKey, TValue> — the shape is what keeps them allocation-free, see
this type's remarks — and a caller who cannot name the key cannot construct a valid argument
for a method they can otherwise call. It is also what an operator filters or groups a query
by, so it is load-bearing in the same way MeterName is: renaming it silently
splits every dashboard's series. LiveSessionRunner builds its own tag from this
constant, so the value a consumer reads here is the value on the wire.
Methods
Dispose()
Disposes the Meter, but only the one this instance created.
public void Dispose()
FlushCompleted(double, in KeyValuePair<string, object?>)
Reports how long a flush took.
public void FlushCompleted(double milliseconds, in KeyValuePair<string, object?> session)
Parameters
millisecondsdoubleThe elapsed time, computed from
Stopwatch.GetTimestamp().sessionKeyValuePair<string, object>The pre-built session tag — see the type's remarks.
Remarks
A double of milliseconds rather than a Duration or the banned
TimeSpan: OpenTelemetry's histogram buckets are numbers, and the runner's caller-side
arithmetic never names a date/time type at all.
ReconnectAttempted(in KeyValuePair<string, object?>)
Reports that the backoff is about to make an attempt.
public void ReconnectAttempted(in KeyValuePair<string, object?> session)
Parameters
sessionKeyValuePair<string, object>The pre-built session tag — see the type's remarks.
Remarks
Published before the attempt rather than after it, so a session that is failing to come back is visible while it is failing rather than only once it gives up.
RecordsReceived(long, in KeyValuePair<string, object?>)
Reports the records one flush carried.
public void RecordsReceived(long count, in KeyValuePair<string, object?> session)
Parameters
countlongHow many records were drained since the previous flush.
sessionKeyValuePair<string, object>The pre-built session tag — see the type's remarks.
Remarks
Called once per flush, never once per record. See the type's remarks for why that is a requirement rather than an optimisation.
SessionStarted(in KeyValuePair<string, object?>)
Reports that a session was established.
public void SessionStarted(in KeyValuePair<string, object?> session)
Parameters
sessionKeyValuePair<string, object>The pre-built session tag — see the type's remarks.
Remarks
One per session opened, restarts included — both the runner's first
StartSessionAsync and every successful reconnect, because a reconnect calls
StartAsync and opens a session exactly as the first one did.
That is what the counter is for, and why it cannot skip the reconnect path. Every successful restart is a newly billed session — ReconnectSupervisor says so at length, and bounds the attempts for that reason. An operator watches this counter to know how many billable sessions a process has opened, so one that under-reported on precisely the path documented as newly billed would be worse than no counter at all: it would still read as authoritative.
ReconnectAttempted(in KeyValuePair<string, object?>) cannot stand in for the difference. It counts attempts, and an attempt that fails opens nothing and bills nothing. Subtracting the two answers a third question — how much the connection flapped — rather than restating either.