Skip to content

Commit ba0ecee

Browse files
Replace (start, stop) event pairs with interval events.
This reduces the number of events that need to be recorded.
1 parent 3b7c3a8 commit ba0ecee

File tree

14 files changed

+739
-604
lines changed

14 files changed

+739
-604
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
### Added
55
- `flamegraph`: new tool that uses the `inferno` crate to generate flamegraph svg files ([GH-73])
66

7+
### Changed
8+
- `measureme`: Events are recorded in a more compact format ([GH-76])
9+
- `stack_collapse`: Removed the `--interval` commandline option ([GH-76])
10+
711
## [0.4.0] - 2019-10-24
812
### Added
913
- `measureme`: Added RAII-based API for recording events ([GH-70])
@@ -42,3 +46,4 @@
4246
[GH-60]: https://github.com/rust-lang/measureme/pull/60
4347
[GH-70]: https://github.com/rust-lang/measureme/pull/70
4448
[GH-73]: https://github.com/rust-lang/measureme/pull/73
49+
[GH-76]: https://github.com/rust-lang/measureme/pull/76

crox/src/main.rs

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use std::path::PathBuf;
55
use std::time::Duration;
66
use std::time::SystemTime;
77

8-
use measureme::{MatchingEvent, ProfilingData};
8+
use measureme::{ProfilingData, Timestamp};
99

1010
use serde::{Serialize, Serializer};
11+
use std::cmp;
1112
use structopt::StructOpt;
1213

1314
fn as_micros<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
@@ -64,14 +65,12 @@ fn generate_thread_to_collapsed_thread_mapping(
6465
for event in data.iter() {
6566
thread_start_and_end
6667
.entry(event.thread_id)
67-
.and_modify(|(start, end)| {
68-
if *start > event.timestamp {
69-
*start = event.timestamp;
70-
} else if *end < event.timestamp {
71-
*end = event.timestamp;
72-
}
68+
.and_modify(|(thread_start, thread_end)| {
69+
let (event_min, event_max) = timestamp_to_min_max(event.timestamp);
70+
*thread_start = cmp::min(*thread_start, event_min);
71+
*thread_end = cmp::max(*thread_end, event_max);
7372
})
74-
.or_insert_with(|| (event.timestamp, event.timestamp));
73+
.or_insert_with(|| timestamp_to_min_max(event.timestamp));
7574
}
7675
// collect the the threads in order of the end time
7776
let mut end_and_thread = thread_start_and_end
@@ -120,37 +119,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
120119

121120
//find the earlier timestamp (it should be the first event)
122121
//subtract one tick so that the start of the event shows in Chrome
123-
let first_event_timestamp = data.iter().next().unwrap().timestamp - Duration::from_micros(1);
122+
let first_event_timestamp = make_start_timestamp(&data);
124123

125124
let mut serializer = serde_json::Serializer::new(chrome_file);
126125
let thread_to_collapsed_thread = generate_thread_to_collapsed_thread_mapping(&opt, &data);
127-
let mut event_iterator = data.iter_matching_events();
126+
let mut event_iterator = data.iter();
128127

129128
//create an iterator so we can avoid allocating a Vec with every Event for serialization
130129
let json_event_iterator = std::iter::from_fn(|| {
131130
while let Some(event) = event_iterator.next() {
132131
// Chrome does not seem to like how many QueryCacheHit events we generate
133132
// only handle startStop events for now
134-
if let MatchingEvent::StartStop(start, stop) = event {
135-
let duration = stop.timestamp.duration_since(start.timestamp).unwrap();
133+
if let Timestamp::Interval { start, end } = event.timestamp {
134+
let duration = end.duration_since(start).unwrap();
136135
if let Some(minimum_duration) = opt.minimum_duration {
137136
if duration.as_micros() < minimum_duration {
138137
continue;
139138
}
140139
}
141140
return Some(Event {
142-
name: start.label.clone().into_owned(),
143-
category: start.event_kind.clone().into_owned(),
141+
name: event.label.clone().into_owned(),
142+
category: event.event_kind.clone().into_owned(),
144143
event_type: EventType::Complete,
145-
timestamp: start
146-
.timestamp
147-
.duration_since(first_event_timestamp)
148-
.unwrap(),
144+
timestamp: start.duration_since(first_event_timestamp).unwrap(),
149145
duration,
150146
process_id: 0,
151147
thread_id: *thread_to_collapsed_thread
152-
.get(&start.thread_id)
153-
.unwrap_or(&start.thread_id),
148+
.get(&event.thread_id)
149+
.unwrap_or(&event.thread_id),
154150
args: None,
155151
});
156152
}
@@ -163,3 +159,33 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
163159

164160
Ok(())
165161
}
162+
163+
fn timestamp_to_min_max(timestamp: Timestamp) -> (SystemTime, SystemTime) {
164+
match timestamp {
165+
Timestamp::Instant(t) => (t, t),
166+
Timestamp::Interval { start, end } => {
167+
// Usually start should always be greater than end, but let's not
168+
// choke on invalid data here.
169+
(cmp::min(start, end), cmp::max(start, end))
170+
}
171+
}
172+
}
173+
174+
// FIXME: Move this to `ProfilingData` and base it on the `start_time` field
175+
// from metadata.
176+
fn make_start_timestamp(data: &ProfilingData) -> SystemTime {
177+
// We cannot assume the first event in the stream actually is the first
178+
// event because interval events are emitted at their end. So in theory it
179+
// is possible that the event with the earliest starting time is the last
180+
// event in the stream (i.e. if there is an interval event that spans the
181+
// entire execution of the profile).
182+
//
183+
// Let's be on the safe side and iterate the whole stream.
184+
let min = data
185+
.iter()
186+
.map(|e| timestamp_to_min_max(e.timestamp).0)
187+
.min()
188+
.unwrap();
189+
190+
min - Duration::from_micros(1)
191+
}

flamegraph/src/main.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,14 @@ use inferno::flamegraph::{from_lines, Options as FlamegraphOptions};
1414
#[derive(StructOpt, Debug)]
1515
struct Opt {
1616
file_prefix: PathBuf,
17-
18-
/// The sampling interval in milliseconds
19-
#[structopt(short = "i", long = "interval", default_value = "1")]
20-
interval: u64,
2117
}
2218

2319
fn main() -> Result<(), Box<dyn Error>> {
2420
let opt = Opt::from_args();
2521

2622
let profiling_data = ProfilingData::new(&opt.file_prefix)?;
2723

28-
let recorded_stacks = collapse_stacks(profiling_data.iter(), opt.interval)
24+
let recorded_stacks = collapse_stacks(&profiling_data)
2925
.iter()
3026
.map(|(unique_stack, count)| format!("{} {}", unique_stack, count))
3127
.collect::<Vec<_>>();

measureme/src/event.rs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,86 @@
1-
use crate::raw_event::TimestampKind;
1+
use crate::raw_event::RawEvent;
22
use std::borrow::Cow;
3-
use std::time::SystemTime;
3+
use std::time::{Duration, SystemTime};
44

55
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
66
pub struct Event<'a> {
77
pub event_kind: Cow<'a, str>,
88
pub label: Cow<'a, str>,
99
pub additional_data: &'a [Cow<'a, str>],
10-
pub timestamp: SystemTime,
11-
pub timestamp_kind: TimestampKind,
10+
pub timestamp: Timestamp,
1211
pub thread_id: u64,
1312
}
13+
14+
impl<'a> Event<'a> {
15+
/// Returns true if the time interval of `self` completely contains the
16+
/// time interval of `other`.
17+
pub fn contains(&self, other: &Event<'_>) -> bool {
18+
match self.timestamp {
19+
Timestamp::Interval {
20+
start: self_start,
21+
end: self_end,
22+
} => match other.timestamp {
23+
Timestamp::Interval {
24+
start: other_start,
25+
end: other_end,
26+
} => self_start <= other_start && other_end <= self_end,
27+
Timestamp::Instant(other_t) => self_start <= other_t && other_t <= self_end,
28+
},
29+
Timestamp::Instant(_) => false,
30+
}
31+
}
32+
33+
pub fn duration(&self) -> Option<Duration> {
34+
match self.timestamp {
35+
Timestamp::Interval { start, end } => end.duration_since(start).ok(),
36+
Timestamp::Instant(_) => None,
37+
}
38+
}
39+
}
40+
41+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
42+
pub enum Timestamp {
43+
Interval { start: SystemTime, end: SystemTime },
44+
Instant(SystemTime),
45+
}
46+
47+
impl Timestamp {
48+
pub fn from_raw_event(raw_event: &RawEvent, start_time: SystemTime) -> Timestamp {
49+
if raw_event.end_ns == std::u64::MAX {
50+
let t = start_time + Duration::from_nanos(raw_event.start_ns);
51+
Timestamp::Instant(t)
52+
} else {
53+
let start = start_time + Duration::from_nanos(raw_event.start_ns);
54+
let end = start_time + Duration::from_nanos(raw_event.end_ns);
55+
Timestamp::Interval { start, end }
56+
}
57+
}
58+
59+
pub fn contains(&self, t: SystemTime) -> bool {
60+
match *self {
61+
Timestamp::Interval { start, end } => t >= start && t < end,
62+
Timestamp::Instant(_) => false,
63+
}
64+
}
65+
66+
pub fn is_instant(&self) -> bool {
67+
match *self {
68+
Timestamp::Interval { .. } => false,
69+
Timestamp::Instant(_) => true,
70+
}
71+
}
72+
73+
pub fn start(&self) -> SystemTime {
74+
match *self {
75+
Timestamp::Interval { start, .. } => start,
76+
Timestamp::Instant(t) => t,
77+
}
78+
}
79+
80+
pub fn end(&self) -> SystemTime {
81+
match *self {
82+
Timestamp::Interval { end, .. } => end,
83+
Timestamp::Instant(t) => t,
84+
}
85+
}
86+
}

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 = 0;
9+
pub const CURRENT_FILE_FORMAT_VERSION: u32 = 1;
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";

measureme/src/lib.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
//! [`ProfilingData::iter()`]: struct.ProfilingData.html#method.iter
5151
//! [`ProfilingData::iter_matching_events()`]: struct.ProfilingData.html#method.iter_matching_events
5252
//! [`StringId`]: struct.StringId.html
53-
//! [`TimestampKind`]: enum.TimestampKind.html
5453
5554
#![deny(warnings)]
5655

@@ -69,14 +68,14 @@ mod stringtable;
6968
pub mod rustc;
7069
pub mod testing_common;
7170

72-
pub use crate::event::Event;
71+
pub use crate::event::{Event, Timestamp};
7372
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
7473
pub use crate::file_serialization_sink::FileSerializationSink;
7574
#[cfg(not(target_arch = "wasm32"))]
7675
pub use crate::mmap_serialization_sink::MmapSerializationSink;
7776
pub use crate::profiler::{Profiler, ProfilerFiles, TimingGuard};
78-
pub use crate::profiling_data::{MatchingEvent, ProfilingData, ProfilingDataBuilder};
79-
pub use crate::raw_event::{RawEvent, Timestamp, TimestampKind};
77+
pub use crate::profiling_data::{ProfilingData, ProfilingDataBuilder};
78+
pub use crate::raw_event::RawEvent;
8079
pub use crate::serialization::{Addr, ByteVecSink, SerializationSink};
8180
pub use crate::stringtable::{
8281
SerializableString, StringId, StringRef, StringTable, StringTableBuilder,

measureme/src/profiler.rs

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::file_header::{write_file_header, FILE_MAGIC_EVENT_STREAM};
2-
use crate::raw_event::{RawEvent, Timestamp, TimestampKind};
2+
use crate::raw_event::RawEvent;
33
use crate::serialization::SerializationSink;
44
use crate::stringtable::{SerializableString, StringId, StringTableBuilder};
55
use std::error::Error;
@@ -83,32 +83,38 @@ impl<S: SerializationSink> Profiler<S> {
8383

8484
/// Records an event with the given parameters. The event time is computed
8585
/// automatically.
86-
pub fn record_event(
87-
&self,
86+
pub fn record_instant_event(&self, event_kind: StringId, event_id: StringId, thread_id: u64) {
87+
let raw_event =
88+
RawEvent::new_instant(event_kind, event_id, thread_id, self.nanos_since_start());
89+
90+
self.record_raw_event(&raw_event);
91+
}
92+
93+
/// Creates a "start" event and returns a `TimingGuard` that will create
94+
/// the corresponding "end" event when it is dropped.
95+
pub fn start_recording_interval_event<'a>(
96+
&'a self,
8897
event_kind: StringId,
8998
event_id: StringId,
9099
thread_id: u64,
91-
timestamp_kind: TimestampKind,
92-
) {
93-
let duration_since_start = self.start_time.elapsed();
94-
let nanos_since_start = duration_since_start.as_secs() * 1_000_000_000
95-
+ duration_since_start.subsec_nanos() as u64;
96-
let timestamp = Timestamp::new(nanos_since_start, timestamp_kind);
97-
98-
let raw_event = RawEvent {
100+
) -> TimingGuard<'a, S> {
101+
TimingGuard {
102+
profiler: self,
103+
event_id,
99104
event_kind,
100-
id: event_id,
101105
thread_id,
102-
timestamp,
103-
};
106+
start_ns: self.nanos_since_start(),
107+
}
108+
}
104109

110+
fn record_raw_event(&self, raw_event: &RawEvent) {
105111
self.event_sink
106112
.write_atomic(std::mem::size_of::<RawEvent>(), |bytes| {
107113
debug_assert_eq!(bytes.len(), std::mem::size_of::<RawEvent>());
108114

109115
let raw_event_bytes: &[u8] = unsafe {
110116
std::slice::from_raw_parts(
111-
&raw_event as *const _ as *const u8,
117+
raw_event as *const _ as *const u8,
112118
std::mem::size_of::<RawEvent>(),
113119
)
114120
};
@@ -117,22 +123,9 @@ impl<S: SerializationSink> Profiler<S> {
117123
});
118124
}
119125

120-
/// Creates a "start" event and returns a `TimingGuard` that will create
121-
/// the corresponding "end" event when it is dropped.
122-
pub fn start_recording_interval_event<'a>(
123-
&'a self,
124-
event_kind: StringId,
125-
event_id: StringId,
126-
thread_id: u64,
127-
) -> TimingGuard<'a, S> {
128-
self.record_event(event_kind, event_id, thread_id, TimestampKind::Start);
129-
130-
TimingGuard {
131-
profiler: self,
132-
event_id,
133-
event_kind,
134-
thread_id,
135-
}
126+
fn nanos_since_start(&self) -> u64 {
127+
let duration_since_start = self.start_time.elapsed();
128+
duration_since_start.as_secs() * 1_000_000_000 + duration_since_start.subsec_nanos() as u64
136129
}
137130
}
138131

@@ -144,16 +137,20 @@ pub struct TimingGuard<'a, S: SerializationSink> {
144137
event_id: StringId,
145138
event_kind: StringId,
146139
thread_id: u64,
140+
start_ns: u64,
147141
}
148142

149143
impl<'a, S: SerializationSink> Drop for TimingGuard<'a, S> {
150144
#[inline]
151145
fn drop(&mut self) {
152-
self.profiler.record_event(
146+
let raw_event = RawEvent::new_interval(
153147
self.event_kind,
154148
self.event_id,
155149
self.thread_id,
156-
TimestampKind::End,
150+
self.start_ns,
151+
self.profiler.nanos_since_start(),
157152
);
153+
154+
self.profiler.record_raw_event(&raw_event);
158155
}
159156
}

0 commit comments

Comments
 (0)