java.lang.Object
io.github.romanhaendler.rbed.Detector
All Implemented Interfaces:
AutoCloseable

public final class Detector extends Object implements AutoCloseable
Receives Samples one at a time and evaluates Rules against them, reporting Event lifecycles to an EventListener. A Detector is stateful, single-use and not thread-safe: use one per data stream.
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static final class 
    Builds a Detector.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    advanceTo(long timestamp)
    Tells this Detector that time has passed up to timestamp without new Samples.
     
    void
    Ends running Events as truncated and makes this Detector reject further Samples.
    Runs a whole recorded series through this unused Detector and closes it.
    Like detect(java.lang.Iterable<io.github.romanhaendler.rbed.Sample>), for a regularly sampled series without timestamps.
    long
    The number of entries dropped because a window was full (see Detector.Builder.retentionLimit) since this Detector was built.
    How far back this Detector's configuration makes it look: the longest time window and the largest Sample count used by its Rules and watched Expressions, computed when it was built.
    void
    push(long timestamp, Map<Channel,Double> values)
    Processes one Sample.
    void
    Processes one Sample of a regularly sampled series; its timestamp is derived from the sampling rate as round(index * 1000 / hz).
    The earliest timestamp from which replaying this Detector's input into a fresh Detector (same Rules and options), with the Events that Ended before that timestamp supplied as History, reproduces this Detector exactly: the replay then continues with the same Events, callbacks, values and exceptions as this Detector.
    long
    The number of entries this Detector currently holds: one per Channel a Rule or watched Expression uses, one per value in each moving-average window and one per timestamp kept for count(...).within(...) (timestamps older than the window are dropped at every moment).
    byte[]
    Returns this Detector's complete state at the latest moment processed, as a byte array that Detector.Builder.restore turns back into a Detector.
    value(Expression expression)
    The value of a watched Expression at the latest moment processed (Sample or time advance), also after close().

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • snapshot

      public byte[] snapshot()
      Returns this Detector's complete state at the latest moment processed, as a byte array that Detector.Builder.restore turns back into a Detector.

      The guarantee: a Detector restored from a Snapshot is indistinguishable from the one the Snapshot was taken from. For every input that follows, the callbacks (kind, order and the complete Event, every Aggregate bit for bit), the values read through value(io.github.romanhaendler.rbed.Expression), retainedValues(), droppedValues(), the warnings logged and the exceptions thrown are identical. The Snapshot also works after close() (it restores a closed Detector), and before the first Sample. It does not contain the listener; the restored Detector reports to the Builder's listener. Taking a Snapshot does not change this Detector. Its size is bounded by the Retention Limit.

      The array starts with a magic header and a format version, followed by a fingerprint of the Rules and options; Detector.Builder.build() rejects a Snapshot that does not match.

      Throws:
      IllegalStateException - if called from within an EventListener callback, where a moment is only half processed
    • value

      public OptionalDouble value(Expression expression)
      The value of a watched Expression at the latest moment processed (Sample or time advance), also after close(). Empty while the value is undefined, for example a Channel before its first value, a moving average during its warm-up, a Pulse Channel absent in that moment or latest(...) before the first such Event. Expressions about Events are defined from the start: before the first Sample, count(...) and totalDuration(...) are 0, or what the History gives. Reading only looks the value up.

      From an EventListener callback, it reads the moment being processed. Rules are evaluated in the order they were added, so for a Rule added later than the Rule whose callback runs, count, totalDuration and latest show its Events as of the previous moment: an Event of it Confirmed or Ended in this moment is not reflected yet, nor is this moment in its Aggregates; a running Event's duration counts up to this moment.

      Throws:
      IllegalArgumentException - if the Expression was not registered with Detector.Builder.watch(io.github.romanhaendler.rbed.Expression...); a Channel matches by name, every other Expression by identity
      NullPointerException - if expression is null
    • memoryHorizon

      public MemoryHorizon memoryHorizon()
      How far back this Detector's configuration makes it look: the longest time window and the largest Sample count used by its Rules and watched Expressions, computed when it was built. This is a configuration bound, not a guarantee. An Event in progress, a Rule waiting for its when Condition to stop holding, and latest(...) can depend on Samples further back; the Replay Point is the exact answer to how far back a replay must start.
    • replayPoint

      public OptionalLong replayPoint()
      The earliest timestamp from which replaying this Detector's input into a fresh Detector (same Rules and options), with the Events that Ended before that timestamp supplied as History, reproduces this Detector exactly: the replay then continues with the same Events, callbacks, values and exceptions as this Detector. Empty before the first Sample, and with a sampling rate (a fresh Detector cannot continue the Sample index). Read it after each moment, store it with the stream position, and the stream has to be kept back to the oldest Replay Point still needed.

      Replay all moments (Samples, time advances, and close() if it was called) with a timestamp from the Replay Point on, in order, with the listener's side effects suppressed up to the position where the replay catches up with this Detector. The History is every Event that Ended before the Replay Point, including those supplied to this Detector as History; Events that Ended later are produced again by the replay.

      The Replay Point reaches back to what the state of this Detector depends on, value by value: the Events in progress, including the End Condition's duration; a Rule that has not seen its when Condition stop holding since its last Event ended on an End Condition, so a fresh Rule would start another Event; the moment when was last seen false, which decides whether the next Event is Truncated at its start; the oldest Sample in each moving-average window; the latest and previous values read by rate(), age(), rising(), falling() and the previous Sample behind maxGap; the Channel values the Rules read; and the start of the latest Event of every Rule whose Aggregates are read through latest(...). It is not inside the span of an Event that another Rule reads or that ended on an End Condition, so such an Event is never split. On a steady input it keeps up with the stop, within what the constructs in use need. Reading it takes time proportional to the number of Channels, moving averages, Rules and finished runs kept, not to the history, and allocates only the result.

      Preconditions: the replay gets the same input, Rules and options, and its listener's side effects are suppressed up to the stop; the Detector has not dropped entries because of the Retention Limit (droppedValues() is 0), which would make windows depend on the dropped entries; and droppedValues(), a count since the Detector was built, and the warning logged with the first drop are not reproduced by a replay. A moving average depends only on the values in its window, so a replay reproduces it bit for bit.

    • retainedValues

      public long retainedValues()
      The number of entries this Detector currently holds: one per Channel a Rule or watched Expression uses, one per value in each moving-average window and one per timestamp kept for count(...).within(...) (timestamps older than the window are dropped at every moment). A plain counter read, cheap to poll between pushes.
    • droppedValues

      public long droppedValues()
      The number of entries dropped because a window was full (see Detector.Builder.retentionLimit) since this Detector was built. Entries that left a window by time are not counted.
    • builder

      public static Detector.Builder builder()
    • push

      public void push(long timestamp, Map<Channel,Double> values)
      Processes one Sample.
      Parameters:
      timestamp - milliseconds; must be strictly greater than the previous Sample's and than the end of every History Event
      values - values of any subset of Channels; must be finite
      Throws:
      IllegalStateException - if a sampling rate was configured, or after close()
    • push

      public void push(Map<Channel,Double> values)
      Processes one Sample of a regularly sampled series; its timestamp is derived from the sampling rate as round(index * 1000 / hz).
      Throws:
      IllegalStateException - if no sampling rate was configured, or after close()
    • detect

      public List<Event> detect(Iterable<Sample> series)
      Runs a whole recorded series through this unused Detector and closes it. The listener is still called.
      Returns:
      all Ended Events in the order they Ended
    • detectValues

      public List<Event> detectValues(Iterable<Map<Channel,Double>> series)
      Like detect(java.lang.Iterable<io.github.romanhaendler.rbed.Sample>), for a regularly sampled series without timestamps.
    • close

      public void close()
      Ends running Events as truncated and makes this Detector reject further Samples.
      Specified by:
      close in interface AutoCloseable
    • advanceTo

      public void advanceTo(long timestamp)
      Tells this Detector that time has passed up to timestamp without new Samples. Rules are evaluated at that moment with every Channel holding its value (Pulse Channels are absent): durations can be reached, max gaps noticed, and age() grows. An advance is not a Sample, so it does not count for forSamples or Sample-based Aggregates.
      Parameters:
      timestamp - milliseconds; must be strictly greater than the previous Sample or advance
      Throws:
      IllegalStateException - if a sampling rate was configured, or after close()