rbed

rbed

Rule-based event detection in time series, for Java.

A small Java 17 library that finds Events in multi-channel time series by evaluating declarative Rules, on a live stream or over a recorded series. Zero runtime dependencies.

You describe what an Event is ("the weight is above 500 kg for 2 seconds", "the temperature is above 8 °C within 30 minutes after the door was closed"), and rbed tells you when it happened: when it started, when it was confirmed, when it ended, and what the data looked like in between. Every chart on this site is the real rbed, compiled to JavaScript and running in your browser.

The terms (Sample, Channel, Condition, Rule, Event, Detector, Pending, Confirmed, Truncated, ...) are defined in the glossary.

A first Rule

HighTemperature is an Event while the Channel temp is above a threshold.

The live chart and its controls need JavaScript. The tables show the Events at the default parameters.

The site cannot run the default parameters: (JavaScript) TypeError: Cannot read properties of undefined (reading 'length')

import io.github.romanhaendler.rbed.*;
import java.util.Map;

Channel temp = Channel.named("temp");

Rule highTemperature = Rule.named("HighTemperature").when(temp.above(80));
Detector detector = Detector.builder()
    .rules(highTemperature)
    .listener(new EventListener() {
      @Override public void onEnded(Event e) {
        System.out.println(e.name() + " from " + e.start() + " to " + e.end().getAsLong());
      }
    })
    .build();

detector.push(1_000, Map.of(temp, 75.0));   // timestamp in ms, then Channel values
detector.push(2_000, Map.of(temp, 85.0));
detector.push(3_000, Map.of(temp, 70.0));
detector.close();

At the default threshold of 80 °C, HighTemperature runs from 2 s to 3 s, and the listener prints "HighTemperature from 2000 to 3000": timestamps are milliseconds, and the site's tables show seconds.

Things to try

  • Lower the threshold to 70 °C: the Condition already holds at the first Sample (75 °C), so the Event starts at 1 s with a Truncated start.
  • Raise it to 90 °C: no Sample is above it, and there is no Event.

Three steps

Every use of rbed takes the same three steps:

  1. Build a Detector from the Rules and a listener.
  2. Feed it Samples with push(timestamp, values), in time order.
  3. Close it with close() when the data ends, so that an Event still in progress is reported, too. A Detector is AutoCloseable, so try-with-resources works.

For a recorded series, detect does all three in one call.

Methods used: Channel.named, Rule.named, Rule.when, Expression.above, Detector.builder, Detector.Builder.rules, Detector.Builder.listener, EventListener.onEnded, Event.name, Event.start, Event.end, Detector.Builder.build, Detector.push, Detector.close.

Install

rbed 0.4.0 is on Maven Central and needs Java 17 or later.

<dependency>
  <groupId>io.github.romanhaendler</groupId>
  <artifactId>rbed</artifactId>
  <version>0.4.0</version>
</dependency>
implementation("io.github.romanhaendler:rbed:0.4.0")

The library is the Java module io.github.romanhaendler.rbed (requires io.github.romanhaendler.rbed;) with a single package of the same name; it also works on the class path.

The code on this site

The Java on every page can be copied and run: it starts with its imports, and its statements are the body of a method, such as main. Most sources leave out the listener to stay short, so they run without output: the Events they detect are the page's Event table. To print them, add a listener before .build(), as in A first Rule, or print the list that detect returns. Where it calls a method of its own, that method follows under // In the enclosing class:. Time values are written as java.time.Duration; every method that takes one also takes a long in milliseconds. Short snippets in the text assume the Channels, Rules, variables and methods they name, and these imports:

import static io.github.romanhaendler.rbed.Conditions.*;   // during, after, count, totalDuration, latest

import io.github.romanhaendler.rbed.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

EventListener shares its simple name with java.util.EventListener: next to import java.util.*;, import io.github.romanhaendler.rbed.EventListener explicitly.

Good to know

  • A Rule is an immutable value: when, forDuration, endWhen and the other methods each return a new Rule, so a Rule can be shared by several Detectors, and a call whose result is not used changes nothing.
  • A Detector is single-use and not thread-safe. Use one per data stream, and feed it from one thread at a time.
  • Rules and the listener are fixed when the Detector is built. To change Rules, build a new Detector and pass it the Events so far as History, or resume the stream from a Snapshot.
  • A pushed Sample costs one hash lookup per value, and in steady state (no Event starting or ending) a Detector allocates nothing. Throughput ranges from about 1.2 million Samples per second for 50 Rules over 20 Channels to over 40 million for a single threshold Rule. See the README's Performance, and Memory and the Retention Limit for what a Detector keeps.

The Guide takes one concept per page, starting with Samples and Channels.

Domain examples

Runnable end-to-end examples from different domains, each a test with its expected Events and a page of its own where it runs live.

Source on GitHub · Maven Central · MIT licence