Modality Tutorial
In this tutorial we will take a tour of Modality's basic concepts and functionality. We will use Modality to collect data from an example system, and then explore the various ways Modality makes it possible for us to learn about our system's behavior.
# Setup
First, install Modality.
Once installed, create a user. For this tutorial we'll create a user named "tutorial". If you already have a Modality user, and a token with permissions for both query and ingest, you can skip this step
$ modality user create tutorial --use
# Example System Overview
The example system we use in this tutorial is a much simplified version of a set of interacting components you might see in an embedded system. There are three components in this system:
The
producer
takes measurements from the outside world via some sensor. It sends these measurement values to theconsumer
. In addition, theproducer
regularly sends heartbeat messages to themonitor
.The
consumer
receives the sensor measurements from the producer, and performs some work based on those measurements (in this example that work is simply a sleep whose duration is dictated by the measurement value). Just like theproducer
, theconsumer
periodically sends a heartbeat message to themonitor
.The
monitor
exists to make sure the other components are running correctly. It receives heartbeat messages from each of theproducer
andconsumer
, and reports an error if either component is late sending its heartbeat.
Here is a simple diagram of the example system:
# Run the System
The example system is included with the Modality client, so you should already have it installed. We'll run it for 4 seconds, to produce some trace data that is stored in Modality.
$ modality_monitored_pipeline_example 4
Running a pipeline of collaborating processes for 4 seconds. Sending traces to modality.
2022-07-20T00:36:32.927870Z INFO monitored_pipeline::consumer: Starting up
2022-07-20T00:36:32.952271Z INFO monitored_pipeline::producer: Starting up
2022-07-20T00:36:32.952480Z INFO monitored_pipeline::producer: Producer sampled raw measurement sample=0
2022-07-20T00:36:32.952578Z INFO monitored_pipeline::producer: Producer sending measurement message sample=0 destination="consumer"
2022-07-20T00:36:32.952696Z INFO monitored_pipeline: Sending heartbeat measurement message destination="monitor"
2022-07-20T00:36:32.952968Z INFO monitored_pipeline::consumer: Received measurement message sample=0 interaction.remote_timeline_id=c87758c7-8c0d-4ef1-a622-7c5e3a7ffabd interaction.remote_timestamp=1658277392952499123
2022-07-20T00:36:32.953580Z INFO monitored_pipeline::monitor: Starting up
2022-07-20T00:36:32.953781Z INFO monitored_pipeline::monitor: Received heartbeat message source="producer" interaction.remote_timeline_id=c87758c7-8c0d-4ef1-a622-7c5e3a7ffabd interaction.remote_timestamp=1658277392952604550
2022-07-20T00:36:32.954024Z INFO monitored_pipeline::monitor: Monitor has observed a component for the first time for that component source="producer"
2022-07-20T00:36:33.116859Z INFO monitored_pipeline::producer: Producer sampled raw measurement sample=-1
2022-07-20T00:36:33.117004Z INFO monitored_pipeline::producer: Producer sending measurement message sample=-1 destination="consumer"
2022-07-20T00:36:33.193490Z INFO monitored_pipeline::consumer: Received measurement message sample=-1 interaction.remote_timeline_id=c87758c7-8c0d-4ef1-a622-7c5e3a7ffabd interaction.remote_timestamp=1658277393116906800
2022-07-20T00:36:33.219735Z INFO monitored_pipeline::producer: Producer sampled raw measurement sample=0
2022-07-20T00:36:33.219874Z INFO monitored_pipeline::producer: Producer sending measurement message sample=0 destination="consumer"
...
Just that easily the data from this run of our system is in Modality,
so it's worth recapping how it got there. This system is written in
Rust. It uses tracing.rs, a popular Rust tracing framework, to collect
diagnostic information. It also includes Modality's tracing.rs
library, which automatically sends
the trace data to the Modality daemon, modalityd
.
Note
There are many different options for getting data from your system into Modality. The example system shows an easy approach for systems written in Rust. To learn about all the ways Modality lets you build a data collection setup that works for you, see Data Collection.
# Explore the Data
Modality uses workspaces to let you select the data you are interested in working with. It includes a default workspace which selects the data from the most recent run of your system. We'll use the default workspace for the rest of the tutorial, so let's select it:
$ modality workspace use default
Using 'default' as the default workspace
Now we can use the timeline
and event
commands to get a first look
at some of our data. First we see that our system has 4 timelines,
each with some basic attributes.
$ modality timeline list
Found 4 Timelines (as grouped by [timeline.name])
*@main
# 1 timeline instances containing a total of 3 events
# 4 attributes:
# timeline.id, timeline.name, timeline.receive_time, timeline.run_id
*@producer
# 1 timeline instances containing a total of 66 events
# 4 attributes:
# timeline.id, timeline.name, timeline.receive_time, timeline.run_id
*@monitor
# 1 timeline instances containing a total of 17 events
# 4 attributes:
# timeline.id, timeline.name, timeline.receive_time, timeline.run_id
*@consumer
# 1 timeline instances containing a total of 33 events
# 4 attributes:
# timeline.id, timeline.name, timeline.receive_time, timeline.run_id
Next, we'll look at the list of events from our system. This tells us various information about all the events in the trace, including their names, their attributes, and the number of times (instances) they were observed:
$ modality event list
Found 10 Events (as grouped by [event.name])
Joined thread@*
# 3 event instances
# 11 attributes:
# event.component, event.name, event.severity, event.source.file,
# event.source.line, event.source.module, event.timestamp, timeline.id,
# timeline.name, timeline.receive_time, timeline.run_id
Starting up@*
# 3 event instances
# 10 attributes:
# event.name, event.severity, event.source.file, event.source.line,
# event.source.module, event.timestamp, timeline.id, timeline.name,
# timeline.receive_time, timeline.run_id
Producer sampled raw measurement@*
# 29 event instances
# 11 attributes:
# event.name, event.sample, event.severity, event.source.file,
# event.source.line, event.source.module, event.timestamp, timeline.id,
# timeline.name, timeline.receive_time, timeline.run_id
Producer sending measurement message@*
# 29 event instances
# 13 attributes:
# event.destination, event.name, event.nonce, event.sample,
# event.severity, event.source.file, event.source.line,
# event.source.module, event.timestamp, timeline.id, timeline.name,
# timeline.receive_time, timeline.run_id
...
Now, to get a graphical view of our system trace, we'll use modality log
. This view shows an ASCII art graph of system execution on the left with event details on the right. Timelines are represented by the vertical lines, events are points on those lines, and interactions are arrows between timelines.
■ "Starting up" @ monitor [35d3faab9351478f822a1d7b4cc88a83:4bc8]
║ name = Starting up
║ severity = info
║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ source.line = 373
║ source.module = monitored_pipeline::monitor
║ timestamp = 2022-10-19 08:23:42.778083012 +00:00
║
║ ■ "Starting up" @ consumer [fc5c3d8d977f4d768b7d5227d9031239:054b2f]
║ ║ name = Starting up
║ ║ severity = info
║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line = 304
║ ║ source.module = monitored_pipeline::consumer
║ ║ timestamp = 2022-10-19 08:23:42.778384295 +00:00
║ ║
║ ║ ■ "Starting up" @ producer [8288fb81fb334257988fccc1fc31c090:0a445b]
║ ║ ║ name = Starting up
║ ║ ║ severity = info
║ ║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ ║ ║ source.line = 212
║ ║ ║ source.module = monitored_pipeline::producer
║ ║ ║ timestamp = 2022-10-19 08:23:42.778768147 +00:00
║ ║ ║
║ ║ ■ "Producer sampled raw measurement" @ producer [8288fb81fb334257988fccc1fc31c090:0ba95e]
║ ║ ║ name = Producer sampled raw measurement
║ ║ ║ sample = -1
║ ║ ║ severity = info
║ ║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ ║ ║ source.line = 237
║ ║ ║ source.module = monitored_pipeline::producer
║ ║ ║ timestamp = 2022-10-19 08:23:42.778954854 +00:00
║ ║ ║
║ ║ ○ "Producer sending measurement message" @ producer [8288fb81fb334257988fccc1fc31c090:0c4c59]
║ ╭────────────────────║ [Interaction i0000]
║ │ ║ ║ destination = consumer
║ │ ║ ║ name = Producer sending measurement message
║ │ ║ ║ nonce = -2186930594787535621
║ │ ║ ║ sample = -1
║ │ ║ ║ severity = info
║ │ ║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ │ ║ ║ source.line = 261
║ │ ║ ║ source.module = monitored_pipeline::producer
║ │ ║ ║ timestamp = 2022-10-19 08:23:42.779103063 +00:00
║ │ ║ ║
║ │ ║ ○ "Sending heartbeat message" @ producer [8288fb81fb334257988fccc1fc31c090:0ceb26]
║ │ ╭─────────────────║ [Interaction i0001]
║ │ │ ║ ║ destination = monitor
║ │ │ ║ ║ name = Sending heartbeat message
║ │ │ ║ ║ nonce = -4634878031135775668
║ │ │ ║ ║ severity = info
║ │ │ ║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ │ │ ║ ║ source.line = 441
║ │ │ ║ ║ source.module = monitored_pipeline
║ │ │ ║ ║ timestamp = 2022-10-19 08:23:42.779286310 +00:00
║ │ │ ║ ║
○ │ │ ║ ║ "Received heartbeat message" @ monitor [35d3faab9351478f822a1d7b4cc88a83:0da3c6]
║◀────╯ ║ ║ [Interaction i0001]
║ │ ║ ║ interaction.remote_nonce = -4634878031135775668
║ │ ║ ║ interaction.remote_timeline_id = 8288fb81-fb33-4257-988f-ccc1fc31c090
║ │ ║ ║ name = Received heartbeat message
║ │ ║ ║ severity = info
║ │ ║ ║ source = producer
║ │ ║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ │ ║ ║ source.line = 383
║ │ ║ ║ source.module = monitored_pipeline::monitor
║ │ ║ ║ timestamp = 2022-10-19 08:23:42.779478930 +00:00
║ │ ║ ║
■ │ ║ ║ "Monitor has observed a component for the first time for that component" @ monitor [35d3faab9351478f822a1d7b4cc88a83:0eab60]
║ │ ║ ║ name = Monitor has observed a component for the first time for that component
║ │ ║ ║ severity = info
║ │ ║ ║ source = producer
║ │ ║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ │ ║ ║ source.line = 390
║ │ ║ ║ source.module = monitored_pipeline::monitor
║ │ ║ ║ timestamp = 2022-10-19 08:23:42.779755538 +00:00
║ │ ║ ║
║ │ ○ ║ "Received measurement message" @ consumer [fc5c3d8d977f4d768b7d5227d9031239:0fa7fa]
║ ╰───────▶║ ║ [Interaction i0000]
║ ║ ║ interaction.remote_nonce = -2186930594787535621
║ ║ ║ interaction.remote_timeline_id = 8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ ║ name = Received measurement message
║ ║ ║ sample = -1
║ ║ ║ severity = info
║ ║ ║ source.file = tracing-modality/examples/monitored_pipeline.rs
║ ║ ║ source.line = 319
║ ║ ║ source.module = monitored_pipeline::consumer
║ ║ ║ timestamp = 2022-10-19 08:23:42.779906462 +00:00
║ ║ ║
...
# Drill Down With Queries
Now that we've taken a general look at our data we'll use queries to ask more specific questions about what our system was doing. Since query syntax is not the main focus of this document, explanations will be brief. To better understand queries and learn more about everything you can do with the SpeQTr language, see the SpeQTr docs. (opens new window)
Results May Vary
The example system we're using in this tutorial is not perfectly deterministic—it will produce slightly different data every run, depending on how your OS schedules the execution of the different threads. As such, when you run the queries below you are likely to see different results than are shown here. You can use this document's results as a guide for interpreting your own.
# Find a Specific Event
To start, we'll ask a universal question: were there any errors during
this run? This query uses the *
wildcard to match any event on any
timeline, and further specifies that events should only match if they
have an attribute named severity
whose value is 'error'
.
Note
This system uses an event attribute named severity
to indicate
whether an event represents an error. Note that this is not a
universal approach. Modality lets you structure your data however is
best for your specific use case. You could use severity
to indicate
errors, but you could also use the event name to indicate error, or
use a different attribute. This means that, when writing queries for
your own system, you need to remember to match the structure of the
data you have put into Modality.
$ modality query "*@*(_.severity = 'error')"
Result 1:
═════════
■ "Detected heartbeat timeout" @ monitor [35d3faab9351478f822a1d7b4cc88a83:649bceb0]
║ component=producer
║ severity=error
║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ source.line=414
║ source.module=monitored_pipeline::monitor
║ timestamp=1666167824464463167ns
║ query.label=*@*(_.severity = 'error')
║
Result 2:
═════════
■ "Detected heartbeat timeout" @ monitor [35d3faab9351478f822a1d7b4cc88a83:655d28a4]
║ component=producer
║ severity=error
║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ source.line=414
║ source.module=monitored_pipeline::monitor
║ timestamp=1666167824477196872ns
║ query.label=*@*(_.severity = 'error')
║
Let's walk through what information is included in the query results:
- The system reported 2 errors during this run.
- Both errors are the same: the
monitor
detected a heartbeat timeout from theproducer
component. - On the top line, each result shows the event and timeline name, followed by event coordinates, which uniquely identify the event instance.
- Next, we see event attributes. For these events, they include the relevant component and event severity, as well as information about where we can find this error event in the source code, the timestamp, and the label assigned when running the query.
So, this query informs us that there were 2 instances where the
producer
heartbeat triggered a timeout error. Let's continue our
investigation.
# Match a Sequence of Events
The next question we'll ask is, "Show me all the pairs of consecutive
heartbeats that the monitor
received from the producer
." This
example demonstrates how easy it is to query for sequences of
events. Sequences of events let you look for events in context, which
is crucial to understanding your system. Rather than just "Show me
every instance of X", you often need to see e.g. "every instance of X
that follows an instance of Y with a payload less than 50" to zero in
on problem areas.
Note
This tutorial takes a slightly roundabout approach. In a real investigation you would probably start from the results of the first query, working back from where the error occurred to see what the system was doing at that time. This tutorial is giving examples of the types of questions you can ask with Modality, so it doesn't always take the shortest route of investigation.
$ modality query "'Received heartbeat message'@monitor(_.source = 'producer') FOLLOWED BY 'Received heartbeat message'@monitor(_.source = 'producer')"
Result 1:
═════════
ɮͼ 8288fb81-fb33-4257-988f-ccc1fc31c090 interacted with monitor at 35d3faab9351478f822a1d7b4cc88a83:0da3c6
║ ║
■ ║ "Received heartbeat message" @ monitor [35d3faab9351478f822a1d7b4cc88a83:0da3c6]
║ ║ interaction.remote_nonce=-4634878031135775668
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ severity=info
║ ║ source=producer
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=383
║ ║ source.module=monitored_pipeline::monitor
║ ║ timestamp=1666167822779478930ns
║ ║ query.label='Received heartbeat message'@monitor(_.source = 'producer')
║ ║
ɮͼ 8288fb81-fb33-4257-988f-ccc1fc31c090 interacted with monitor at 35d3faab9351478f822a1d7b4cc88a83:20747d7d
║ ║
■ ║ "Received heartbeat message" @ monitor [35d3faab9351478f822a1d7b4cc88a83:20747d7d]
║ ║ interaction.remote_nonce=-6025894133084304786
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ severity=info
║ ║ source=producer
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=383
║ ║ source.module=monitored_pipeline::monitor
║ ║ timestamp=1666167823321250844ns
║ ║ query.label='Received heartbeat message'@monitor(_.source = 'producer') [2]
║ ║
Result 2:
═════════
ɮͼ 8288fb81-fb33-4257-988f-ccc1fc31c090 interacted with monitor at 35d3faab9351478f822a1d7b4cc88a83:20747d7d
║ ║
■ ║ "Received heartbeat message" @ monitor [35d3faab9351478f822a1d7b4cc88a83:20747d7d]
║ ║ interaction.remote_nonce=-6025894133084304786
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ severity=info
║ ║ source=producer
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=383
║ ║ source.module=monitored_pipeline::monitor
║ ║ timestamp=1666167823321250844ns
║ ║ query.label='Received heartbeat message'@monitor(_.source = 'producer')
║ ║
ɮͼ 8288fb81-fb33-4257-988f-ccc1fc31c090 interacted with monitor at 35d3faab9351478f822a1d7b4cc88a83:403df013
║ ║
■ ║ "Received heartbeat message" @ monitor [35d3faab9351478f822a1d7b4cc88a83:403df013]
║ ║ interaction.remote_nonce=5473947973528223796
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ severity=info
║ ║ source=producer
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=383
║ ║ source.module=monitored_pipeline::monitor
║ ║ timestamp=1666167823854899763ns
║ ║ query.label='Received heartbeat message'@monitor(_.source = 'producer') [2]
║ ║
...
Our results show us every consecutive pair of heartbeats that the
monitor
received from the producer
. This is interesting, but it
does not nail down our problem. The missing piece is a time
constraint—heartbeats don't just need to be received, they need to be
received within a time limit. It would be possible to look through
these heartbeat pairs and compare their timestamps to find the problem
areas, but that would be tedious and time consuming. Let's have
Modality do it for us instead.
# Query With Time Constraints
We are now ready to write a query to find the places where an error
occurred. The question we are asking here is, "Show me all the places
where at least 600 milliseconds elapsed before the monitor
received
a heartbeat from the producer
." We can write this query with a
simple modification to the relationship specifier in the previous
query, adding AFTER 600 ms
.
Let's run the query and see where our errors occurred:
$ modality query "'Received heartbeat message'@monitor(_.source = 'producer') FOLLOWED BY AFTER 600 ms 'Received heartbeat message'@monitor(_.source = 'producer')"
Result 1:
═════════
ɮͼ 8288fb81-fb33-4257-988f-ccc1fc31c090 interacted with monitor at 35d3faab9351478f822a1d7b4cc88a83:403df013
║ ║
■ ║ "Received heartbeat message" @ monitor [35d3faab9351478f822a1d7b4cc88a83:403df013]
║ ║ interaction.remote_nonce=5473947973528223796
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ severity=info
║ ║ source=producer
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=383
║ ║ source.module=monitored_pipeline::monitor
║ ║ timestamp=1666167823854899763ns
║ ║ query.label='Received heartbeat message'@monitor(_.source = 'producer')
║ ║
ɮͼ 8288fb81-fb33-4257-988f-ccc1fc31c090 interacted with monitor at 35d3faab9351478f822a1d7b4cc88a83:6585ba4c
║ ║
■ ║ "Received heartbeat message" @ monitor [35d3faab9351478f822a1d7b4cc88a83:6585ba4c]
║ ║ interaction.remote_nonce=-1546364400581164495
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ severity=info
║ ║ source=producer
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=383
║ ║ source.module=monitored_pipeline::monitor
║ ║ timestamp=1666167824479940281ns
║ ║ query.label='Received heartbeat message'@monitor(_.source = 'producer') [2]
║ ║
Modality has pointed us to a single place where the producer
heartbeat was late. You may remember, however, that the original query
found two errors. Our next example will confirm the cause of the other
error.
# Querying Across Timelines
So far we have only looked at sequences of events on a single
timeline. Many times, however, the context you need to answer a
question spans multiple different components of your system. This
example shows how easy it is to write a single query for a sequence of
events across timelines. The question we are asking is "Show me all
the places where the producer
sent a measurement message and then
the consumer
received it".
$ modality query "'Producer sending measurement message'@producer FOLLOWED BY 'Received measurement message'@consumer"
Result 1:
═════════
■ ║ "Producer sending measurement message" @ producer [8288fb81fb334257988fccc1fc31c090:0c4c59]
║ ║ destination=consumer
║ ║ nonce=-2186930594787535621
║ ║ sample=-1
║ ║ severity=info
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=261
║ ║ source.module=monitored_pipeline::producer
║ ║ timestamp=1666167822779103063ns
║ ║ query.label='Producer sending measurement message'@producer
║ ║
╚═»╗ producer interacted with consumer at fc5c3d8d977f4d768b7d5227d9031239:0fa7fa
║ ║
║ ■ "Received measurement message" @ consumer [fc5c3d8d977f4d768b7d5227d9031239:0fa7fa]
║ ║ interaction.remote_nonce=-2186930594787535621
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ sample=-1
║ ║ severity=info
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=319
║ ║ source.module=monitored_pipeline::consumer
║ ║ timestamp=1666167822779906462ns
║ ║ query.label='Received measurement message'@consumer
║ ║
Result 2:
═════════
■ ║ "Producer sending measurement message" @ producer [8288fb81fb334257988fccc1fc31c090:06b07d0e]
║ ║ destination=consumer
║ ║ nonce=-1048534873025976780
║ ║ sample=-2
║ ║ severity=info
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=261
║ ║ source.module=monitored_pipeline::producer
║ ║ timestamp=1666167822889337122ns
║ ║ query.label='Producer sending measurement message'@producer
║ ║
╚═»╗ producer interacted with consumer at fc5c3d8d977f4d768b7d5227d9031239:09c786ed
║ ║
║ ■ "Received measurement message" @ consumer [fc5c3d8d977f4d768b7d5227d9031239:09c786ed]
║ ║ interaction.remote_nonce=-1048534873025976780
║ ║ interaction.remote_timeline_id=8288fb81-fb33-4257-988f-ccc1fc31c090
║ ║ sample=-2
║ ║ severity=info
║ ║ source.file=tracing-modality/examples/monitored_pipeline.rs
║ ║ source.line=319
║ ║ source.module=monitored_pipeline::consumer
║ ║ timestamp=1666167822940424169ns
║ ║ query.label='Received measurement message'@consumer
║ ║
...
# Query Aggregation
So far we have looked at using queries to find specific places in the
trace that match a given pattern. Queries also have aggregation
capabilities, allowing you to perform various calculations. The
question we are asking is "I want to get a statistical picture of some
runtime parameter reported by my system", in this case the sensor
measurement taken by the producer
.
This example also demonstrates running a query which is saved in a
file. Since complex queries can get quite long, it is often useful to
work with them in files and then use the --file
option.
# aggregate-example.speqtr
'Producer sampled raw measurement'@producer AS measurement
AGGREGATE
std_dev(measurement.sample) AS meas_stddev,
mean(measurement.sample) AS meas_mean
$ modality query --file aggregate-example.speqtr
Aggregates:
meas_stddev = 1.3163022337173969
meas_mean = -2.675675675675676
# JSON Output
Since Modality can answer complex questions about your system, it's a natural fit in CI pipelines and other testing scenarios. In these cases processing and reporting results is key. As such, virtually all Modality commands have the option of formatting their output as JSON.
$ modality query --file /vagrant/aggregate-example.speqtr --format json
[{"label":"meas_stddev","value":{"Scalar":1.3163022337173969}},{"label":"meas_mean","value":{"Scalar":-2.675675675675676}}]
# Select Data of Interest
Thus far in this tutorial we have taken a tour of analysis functions after a single run of our system. We know all of our results have been related to this run, since that's the only data in Modality. Once you start collecting more data you need some way of telling Modality what you're interested in investigating. Workspaces and segmentation are the tools that let you do this.
Modality compiles all the data you ever collect into a single database, building an empirical model of your system that allows you to ask complex questions, across disparate parts and over time. Workspaces let you create a view on your Modality database that only includes the relevant data for what you're trying to accomplish.
In addition, many use cases involve some related set of data that is split into discrete chunks. Segmentation rules let you tell Modality how to split up your data. Modality will then automatically create segments in your workspace that you can analyze, individually or together.
The default workspace we're using in this tutorial automatically
segments by run_id
, an attribute which many Modality integrations
add to your data. This automatically segments data from separate runs
of your system.
# Collect More Data
To see segmentation in action we will run the example system again. This time, to keep the segment small, let's just run for one second.
$ modality_monitored_pipeline_example 1
Running a pipeline of collaborating processes for 1 seconds. Sending traces to modality.
2022-10-18T00:38:28.533174Z INFO monitored_pipeline::monitor: Starting up
2022-10-18T00:38:28.533422Z INFO monitored_pipeline::producer: Starting up
2022-10-18T00:38:28.533783Z INFO monitored_pipeline::producer: Producer sampled raw measurement sample=-1
2022-10-18T00:38:28.533945Z INFO monitored_pipeline::producer: Producer sending measurement message sample=-1 nonce=-2186930594787535621 destination="consumer"
2022-10-18T00:38:28.534131Z INFO monitored_pipeline: Sending heartbeat message destination="monitor" nonce=-4634878031135775668
2022-10-18T00:38:28.534246Z INFO monitored_pipeline::monitor: Received heartbeat message source="producer" interaction.remote_timeline_id=729a8cfe-315e-4472-8ae7-3c33ddf6b926 interaction.remote_nonce=-4634878031135775668
2022-10-18T00:38:28.534357Z INFO monitored_pipeline::monitor: Monitor has observed a component for the first time for that component source="producer"
2022-10-18T00:38:28.535274Z INFO monitored_pipeline::consumer: Starting up
2022-10-18T00:38:28.535427Z INFO monitored_pipeline::consumer: Received measurement message sample=-1 interaction.remote_timeline_id=729a8cfe-315e-4472-8ae7-3c33ddf6b926 interaction.remote_nonce=-2186930594787535621
2022-10-18T00:38:28.635071Z INFO monitored_pipeline::producer: Producer sampled raw measurement sample=-2
2022-10-18T00:38:28.636118Z INFO monitored_pipeline::producer: Producer sending measurement message sample=-2 nonce=-1048534873025976780 destination="consumer"
...
# Work With New Data
First, let's confirm that we now have 2 segments as expected.
$ modality segment list
'by-run-id':
Name: 'Run 36deea7f-71d9-45b8-87a2-2ddc26d9994d'
Name: 'Run 5bf70ad4-8263-4509-bd45-42649b2fc460'
Since these segments are auto-generated, a UUID is used for their names. When writing your own segmentation rules you can provide a template for naming the segments.
Let's now make sure we are working with our latest run. The modality segment use
command has a --latest
option—this will select the most
recent segment, and it will update if we collect more data.
$ modality segment use --latest
Using the latest segment as the default segment
Now, let's run our first query again and see if our results have changed.
$ modality query "*@*(_.severity = 'error')"
$
No results. It looks like, with the system only running for 1 second, we didn't see any heartbeat timeout errors. This confirms that we are only working with the data from our latest run.
Workspaces
There are many different ways that you can use workspaces. One example would be a workspace for all the data produced by some system, and other workspaces for other systems. Another could be a workspace for CI, where data is segmented by CI runs, and separate workspaces for bench testing or integration testing with different segmentation rules. Workspaces and segmentation are designed to be flexible so you can adapt them to your needs.
← Server Collect Data →