Skip to content

Commit 9476b16

Browse files
authored
Merge pull request #84 from andjo403/multi_process_support
use meta data in crox events to be able to merge multiple logs
2 parents 13891f8 + 35b1ad2 commit 9476b16

File tree

5 files changed

+130
-61
lines changed

5 files changed

+130
-61
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Unreleased
44
### Added
55
- `flamegraph`: new tool that uses the `inferno` crate to generate flamegraph svg files ([GH-73])
6+
- `crox`: Added the `--dir` parameter to merge all events files in dir in to one trace file ([GH-84])
7+
- `crox`: Added possibility to add multiple `file_prefix` parameters to merge all them to one trace file ([GH-84])
68

79
### Changed
810
- `measureme`: Events are recorded in a more compact format ([GH-76])
@@ -47,3 +49,4 @@
4749
[GH-70]: https://github.com/rust-lang/measureme/pull/70
4850
[GH-73]: https://github.com/rust-lang/measureme/pull/73
4951
[GH-76]: https://github.com/rust-lang/measureme/pull/76
52+
[GH-84]: https://github.com/rust-lang/measureme/pull/84

analyzeme/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ license = "MIT OR Apache-2.0"
88
[dependencies]
99
measureme = { path = "../measureme" }
1010
rustc-hash = "1.0.1"
11+
serde = { version = "1.0", features = [ "derive" ] }
12+
serde_json = "1.0"

analyzeme/src/profiling_data.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,38 @@ use measureme::file_header::{
55
};
66
use measureme::ByteVecSink;
77
use measureme::{ProfilerFiles, RawEvent, SerializationSink, StringTable, StringTableBuilder};
8+
use serde::{Deserialize, Deserializer};
89
use std::error::Error;
910
use std::fs;
1011
use std::mem;
1112
use std::path::Path;
1213
use std::sync::Arc;
13-
use std::time::SystemTime;
14+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1415

1516
const RAW_EVENT_SIZE: usize = mem::size_of::<RawEvent>();
1617

18+
fn system_time_from_nanos<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
19+
where
20+
D: Deserializer<'de>,
21+
{
22+
let duration_from_epoch = Duration::from_nanos(u64::deserialize(deserializer)?);
23+
Ok(UNIX_EPOCH
24+
.checked_add(duration_from_epoch)
25+
.expect("a time that can be represented as SystemTime"))
26+
}
27+
28+
#[derive(Deserialize)]
29+
pub struct Metadata {
30+
#[serde(deserialize_with = "system_time_from_nanos")]
31+
pub start_time: SystemTime,
32+
pub process_id: u32,
33+
pub cmd: String,
34+
}
35+
1736
pub struct ProfilingData {
1837
event_data: Vec<u8>,
1938
string_table: StringTable,
39+
pub metadata: Metadata,
2040
}
2141

2242
impl ProfilingData {
@@ -39,9 +59,13 @@ impl ProfilingData {
3959

4060
let string_table = StringTable::new(string_data, index_data)?;
4161

62+
let metadata = string_table.get_metadata().to_string();
63+
let metadata: Metadata = serde_json::from_str(&metadata)?;
64+
4265
Ok(ProfilingData {
4366
string_table,
4467
event_data,
68+
metadata,
4569
})
4670
}
4771

@@ -88,8 +112,7 @@ impl<'a> ProfilerEventIterator<'a> {
88112

89113
let string_table = &self.data.string_table;
90114

91-
// FIXME: Lots of Rust code compiled in the seventies apparently.
92-
let timestamp = Timestamp::from_raw_event(&raw_event, SystemTime::UNIX_EPOCH);
115+
let timestamp = Timestamp::from_raw_event(&raw_event, self.data.metadata.start_time);
93116

94117
Event {
95118
event_kind: string_table.get(raw_event.event_kind).to_string(),
@@ -240,10 +263,16 @@ impl ProfilingDataBuilder {
240263
CURRENT_FILE_FORMAT_VERSION
241264
);
242265
let string_table = StringTable::new(data_bytes, index_bytes).unwrap();
266+
let metadata = Metadata {
267+
start_time: UNIX_EPOCH,
268+
process_id: 0,
269+
cmd: "test cmd".to_string(),
270+
};
243271

244272
ProfilingData {
245273
event_data,
246274
string_table,
275+
metadata,
247276
}
248277
}
249278

crox/src/main.rs

Lines changed: 89 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ use rustc_hash::FxHashMap;
22
use std::fs;
33
use std::io::BufWriter;
44
use std::path::PathBuf;
5-
use std::time::Duration;
6-
use std::time::SystemTime;
5+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
76

87
use analyzeme::{ProfilingData, Timestamp};
98

9+
use serde::ser::SerializeSeq;
1010
use serde::{Serialize, Serializer};
11+
use serde_json::json;
1112
use std::cmp;
1213
use structopt::StructOpt;
1314

@@ -43,7 +44,11 @@ struct Event {
4344

4445
#[derive(StructOpt, Debug)]
4546
struct Opt {
46-
file_prefix: PathBuf,
47+
#[structopt(required_unless = "dir")]
48+
file_prefix: Vec<PathBuf>,
49+
/// all event trace files in dir will be merged to one chrome_profiler.json file
50+
#[structopt(long = "dir")]
51+
dir: Option<PathBuf>,
4752
/// collapse threads without overlapping events
4853
#[structopt(long = "collapse-threads")]
4954
collapse_threads: bool,
@@ -114,53 +119,98 @@ fn generate_thread_to_collapsed_thread_mapping(
114119
fn main() -> Result<(), Box<dyn std::error::Error>> {
115120
let opt = Opt::from_args();
116121

117-
let data = ProfilingData::new(&opt.file_prefix)?;
118-
119122
let chrome_file = BufWriter::new(fs::File::create("chrome_profiler.json")?);
123+
let mut serializer = serde_json::Serializer::new(chrome_file);
120124

121-
//find the earlier timestamp (it should be the first event)
122-
//subtract one tick so that the start of the event shows in Chrome
123-
let first_event_timestamp = make_start_timestamp(&data);
125+
let mut seq = serializer.serialize_seq(None)?;
124126

125-
let mut serializer = serde_json::Serializer::new(chrome_file);
126-
let thread_to_collapsed_thread = generate_thread_to_collapsed_thread_mapping(&opt, &data);
127-
let mut event_iterator = data.iter();
128-
129-
//create an iterator so we can avoid allocating a Vec with every Event for serialization
130-
let json_event_iterator = std::iter::from_fn(|| {
131-
while let Some(event) = event_iterator.next() {
132-
// Chrome does not seem to like how many QueryCacheHit events we generate
133-
// only handle startStop events for now
134-
if let Timestamp::Interval { start, end } = event.timestamp {
135-
let duration = end.duration_since(start).unwrap();
136-
if let Some(minimum_duration) = opt.minimum_duration {
137-
if duration.as_micros() < minimum_duration {
138-
continue;
139-
}
127+
let dir_paths = file_prefixes_in_dir(&opt)?;
128+
129+
for file_prefix in opt.file_prefix.iter().chain(dir_paths.iter()) {
130+
let data = ProfilingData::new(&file_prefix)?;
131+
132+
let thread_to_collapsed_thread = generate_thread_to_collapsed_thread_mapping(&opt, &data);
133+
134+
// Chrome does not seem to like how many QueryCacheHit events we generate
135+
// only handle Interval events for now
136+
for event in data.iter().filter(|e| !e.timestamp.is_instant()) {
137+
let duration = event.duration().unwrap();
138+
if let Some(minimum_duration) = opt.minimum_duration {
139+
if duration.as_micros() < minimum_duration {
140+
continue;
140141
}
141-
return Some(Event {
142-
name: event.label.clone().into_owned(),
143-
category: event.event_kind.clone().into_owned(),
144-
event_type: EventType::Complete,
145-
timestamp: start.duration_since(first_event_timestamp).unwrap(),
146-
duration,
147-
process_id: 0,
148-
thread_id: *thread_to_collapsed_thread
149-
.get(&event.thread_id)
150-
.unwrap_or(&event.thread_id),
151-
args: None,
152-
});
153142
}
143+
let crox_event = Event {
144+
name: event.label.clone().into_owned(),
145+
category: event.event_kind.clone().into_owned(),
146+
event_type: EventType::Complete,
147+
timestamp: event.timestamp.start().duration_since(UNIX_EPOCH).unwrap(),
148+
duration,
149+
process_id: data.metadata.process_id,
150+
thread_id: *thread_to_collapsed_thread
151+
.get(&event.thread_id)
152+
.unwrap_or(&event.thread_id),
153+
args: None,
154+
};
155+
seq.serialize_element(&crox_event)?;
154156
}
157+
// add crate name for the process_id
158+
let index_of_crate_name = data
159+
.metadata
160+
.cmd
161+
.find(" --crate-name ")
162+
.map(|index| index + 14);
163+
if let Some(index) = index_of_crate_name {
164+
let (_, last) = data.metadata.cmd.split_at(index);
165+
let (crate_name, _) = last.split_at(last.find(" ").unwrap_or(last.len()));
166+
167+
let process_name = json!({
168+
"name": "process_name",
169+
"ph" : "M",
170+
"ts" : 0,
171+
"tid" : 0,
172+
"cat" : "",
173+
"pid" : data.metadata.process_id,
174+
"args": {
175+
"name" : crate_name
176+
}
177+
});
178+
seq.serialize_element(&process_name)?;
179+
}
180+
// sort the processes after start time
181+
let process_name = json!({
182+
"name": "process_sort_index",
183+
"ph" : "M",
184+
"ts" : 0,
185+
"tid" : 0,
186+
"cat" : "",
187+
"pid" : data.metadata.process_id,
188+
"args": {
189+
"sort_index" : data.metadata.start_time.duration_since(UNIX_EPOCH).unwrap().as_micros() as u64
190+
}
191+
});
192+
seq.serialize_element(&process_name)?;
193+
}
155194

156-
None
157-
});
158-
159-
serializer.collect_seq(json_event_iterator)?;
195+
seq.end()?;
160196

161197
Ok(())
162198
}
163199

200+
fn file_prefixes_in_dir(opt: &Opt) -> Result<Vec<PathBuf>, std::io::Error> {
201+
let mut result = Vec::new();
202+
if let Some(dir_path) = &opt.dir {
203+
for entry in fs::read_dir(dir_path)? {
204+
let entry = entry?;
205+
let path = entry.path();
206+
if path.extension().filter(|e| *e == "events").is_some() {
207+
result.push(path)
208+
}
209+
}
210+
}
211+
Ok(result)
212+
}
213+
164214
fn timestamp_to_min_max(timestamp: Timestamp) -> (SystemTime, SystemTime) {
165215
match timestamp {
166216
Timestamp::Instant(t) => (t, t),
@@ -171,22 +221,3 @@ fn timestamp_to_min_max(timestamp: Timestamp) -> (SystemTime, SystemTime) {
171221
}
172222
}
173223
}
174-
175-
// FIXME: Move this to `ProfilingData` and base it on the `start_time` field
176-
// from metadata.
177-
fn make_start_timestamp(data: &ProfilingData) -> SystemTime {
178-
// We cannot assume the first event in the stream actually is the first
179-
// event because interval events are emitted at their end. So in theory it
180-
// is possible that the event with the earliest starting time is the last
181-
// event in the stream (i.e. if there is an interval event that spans the
182-
// entire execution of the profile).
183-
//
184-
// Let's be on the safe side and iterate the whole stream.
185-
let min = data
186-
.iter()
187-
.map(|e| timestamp_to_min_max(e.timestamp).0)
188-
.min()
189-
.unwrap();
190-
191-
min - Duration::from_micros(1)
192-
}

measureme/src/stringtable.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ impl StringTable {
295295
pub fn get<'a>(&'a self, id: StringId) -> StringRef<'a> {
296296
StringRef { id, table: self }
297297
}
298+
pub fn get_metadata<'a>(&'a self) -> StringRef<'a> {
299+
let id = StringId(METADATA_STRING_ID);
300+
self.get(id)
301+
}
298302
}
299303

300304
#[cfg(test)]

0 commit comments

Comments
 (0)