blob: 2087f62941101f7c186b4fcdc4da8bf1839d135b [file] [log] [blame] [edit]
use std::convert::TryInto;
use std::{
error::Error,
mem,
path::Path,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use event::Event;
use event_payload::EventPayload;
use lightweight_event::LightweightEvent;
use measureme::file_header::{verify_file_header, FILE_MAGIC_EVENT_STREAM};
pub mod event;
pub mod event_payload;
pub mod lightweight_event;
pub mod stringtable;
// These re-exports allow us to use some types from the measureme version tied to this
// version of decodeme, with explicitly mentioning that measureme version in downstream
// Cargo.tomls.
pub use measureme::file_header::CURRENT_FILE_FORMAT_VERSION;
pub use measureme::file_header::FILE_HEADER_SIZE;
pub use measureme::file_header::FILE_MAGIC_TOP_LEVEL;
pub use measureme::PageTag;
pub use measureme::RawEvent;
use serde::{Deserialize, Deserializer};
use stringtable::StringTable;
fn system_time_from_nanos<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
where
D: Deserializer<'de>,
{
let duration_from_epoch = Duration::from_nanos(u64::deserialize(deserializer)?);
Ok(UNIX_EPOCH
.checked_add(duration_from_epoch)
.expect("a time that can be represented as SystemTime"))
}
#[derive(Clone, Debug, Deserialize)]
pub struct Metadata {
#[serde(deserialize_with = "system_time_from_nanos")]
pub start_time: SystemTime,
pub process_id: u32,
pub cmd: String,
}
#[must_use]
pub fn read_file_header(
bytes: &[u8],
expected_magic: &[u8; 4],
diagnostic_file_path: Option<&Path>,
stream_tag: &str,
) -> Result<u32, Box<dyn Error + Send + Sync>> {
// The implementation here relies on FILE_HEADER_SIZE to have the value 8.
// Let's make sure this assumption cannot be violated without being noticed.
assert_eq!(FILE_HEADER_SIZE, 8);
let diagnostic_file_path = diagnostic_file_path.unwrap_or(Path::new("<in-memory>"));
if bytes.len() < FILE_HEADER_SIZE {
let msg = format!(
"Error reading {} stream in file `{}`: Expected file to contain at least `{:?}` bytes but found `{:?}` bytes",
stream_tag,
diagnostic_file_path.display(),
FILE_HEADER_SIZE,
bytes.len()
);
return Err(From::from(msg));
}
let actual_magic = &bytes[0..4];
if actual_magic != expected_magic {
let msg = format!(
"Error reading {} stream in file `{}`: Expected file magic `{:?}` but found `{:?}`",
stream_tag,
diagnostic_file_path.display(),
expected_magic,
actual_magic
);
return Err(From::from(msg));
}
let file_format_version = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
Ok(file_format_version)
}
const RAW_EVENT_SIZE: usize = std::mem::size_of::<RawEvent>();
#[derive(Debug)]
pub struct EventDecoder {
event_data: Vec<u8>,
stringtable: StringTable,
metadata: Metadata,
}
impl EventDecoder {
pub fn new(
entire_file_data: Vec<u8>,
diagnostic_file_path: Option<&Path>,
) -> Result<EventDecoder, Box<dyn Error + Send + Sync>> {
verify_file_header(
&entire_file_data,
FILE_MAGIC_TOP_LEVEL,
diagnostic_file_path,
"top-level",
)?;
let mut split_data = measureme::split_streams(&entire_file_data[FILE_HEADER_SIZE..]);
let string_data = split_data
.remove(&PageTag::StringData)
.expect("Invalid file: No string data found");
let index_data = split_data
.remove(&PageTag::StringIndex)
.expect("Invalid file: No string index data found");
let event_data = split_data
.remove(&PageTag::Events)
.expect("Invalid file: No event data found");
Self::from_separate_buffers(string_data, index_data, event_data, diagnostic_file_path)
}
pub fn from_separate_buffers(
string_data: Vec<u8>,
index_data: Vec<u8>,
event_data: Vec<u8>,
diagnostic_file_path: Option<&Path>,
) -> Result<EventDecoder, Box<dyn Error + Send + Sync>> {
verify_file_header(
&event_data,
FILE_MAGIC_EVENT_STREAM,
diagnostic_file_path,
"event",
)?;
let stringtable = StringTable::new(string_data, index_data, diagnostic_file_path)?;
let metadata = stringtable.get_metadata().to_string();
let metadata: Metadata = serde_json::from_str(&metadata)?;
Ok(EventDecoder {
event_data,
stringtable,
metadata,
})
}
pub fn num_events(&self) -> usize {
let event_byte_count = self.event_data.len() - FILE_HEADER_SIZE;
assert!(event_byte_count % RAW_EVENT_SIZE == 0);
event_byte_count / RAW_EVENT_SIZE
}
pub fn metadata(&self) -> Metadata {
self.metadata.clone()
}
pub fn decode_full_event<'a>(&'a self, event_index: usize) -> Event<'a> {
let event_start_addr = event_index_to_addr(event_index);
let event_end_addr = event_start_addr.checked_add(RAW_EVENT_SIZE).unwrap();
let raw_event_bytes = &self.event_data[event_start_addr..event_end_addr];
let raw_event = RawEvent::deserialize(raw_event_bytes);
let stringtable = &self.stringtable;
let payload = EventPayload::from_raw_event(&raw_event, self.metadata.start_time);
let event_id = stringtable
.get(raw_event.event_id.to_string_id())
.to_string();
// Parse out the label and arguments from the `event_id`.
let (label, additional_data) = Event::parse_event_id(event_id);
Event {
event_kind: stringtable.get(raw_event.event_kind).to_string(),
label,
additional_data,
payload,
thread_id: raw_event.thread_id,
}
}
pub fn decode_lightweight_event<'a>(&'a self, event_index: usize) -> LightweightEvent {
let event_start_addr = event_index_to_addr(event_index);
let event_end_addr = event_start_addr.checked_add(RAW_EVENT_SIZE).unwrap();
let raw_event_bytes = &self.event_data[event_start_addr..event_end_addr];
let raw_event = RawEvent::deserialize(raw_event_bytes);
let payload = EventPayload::from_raw_event(&raw_event, self.metadata.start_time);
LightweightEvent {
event_index,
payload,
thread_id: raw_event.thread_id,
}
}
}
fn event_index_to_addr(event_index: usize) -> usize {
FILE_HEADER_SIZE + event_index * mem::size_of::<RawEvent>()
}