Skip to content

Commit

Permalink
Add support for Deviant custom events
Browse files Browse the repository at this point in the history
  • Loading branch information
jonlamb-gh committed Feb 13, 2024
1 parent 354c394 commit cf34b05
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 4 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "modality-trace-recorder-plugin"
version = "0.5.0"
version = "0.6.0"
edition = "2021"
authors = ["Jon Lamb <jon@auxon.io>"]
description = "A Modality reflector plugin suite and ingest adapter library for Percepio's TraceRecorder data"
Expand Down Expand Up @@ -35,6 +35,7 @@ dirs = "4"
url = "2"
uuid = { version = "1", features = ["v4", "serde"] }
humantime = "2"
byteordered = "0.6"
async-trait = "0.1"
serde = { version = "1.0", features=["derive"] }
serde_with = "2.0"
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ These sections are the same for each of the plugins.
- `single-task-timeline` — Use a single timeline for all tasks instead of a timeline per task. ISRs can still be represented with their own timelines or not.
- `disable-task-interactions` — Don't synthesize interactions between tasks and ISRs when a context switch occurs.
- `use-timeline-id-channel` — Detect task/ISR timeline IDs from the device by reading events on the `modality_timeline_id` channel (format is `name=<obj-name>,id=<timeline-id>`).
- `deviant-event-id-base` — Parse Deviant custom events using the provided base event ID.
- `ignored-object-classes` — Array of object classes to ignore processing during ingest (e.g. `[queue, semaphore]`)
- `user-event-channel` — Instead of `USER_EVENT @ <task-name>`, use the user event channel as the event name (`<channel> @ <task-name>`).
- `user-event-format-string` — Instead of `USER_EVENT @ <task-name>`, use the user event format string as the event name (`<format-string> @ <task-name>`).
Expand All @@ -65,6 +66,47 @@ These sections are the same for each of the plugins.
* `format-string`— The input format string to match on.
* `attribute-keys`— Array of Modality event attribute keys to use.

#### Deviant Events

When the `deviant-event-id-base` configuration is provided, Deviant related information will be parsed and mapped
from TraceRecorder custom events to their reserved Modality event names and attributes.

Expected event ID offset and data:
* Event: `modality.mutator.announced`
- Event ID offset: 0
- data: `['mutator_id']`
- `mutator_id` is a 16-byte UUID array
* Event: `modality.mutator.retired`
- Event ID offset: 1
- data: `['mutator_id']`
- `mutator_id` is a 16-byte UUID array
* Event: `modality.mutation.command_communicated`
- Event ID offset: 2
- data: `['mutator_id', 'mutation_id', 'mutation_success']`
- `mutator_id` is a 16-byte UUID array
- `mutation_id` is a 16-byte UUID array
- `mutation_success` is a 4-byte (`uint32_t`) boolean
* Event: `modality.mutation.clear_communicated`
- Event ID offset: 3
- data: `['mutator_id', 'mutation_id', 'mutation_success']`
- `mutator_id` is a 16-byte UUID array
- `mutation_id` is a 16-byte UUID array
- `mutation_success` is a 4-byte (`uint32_t`) boolean
* Event: `modality.mutation.triggered`
- Event ID offset: 4
- data: `['mutator_id', 'mutation_id', 'mutation_success']`
- `mutator_id` is a 16-byte UUID array
- `mutation_id` is a 16-byte UUID array
- `mutation_success` is a 4-byte (`uint32_t`) boolean
* Event: `modality.mutation.injected`
- Event ID offset: 5
- data: `['mutator_id', 'mutation_id', 'mutation_success']`
- `mutator_id` is a 16-byte UUID array
- `mutation_id` is a 16-byte UUID array
- `mutation_success` is a 4-byte (`uint32_t`) boolean

See the [Deviant documentation](https://docs.auxon.io/deviant/) for more information on Mutators and Mutations.

### Importer Section

These `metadata` fields are specific to the importer plugin.
Expand Down
10 changes: 10 additions & 0 deletions src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ pub enum CommonEventAttrKey {
RemoteTimelineId,
#[display(fmt = "event.interaction.remote_timestamp")]
RemoteTimestamp,
#[display(fmt = "event.mutator.id")]
MutatorId,
#[display(fmt = "event.internal.trace_recorder.mutator.id")]
InternalMutatorId,
#[display(fmt = "event.mutation.id")]
MutationId,
#[display(fmt = "event.internal.trace_recorder.mutation.id")]
InternalMutationId,
#[display(fmt = "event.mutation.success")]
MutationSuccess,

#[display(fmt = "event.internal.trace_recorder.code")]
EventCode,
Expand Down
9 changes: 9 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct PluginConfig {
pub flatten_isr_timelines: bool,
pub disable_task_interactions: bool,
pub use_timeline_id_channel: bool,
pub deviant_event_id_base: Option<u16>,
pub ignored_object_classes: IgnoredObjectClasses,
pub user_event_channel: bool,
pub user_event_format_string: bool,
Expand Down Expand Up @@ -190,6 +191,9 @@ impl TraceRecorderConfig {
} else {
cfg_plugin.use_timeline_id_channel
},
deviant_event_id_base: tr_opts
.deviant_event_id_base
.or(cfg_plugin.deviant_event_id_base),
ignored_object_classes: if !tr_opts.ignore_object_class.is_empty() {
tr_opts.ignore_object_class.clone().into_iter().collect()
} else {
Expand Down Expand Up @@ -270,6 +274,7 @@ mod internal {
pub flatten_isr_timelines: bool,
pub disable_task_interactions: bool,
pub use_timeline_id_channel: bool,
pub deviant_event_id_base: Option<u16>,
pub ignored_object_classes: IgnoredObjectClasses,
pub user_event_channel: bool,
pub user_event_format_string: bool,
Expand All @@ -290,6 +295,7 @@ mod internal {
flatten_isr_timelines: c.flatten_isr_timelines,
disable_task_interactions: c.disable_task_interactions,
use_timeline_id_channel: c.use_timeline_id_channel,
deviant_event_id_base: c.deviant_event_id_base,
ignored_object_classes: c.ignored_object_classes,
user_event_channel: c.user_event_channel,
user_event_format_string: c.user_event_format_string,
Expand Down Expand Up @@ -584,6 +590,7 @@ reset = true
flatten_isr_timelines: true,
disable_task_interactions: true,
use_timeline_id_channel: false,
deviant_event_id_base: None,
ignored_object_classes: Default::default(),
user_event_channel: true,
user_event_format_string: true,
Expand Down Expand Up @@ -686,6 +693,7 @@ reset = true
flatten_isr_timelines: true,
disable_task_interactions: true,
use_timeline_id_channel: false,
deviant_event_id_base: None,
ignored_object_classes: Default::default(),
user_event_channel: true,
user_event_format_string: true,
Expand Down Expand Up @@ -790,6 +798,7 @@ reset = true
flatten_isr_timelines: true,
disable_task_interactions: true,
use_timeline_id_channel: true,
deviant_event_id_base: None,
ignored_object_classes: vec![ObjectClass::Queue, ObjectClass::Semaphore]
.into_iter()
.collect(),
Expand Down
158 changes: 158 additions & 0 deletions src/deviant_event_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use crate::trace_recorder::Error;
use byteordered::ByteOrdered;
use modality_api::AttrVal;
use std::io::Read;
use trace_recorder_parser::{
streaming::event::{BaseEvent, EventId},
types::Endianness,
};
use uuid::Uuid;

#[derive(Debug)]
pub struct DeviantEventParser {
endianness: Endianness,
base_event_id: EventId,
uuid_bytes: [u8; 16],
scratch: Vec<u8>,
}

impl DeviantEventParser {
// Event ID offsets from the base
const MUTATOR_ANNOUNCED_OFFSET: u16 = 0;
const MUTATOR_RETIRED_OFFSET: u16 = 1;
const MUTATION_CMD_COMM_OFFSET: u16 = 2;
const MUTATION_CLR_COMM_OFFSET: u16 = 3;
const MUTATION_TRIGGERED_OFFSET: u16 = 4;
const MUTATION_INJECTED_OFFSET: u16 = 5;

pub fn new(endianness: Endianness, base_event_id: EventId) -> Result<Self, Error> {
if base_event_id.0 > 0x0F_FF {
Err(Error::DeviantEvent(format!(
"The deviant custom event base ID {} exceeds the max",
base_event_id
)))
} else {
Ok(Self {
endianness,
base_event_id,
uuid_bytes: [0; 16],
scratch: Vec::with_capacity(64),
})
}
}

/// Returns true if the event was a deviant custom event
pub fn parse(&mut self, event: &BaseEvent) -> Result<Option<DeviantEvent>, Error> {
let kind = match DeviantEventKind::from_base_event(self.base_event_id, event) {
Some(k) => k,
None => return Ok(None),
};
let params = event.parameters();
self.scratch.clear();
for p in params {
self.scratch.extend_from_slice(&p.to_le_bytes());
}

if self.scratch.len() != kind.expected_parameter_byte_count() {
return Err(Error::DeviantEvent(format!(
"The event {} ({}) has an incorrect number of parameter bytes ({}), expected {}",
event.code,
kind.to_modality_name(),
self.scratch.len(),
kind.expected_parameter_byte_count(),
)));
}

let mut r = ByteOrdered::new(
self.scratch.as_slice(),
byteordered::Endianness::from(self.endianness),
);

r.read_exact(&mut self.uuid_bytes)?;
let mutator_uuid = Uuid::from_bytes(self.uuid_bytes);

let mut deviant_event = DeviantEvent {
kind,
mutator_id: (mutator_uuid, uuid_to_integer_attr_val(&mutator_uuid)),
mutation_id: None,
mutation_success: None,
};

match kind {
DeviantEventKind::MutatorAnnounced | DeviantEventKind::MutatorRetired => (),
_ => {
r.read_exact(&mut self.uuid_bytes)?;
let mutation_uuid = Uuid::from_bytes(self.uuid_bytes);
let mutation_success = r.read_u32()?;
deviant_event.mutation_id =
Some((mutation_uuid, uuid_to_integer_attr_val(&mutation_uuid)));
deviant_event.mutation_success = Some((mutation_success != 0).into());
}
}

Ok(Some(deviant_event))
}
}

#[derive(Clone, Debug)]
pub struct DeviantEvent {
pub kind: DeviantEventKind,
pub mutator_id: (Uuid, AttrVal),
pub mutation_id: Option<(Uuid, AttrVal)>,
pub mutation_success: Option<AttrVal>,
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum DeviantEventKind {
MutatorAnnounced,
MutatorRetired,
MutationCmdCommunicated,
MutationClearCommunicated,
MutationTriggered,
MutationInjected,
}

impl DeviantEventKind {
pub const fn to_modality_name(self) -> &'static str {
use DeviantEventKind::*;
match self {
MutatorAnnounced => "modality.mutator.announced",
MutatorRetired => "modality.mutator.retired",
MutationCmdCommunicated => "modality.mutation.command_communicated",
MutationClearCommunicated => "modality.mutation.clear_communicated",
MutationTriggered => "modality.mutation.triggered",
MutationInjected => "modality.mutation.injected",
}
}

const fn expected_parameter_byte_count(self) -> usize {
use DeviantEventKind::*;
match self {
MutatorAnnounced | MutatorRetired => 16, // UUID
_ => 16 + 16 + 4, // UUID, UUID, u32
}
}

fn from_base_event(base_event_id: EventId, event: &BaseEvent) -> Option<Self> {
use DeviantEventKind::*;

if event.code.event_id().0 >= base_event_id.0 {
let offset = event.code.event_id().0 - base_event_id.0;
Some(match offset {
DeviantEventParser::MUTATOR_ANNOUNCED_OFFSET => MutatorAnnounced,
DeviantEventParser::MUTATOR_RETIRED_OFFSET => MutatorRetired,
DeviantEventParser::MUTATION_CMD_COMM_OFFSET => MutationCmdCommunicated,
DeviantEventParser::MUTATION_CLR_COMM_OFFSET => MutationClearCommunicated,
DeviantEventParser::MUTATION_TRIGGERED_OFFSET => MutationTriggered,
DeviantEventParser::MUTATION_INJECTED_OFFSET => MutationInjected,
_ => return None,
})
} else {
None
}
}
}

fn uuid_to_integer_attr_val(u: &Uuid) -> AttrVal {
i128::from_le_bytes(*u.as_bytes()).into()
}
3 changes: 3 additions & 0 deletions src/import/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ pub async fn import<R: Read + Seek + Send>(
if cfg.plugin.use_timeline_id_channel {
warn!("Configuration field 'use-timeline-id-channel` is not supported in snapshot mode");
}
if cfg.plugin.deviant_event_id_base.is_some() {
warn!("Configuration field 'deviant-event-id-base` is not supported in snapshot mode");
}

let client =
IngestClient::connect(&cfg.protocol_parent_url()?, cfg.ingest.allow_insecure_tls).await?;
Expand Down
Loading

0 comments on commit cf34b05

Please sign in to comment.