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:
This will match all events named
read_voltage which occurred on
pmu. You can use wildcards in either the event or
*@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
read_voltage@pmu(_.voltage > 3.3 and _.timeline.build_version = "0.9beta")
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
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
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
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
the part of the pattern is referenced by name, that means that it must
be the exact same event, within each result.
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.