Skip to content

Commit 1a17bb4

Browse files
Add support of having arguments on events.
This is implemented by encoding arguments as part of the event_id.
1 parent 6925f2d commit 1a17bb4

File tree

6 files changed

+244
-23
lines changed

6 files changed

+244
-23
lines changed

analyzeme/src/event.rs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use crate::timestamp::Timestamp;
2+
use memchr::memchr;
23
use std::borrow::Cow;
34
use std::time::Duration;
45

6+
const SEPARATOR: u8 = 0x1E;
7+
const ARG_TAG: u8 = 0x11;
8+
59
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
610
pub struct Event<'a> {
711
pub event_kind: Cow<'a, str>,
812
pub label: Cow<'a, str>,
9-
pub additional_data: &'a [Cow<'a, str>],
13+
pub additional_data: Vec<Cow<'a, str>>,
1014
pub timestamp: Timestamp,
1115
pub thread_id: u32,
1216
}
@@ -36,4 +40,144 @@ impl<'a> Event<'a> {
3640
Timestamp::Instant(_) => None,
3741
}
3842
}
43+
44+
pub(crate) fn parse_event_id(event_id: Cow<'a, str>) -> (Cow<'a, str>, Vec<Cow<'a, str>>) {
45+
let event_id = match event_id {
46+
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
47+
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
48+
};
49+
50+
let mut parser = Parser::new(event_id);
51+
52+
let label = match parser.parse_label() {
53+
Ok(label) => label,
54+
Err(message) => {
55+
eprintln!("{}", message);
56+
return (Cow::from("<parse error>"), Vec::new());
57+
}
58+
};
59+
60+
let mut args = Vec::new();
61+
62+
while parser.pos != parser.full_text.len() {
63+
match parser.parse_arg() {
64+
Ok(arg) => args.push(arg),
65+
Err(message) => {
66+
eprintln!("{}", message);
67+
break;
68+
}
69+
}
70+
}
71+
72+
(label, args)
73+
}
74+
}
75+
76+
struct Parser<'a> {
77+
full_text: Cow<'a, [u8]>,
78+
pos: usize,
79+
}
80+
81+
impl<'a> Parser<'a> {
82+
fn new(full_text: Cow<'a, [u8]>) -> Parser<'a> {
83+
Parser { full_text, pos: 0 }
84+
}
85+
86+
fn peek(&self) -> u8 {
87+
self.full_text[self.pos]
88+
}
89+
90+
fn parse_label(&mut self) -> Result<Cow<'a, str>, String> {
91+
assert!(self.pos == 0);
92+
self.parse_separator_terminated_text()
93+
}
94+
95+
fn parse_separator_terminated_text(&mut self) -> Result<Cow<'a, str>, String> {
96+
let start = self.pos;
97+
98+
let end = memchr(SEPARATOR, &self.full_text[start..])
99+
.map(|pos| pos + start)
100+
.unwrap_or(self.full_text.len());
101+
102+
if start == end {
103+
return self.err("Zero-length <text>");
104+
}
105+
106+
self.pos = end;
107+
108+
Ok(self.substring(start, end))
109+
}
110+
111+
fn parse_arg(&mut self) -> Result<Cow<'a, str>, String> {
112+
if self.peek() != SEPARATOR {
113+
return self.err(&format!(
114+
"Expected '\\x{:x}' char at start of <argument>",
115+
SEPARATOR
116+
));
117+
}
118+
119+
self.pos += 1;
120+
let tag = self.peek();
121+
122+
match tag {
123+
ARG_TAG => {
124+
self.pos += 1;
125+
self.parse_separator_terminated_text()
126+
}
127+
other => self.err(&format!("Unexpected argument tag '{:x}'", other)),
128+
}
129+
}
130+
131+
fn err<T>(&self, message: &str) -> Result<T, String> {
132+
Err(format!(
133+
r#"Could not parse `event_id`. {} at {} in "{}""#,
134+
message,
135+
self.pos,
136+
std::str::from_utf8(&self.full_text[..]).unwrap()
137+
))
138+
}
139+
140+
fn substring(&self, start: usize, end: usize) -> Cow<'a, str> {
141+
match self.full_text {
142+
Cow::Owned(ref s) => {
143+
let bytes = s[start..end].to_owned();
144+
Cow::Owned(String::from_utf8(bytes).unwrap())
145+
}
146+
Cow::Borrowed(s) => Cow::Borrowed(std::str::from_utf8(&s[start..end]).unwrap()),
147+
}
148+
}
149+
}
150+
151+
#[cfg(test)]
152+
mod tests {
153+
use super::*;
154+
use std::borrow::Cow;
155+
156+
#[test]
157+
fn parse_event_id_no_args() {
158+
let (label, args) = Event::parse_event_id(Cow::from("foo"));
159+
160+
assert_eq!(label, "foo");
161+
assert!(args.is_empty());
162+
}
163+
164+
#[test]
165+
fn parse_event_id_one_arg() {
166+
let (label, args) = Event::parse_event_id(Cow::from("foo\x1e\x11my_arg"));
167+
168+
assert_eq!(label, "foo");
169+
assert_eq!(args, vec![Cow::from("my_arg")]);
170+
}
171+
172+
#[test]
173+
fn parse_event_id_n_args() {
174+
let (label, args) =
175+
Event::parse_event_id(Cow::from("foo\x1e\x11arg1\x1e\x11arg2\x1e\x11arg3"));
176+
177+
assert_eq!(label, "foo");
178+
assert_eq!(
179+
args,
180+
vec![Cow::from("arg1"), Cow::from("arg2"), Cow::from("arg3")]
181+
);
182+
}
39183
}

analyzeme/src/profiling_data.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::event::Event;
22
use crate::lightweight_event::LightweightEvent;
3-
use crate::StringTable;
43
use crate::timestamp::Timestamp;
4+
use crate::StringTable;
55
use measureme::file_header::{
66
read_file_header, write_file_header, CURRENT_FILE_FORMAT_VERSION, FILE_HEADER_SIZE,
77
FILE_MAGIC_EVENT_STREAM,
@@ -94,10 +94,14 @@ impl ProfilingData {
9494

9595
let timestamp = Timestamp::from_raw_event(&raw_event, self.metadata.start_time);
9696

97+
let event_id = string_table.get(raw_event.event_id).to_string();
98+
// Parse out the label and arguments from the `event_id`.
99+
let (label, additional_data) = Event::parse_event_id(event_id);
100+
97101
Event {
98102
event_kind: string_table.get(raw_event.event_kind).to_string(),
99-
label: string_table.get(raw_event.event_id).to_string(),
100-
additional_data: &[],
103+
label,
104+
additional_data,
101105
timestamp,
102106
thread_id: raw_event.thread_id,
103107
}
@@ -319,7 +323,7 @@ mod tests {
319323
Event {
320324
event_kind: Cow::from(event_kind),
321325
label: Cow::from(label),
322-
additional_data: &[],
326+
additional_data: Vec::new(),
323327
timestamp: Timestamp::Interval {
324328
start: SystemTime::UNIX_EPOCH + Duration::from_nanos(start_nanos),
325329
end: SystemTime::UNIX_EPOCH + Duration::from_nanos(end_nanos),
@@ -337,7 +341,7 @@ mod tests {
337341
Event {
338342
event_kind: Cow::from(event_kind),
339343
label: Cow::from(label),
340-
additional_data: &[],
344+
additional_data: Vec::new(),
341345
timestamp: Timestamp::Instant(
342346
SystemTime::UNIX_EPOCH + Duration::from_nanos(timestamp_nanos),
343347
),
@@ -399,7 +403,6 @@ mod tests {
399403
assert_eq!(events[0].to_event(), full_interval("k1", "id1", 0, 10, 100));
400404
assert_eq!(events[1].to_event(), full_interval("k2", "id2", 1, 100, 110));
401405
assert_eq!(events[2].to_event(), full_interval("k3", "id3", 0, 120, 140));
402-
403406
}
404407

405408
#[test]

analyzeme/src/testing_common.rs

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::timestamp::Timestamp;
22
use crate::{Event, ProfilingData};
3-
use measureme::{Profiler, SerializationSink, StringId};
3+
use measureme::{EventIdBuilder, Profiler, SerializationSink, StringId};
44
use rustc_hash::FxHashMap;
55
use std::borrow::Cow;
66
use std::default::Default;
@@ -18,6 +18,27 @@ fn mk_filestem(file_name_stem: &str) -> PathBuf {
1818
path
1919
}
2020

21+
#[derive(Clone)]
22+
struct ExpectedEvent {
23+
kind: Cow<'static, str>,
24+
label: Cow<'static, str>,
25+
args: Vec<Cow<'static, str>>,
26+
}
27+
28+
impl ExpectedEvent {
29+
fn new(
30+
kind: &'static str,
31+
label: &'static str,
32+
args: &[&'static str]
33+
) -> ExpectedEvent {
34+
ExpectedEvent {
35+
kind: Cow::from(kind),
36+
label: Cow::from(label),
37+
args: args.iter().map(|&x| Cow::from(x)).collect(),
38+
}
39+
}
40+
}
41+
2142
// Generate some profiling data. This is the part that would run in rustc.
2243
fn generate_profiling_data<S: SerializationSink>(
2344
filestem: &Path,
@@ -28,25 +49,34 @@ fn generate_profiling_data<S: SerializationSink>(
2849

2950
let event_id_virtual = StringId::new_virtual(42);
3051

52+
let event_id_builder = EventIdBuilder::new(&profiler);
53+
3154
let event_ids = vec![
3255
(
3356
profiler.alloc_string("Generic"),
3457
profiler.alloc_string("SomeGenericActivity"),
3558
),
3659
(profiler.alloc_string("Query"), event_id_virtual),
60+
(
61+
profiler.alloc_string("QueryWithArg"),
62+
event_id_builder.from_label_and_arg(
63+
profiler.alloc_string("AQueryWithArg"),
64+
profiler.alloc_string("some_arg")
65+
),
66+
),
3767
];
3868

3969
// This and event_ids have to match!
40-
let mut event_ids_as_str: FxHashMap<_, _> = Default::default();
41-
event_ids_as_str.insert(event_ids[0].0, "Generic");
42-
event_ids_as_str.insert(event_ids[0].1, "SomeGenericActivity");
43-
event_ids_as_str.insert(event_ids[1].0, "Query");
44-
event_ids_as_str.insert(event_ids[1].1, "SomeQuery");
70+
let expected_events_templates = vec![
71+
ExpectedEvent::new("Generic", "SomeGenericActivity", &[]),
72+
ExpectedEvent::new("Query", "SomeQuery", &[]),
73+
ExpectedEvent::new("QueryWithArg", "AQueryWithArg", &["some_arg"]),
74+
];
4575

4676
let threads: Vec<_> = (0.. num_threads).map(|thread_id| {
4777
let event_ids = event_ids.clone();
4878
let profiler = profiler.clone();
49-
let event_ids_as_str = event_ids_as_str.clone();
79+
let expected_events_templates = expected_events_templates.clone();
5080

5181
std::thread::spawn(move || {
5282
let mut expected_events = Vec::new();
@@ -60,7 +90,7 @@ fn generate_profiling_data<S: SerializationSink>(
6090
thread_id as u32,
6191
4,
6292
&event_ids[..],
63-
&event_ids_as_str,
93+
&expected_events_templates,
6494
&mut expected_events,
6595
);
6696
}
@@ -166,14 +196,16 @@ fn pseudo_invocation<S: SerializationSink>(
166196
thread_id: u32,
167197
recursions_left: usize,
168198
event_ids: &[(StringId, StringId)],
169-
event_ids_as_str: &FxHashMap<StringId, &'static str>,
199+
expected_events_templates: &[ExpectedEvent],
170200
expected_events: &mut Vec<Event<'static>>,
171201
) {
172202
if recursions_left == 0 {
173203
return;
174204
}
175205

176-
let (event_kind, event_id) = event_ids[random % event_ids.len()];
206+
let random_event_index = random % event_ids.len();
207+
208+
let (event_kind, event_id) = event_ids[random_event_index];
177209

178210
let _prof_guard = profiler.start_recording_interval_event(event_kind, event_id, thread_id);
179211

@@ -183,19 +215,19 @@ fn pseudo_invocation<S: SerializationSink>(
183215
thread_id,
184216
recursions_left - 1,
185217
event_ids,
186-
event_ids_as_str,
218+
expected_events_templates,
187219
expected_events,
188220
);
189221

190222
expected_events.push(Event {
191-
event_kind: Cow::from(event_ids_as_str[&event_kind]),
192-
label: Cow::from(event_ids_as_str[&event_id]),
193-
additional_data: &[],
223+
event_kind: expected_events_templates[random_event_index].kind.clone(),
224+
label: expected_events_templates[random_event_index].label.clone(),
225+
additional_data: expected_events_templates[random_event_index].args.clone(),
226+
thread_id,
194227
// We can't test this anyway:
195228
timestamp: Timestamp::Interval {
196229
start: SystemTime::UNIX_EPOCH,
197230
end: SystemTime::UNIX_EPOCH,
198231
},
199-
thread_id,
200232
});
201233
}

measureme/src/event_id.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use crate::{Profiler, SerializationSink, StringComponent, StringId};
2+
3+
/// Event IDs are strings conforming to the following grammar:
4+
///
5+
/// ```ignore
6+
/// <event_id> = <label> {<argument>}
7+
/// <label> = <text>
8+
/// <argument> = '\x1E' '\x11' <text>
9+
/// <text> = regex([^0x1E]+) // Anything but the separator byte
10+
/// ```
11+
///
12+
/// This means there's always a "label", followed by an optional list of
13+
/// arguments. Future versions my support other optional suffixes (with a tag
14+
/// other than '\x11' after the '\x1E' separator), such as a "category".
15+
16+
pub struct EventIdBuilder<'p, S: SerializationSink> {
17+
profiler: &'p Profiler<S>,
18+
}
19+
20+
impl<'p, S: SerializationSink> EventIdBuilder<'p, S> {
21+
pub fn new(profiler: &Profiler<S>) -> EventIdBuilder<'_, S> {
22+
EventIdBuilder { profiler }
23+
}
24+
25+
pub fn from_label(&self, label: StringId) -> StringId {
26+
// Just forward the string ID, i single identifier is a valid event_id
27+
label
28+
}
29+
30+
pub fn from_label_and_arg(&self, label: StringId, arg: StringId) -> StringId {
31+
self.profiler.alloc_string(&[
32+
// Label
33+
StringComponent::Ref(label),
34+
// Seperator and start tag for arg
35+
StringComponent::Value("\x1E\x11"),
36+
// Arg string id
37+
StringComponent::Ref(arg),
38+
])
39+
}
40+
}

measureme/src/file_header.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::serialization::SerializationSink;
66
use byteorder::{ByteOrder, LittleEndian};
77
use std::error::Error;
88

9-
pub const CURRENT_FILE_FORMAT_VERSION: u32 = 4;
9+
pub const CURRENT_FILE_FORMAT_VERSION: u32 = 5;
1010
pub const FILE_MAGIC_EVENT_STREAM: &[u8; 4] = b"MMES";
1111
pub const FILE_MAGIC_STRINGTABLE_DATA: &[u8; 4] = b"MMSD";
1212
pub const FILE_MAGIC_STRINGTABLE_INDEX: &[u8; 4] = b"MMSI";

0 commit comments

Comments
 (0)