Table of Contents

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

meterFactory IMeterFactory

The 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

string

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

string

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

milliseconds double

The elapsed time, computed from Stopwatch.GetTimestamp().

session KeyValuePair<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

session KeyValuePair<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

count long

How many records were drained since the previous flush.

session KeyValuePair<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

session KeyValuePair<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.