Querying Modality

# The modality query command

To run a query, use the modality query command. It takes a SpeQTr event pattern as its main parameter, and finds all the sets of events in your dataset that match the pattern. It looks at the timelines in the currently selected segment(s) of the currently selected workspace by default, but these can be changed with command line options.

# Matching Single Events

The simplest query is one that matches a single event by name, on a named timeline:

read_voltage@pmu

This will match all events named read_voltage which occurred on timelines named pmu. You can use wildcards in either the event or timeline position: *@pmu would match all events which occurred on timelines named 'pmu'. read_*@* would match all events whose name starts with 'read_', on any timeline.

Events aren't just names, they also have a bunch of attributes on them. You can match these by adding an event predicate to your query. This is a simple boolean expression using the attributes on the event being matched; the underscore symbol _ is used to represent the event being examined. So, this query would match events where the voltage is over a certain threshold:

read_voltage@pmu(_.voltage > 3.3)

You also have access to all the timeline metadata in this context, behind _.timeline:

read_voltage@pmu(_.voltage > 3.3 and _.timeline.build_version = "0.9beta")

TIP

The event and timeline name part of the pattern is given special syntax for convenience, but it isn't fundamentally different from the event predicate. The above query, for example, could equivalently be written as:

(_.name = "read_voltage" AND
 _.timeline.name = "pmu" AND
 "voltage" > 3.3 AND
 _.timeline.build_version = "0.9beta")

# Multi-Event Patterns

Single event patterns are interesting, but not that special. Modality's true power lies in its ability to match SpeQTr queries consisting of multiple events, based on the relationships between them.

Imagine you have a system that reads the voltage on a pmu, and then, if it was too high, is supposed to trigger an alarm. We could match the alarm event as alarm@controller, and the voltage reading as read_voltage@pmu, but it's really the combination of the two that's interesting:

read_voltage@pmu FOLLOWED BY alarm@controller

Or equivalently, using a terser syntax:

read_voltage@pmu -> alarm@controller

Using this with modality query will find all the times in your system trace where a voltage reading led to an alarm. Note that these are events which occurred on different components: Modality can evaluate a single event pattern over multiple components, as long as the necessary causality information is available.

There is also a rich set of relationship constraints available. One common example is timeliness:

read_voltage@pmu
  FOLLOWED BY WITHIN 15ms
alarm@controller

This is only a simple example. Much more complex event patterns can be represented, including non-linear fork and join-style patterns.

# Interpreting query output

When you run modality query, you will get a printout of all the results. What exactly do these mean?

First, Modality will find a matching event for every event pattern in your query. It will use any custom names you gave to the events, or just the matching expression if none are there. Modality displays event name and timeline name for each event, and then the event coordinates which are used to uniquely identify that specific event occurrence in the database.

Second, Modality will print the attribute keys and values for each named event. Timeline attributes are hidden by default, but this can be changed with the --with-timeline-attributes flag.

Modality will repeat this for every result. If your query had two event patterns in it, each result shown by Modality will have two events.

# Overlapping results

When it evaluates a query, Modality finds all the ways to match the query, inside the configured region. For most queries, on most systems, this is very intuitive. But when things start to get complex, you need to be a little more careful.

For example, suppose that you have a 'fan-out' interaction in your system, where a central dispatcher sends a broadcast message to 3 workers. We could query for that kind of interaction like this:

send_message@dispatcher
  FOLLOWED BY
recv_message@worker*

Modality will then display 3 different results for every occurrence of that interaction; the send_message result will be the same for each of them, and then recv_message will be from a different worker each time.

If you wanted to write a single query that matches the whole interaction at once, you need to be a bit more verbose:

send_message@dispatcher as s FOLLOWED BY recv_message@worker1
AND s FOLLOWED BY recv_message@worker2
AND s FOLLOWED BY recv_message@worker3

In this case, we have named the first match s, and reused that name in two more patterns that are all joined together with AND. Since the part of the pattern is referenced by name, that means that it must be the exact same event, within each result.

# Aggregations

Modality supports aggregations as well, as part of the SpeQTr query language. You can use this to compute something that takes into account all the matches of a query. For example, you could easily find a minimum and maximum value for the voltage reading from a system trace:

read_voltage@pmu as rv
AGGREGATE max(rv.voltage), min(rv.voltage)

# SpeQTr Reference

There's much more to learn about the the SpeQTr language's capabilities: see the reference documentation (opens new window) for the details.