Class CountingDispatcher
Every other Dispatcher in this module writes somewhere (a file, InfluxDB), so a run
against them measures the sink as much as the framework. This one deliberately does nothing but
increment counters, isolating the cost of the acquisition path itself: event-bus dispatch,
DataPoint parsing and the async push queue. It answers three questions per run:
- Keep-up — how many datapoints arrived, per channel, and how many the bounded push queue had to drop.
- Throughput — datapoints per second between the first and last push.
- End-to-end latency —
now − dataPoint.timestamp()at push time. Valid as an emit→sink latency whenever producer and sink share a clock (same host); across hosts it also carries the clock offset and should not be read as latency.
A summary line is printed on stop(), so a full-app deployment emits it via the
launcher's shutdown hook; in-process harnesses can read the same figures from snapshot().
Configuration keys
devices— JSON array of device identifiers whose announced channels to count.channels— JSON array of channel identifiers to count; empty counts every channel the announced devices produce.
The channel filter scopes the measurement, not the traffic: a datapoint on an
unlisted channel still crosses the bus, is still parsed, still occupies the push queue and still
counts as activity for awaitQuiescence(java.time.Duration, java.time.Duration) — it is only left out of the figures. That is what makes a filtered run comparable to an unfiltered one,
and it is why the filter is the right instrument when one producer group carries several stages
of a pipeline. A reactor network, for instance, announces every reactor's output under one group,
so an unfiltered sink mixes the intermediate results into the latency distribution of the final
one.
Threading: push(com.framed.io.dispatch.DataPoint<?>) runs on the dispatcher's single worker thread and
onDrop(com.framed.io.dispatch.DataPoint<?>, java.lang.Throwable) may run on either that thread or an event-bus handler thread; all counter updates
are taken under a private lock, so snapshot() is safe to call from any thread at any time
and always observes a mutually consistent set of figures.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordImmutable view of one measurement run. -
Field Summary
Fields inherited from class com.framed.core.Service
ADDRESS_REGISTRY_SUFFIX, eventBus, formatter, logger -
Constructor Summary
ConstructorsConstructorDescriptionCountingDispatcher(EventBus eventBus, org.json.JSONArray devices, org.json.JSONArray channels) Creates a counting sink bound to the announced channels of the given devices. -
Method Summary
Modifier and TypeMethodDescriptionbooleanawaitQuiescence(Duration quietPeriod, Duration timeout) Blocks until the sink has been idle forquietPeriod, i.e. until the push queue has drained.protected voidCounts the drop instead of printing it.protected voidonHandlerError(String deviceID, String address, Object rawMsg, Exception e) Counts the failure instead of printing a stack trace, for the same reason asonDrop(com.framed.io.dispatch.DataPoint<?>, java.lang.Throwable).voidPushes a single datapoint to the underlying sink.voidPushes a batch of datapoints to the underlying sink.voidreset()Discards everything counted so far, as if the sink had just been constructed.snapshot()Returns a consistent snapshot of everything counted so far.voidstop()Prints the run summary, then stops the push worker.Methods inherited from class com.framed.io.dispatch.Dispatcher
shutdownMethods inherited from class com.framed.core.Service
addressRegistry, announceAddress, subscribeToAnnouncements
-
Constructor Details
-
CountingDispatcher
public CountingDispatcher(EventBus eventBus, org.json.JSONArray devices, org.json.JSONArray channels) Creates a counting sink bound to the announced channels of the given devices. Parameter names match the JSON configuration keys resolved byFactory; theEventBusis injected automatically.- Parameters:
eventBus- the event bus to discover channels and receive samples ondevices- the device identifiers whose announced channels this sink binds tochannels- the channel identifiers to count; empty counts every announced channel
-
-
Method Details
-
push
Description copied from class:DispatcherPushes a single datapoint to the underlying sink.Invoked on the dispatcher's worker thread. Implementations may throw
IOExceptionto signal a transient failure, which triggers retry with exponential backoff.- Specified by:
pushin classDispatcher- Parameters:
dataPoint- the datapoint to push
-
pushBatch
Description copied from class:DispatcherPushes a batch of datapoints to the underlying sink.- Specified by:
pushBatchin classDispatcher- Parameters:
batch- the datapoints to push
-
onDrop
Counts the drop instead of printing it. A benchmark can saturate the queue by design, and the inherited stderr trace would then cost more than the work being measured.- Overrides:
onDropin classDispatcher- Parameters:
dp- the datapoint that could not be queued or pushedcause- the reason the datapoint was dropped
-
onHandlerError
Counts the failure instead of printing a stack trace, for the same reason asonDrop(com.framed.io.dispatch.DataPoint<?>, java.lang.Throwable).Not subject to the channel filter: a message that failed to parse has no channel identifier to filter on, so this count spans every channel the sink is bound to.
- Overrides:
onHandlerErrorin classDispatcher- Parameters:
deviceID- the identifier of the device whose message failed to be handledaddress- the channel address the failing message arrived onrawMsg- the raw, unparsed message payloade- the exception raised while handling the message
-
snapshot
Returns a consistent snapshot of everything counted so far. Safe to call at any time from any thread; figures are cumulative, not reset by reading.- Returns:
- the current counts, throughput and latency distribution
-
reset
public void reset()Discards everything counted so far, as if the sink had just been constructed.For harnesses that warm up or prime the pipeline before the run they actually want to measure: without it, the setup traffic would show up in the counts, in the latency distribution and — because it moves the start of the timing window earlier — in the throughput figure. Call it while the sink is quiescent; concurrent pushes race with the clear and may survive it.
-
awaitQuiescence
Blocks until the sink has been idle forquietPeriod, i.e. until the push queue has drained. Benchmarks need this before reading a finalsnapshot(): the producer finishing its last publish says nothing about the async queue behind this sink, and callingDispatcher.shutdown(Duration)instead would drop whatever is still queued.Idleness is inferred from arrivals, so this reports "drained" for a producer that has merely stalled longer than
quietPeriod. Callers that need a true end-of-stream should observe producer completion first and use this only to await the queue behind it.Every arrival counts as activity, including one the channel filter excludes from the figures: the queue this waits on is shared by the whole bound stream, so a filtered run and an unfiltered one drain at the same instant.
- Parameters:
quietPeriod- how long nothing must arrive for the sink to count as drainedtimeout- give up after this long- Returns:
trueif the sink went quiet,falseiftimeoutelapsed first- Throws:
InterruptedException- if the calling thread is interrupted while waiting
-
stop
public void stop()Prints the run summary, then stops the push worker. Invoked by the launcher's shutdown hook in a full-app deployment, which is what makes the figures visible without any extra tooling.The summary deliberately goes to
System.outrather than through the inheritedlogger:java.util.logging'sLogManagerinstalls its own shutdown hook, and once it has reset the handlers every subsequent log record is silently discarded. Since this summary is the measurement, it cannot depend on winning that race.- Overrides:
stopin classDispatcher
-
