use crate::core::types::DeadlockInfo;
use anyhow::{Context, Result};
use base64::alphabet::URL_SAFE;
use base64::engine::{Engine as _, general_purpose};
use flate2::Compression;
use flate2::write::GzEncoder;
use rmp_serde;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
pub(crate) fn process_log_for_url<P: AsRef<Path>>(log_path: P) -> Result<String> {
let file = File::open(log_path).context("Failed to open log file")?;
let reader = BufReader::new(file);
let mut compact_events = Vec::new();
let mut terminal_deadlock: Option<DeadlockCompact> = None;
for line in reader.lines() {
let line = line.context("Failed to read line from log file")?;
if let Ok(entry) = serde_json::from_str::<LogEntry>(&line) {
let event = parse_log_entry(entry).context("Failed to parse log entry")?;
compact_events.push(event);
} else if let Ok(dl) = serde_json::from_str::<DeadlockRecord>(&line) {
terminal_deadlock = Some(DeadlockCompact {
thread_cycle: dl.deadlock.thread_cycle.iter().map(|&t| t as u64).collect(),
thread_waiting_for_locks: dl
.deadlock
.thread_waiting_for_locks
.iter()
.map(|&(t, l)| (t as u64, l as u64))
.collect(),
timestamp: dl.deadlock.timestamp.clone(),
});
}
}
let compact_output: (Events, Option<DeadlockCompact>) = (compact_events, terminal_deadlock);
let msgpack =
rmp_serde::to_vec(&compact_output).context("Failed to convert data to MessagePack")?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
encoder
.write_all(&msgpack)
.context("Failed to compress data")?;
let compressed = encoder.finish().context("Failed to finish compression")?;
let base64_engine = general_purpose::GeneralPurpose::new(&URL_SAFE, general_purpose::PAD);
let encoded = base64_engine.encode(compressed);
Ok(encoded)
}
#[derive(Debug, Deserialize)]
struct LogEntry {
sequence: u64,
thread_id: u64,
lock_id: u64,
event: String,
timestamp: f64,
#[serde(default)]
parent_id: Option<u64>,
#[serde(default)]
woken_thread: Option<u64>,
}
type Event = (u64, u64, u64, u8, f64, u64, u64);
type Events = Vec<Event>;
#[derive(Serialize, Deserialize)]
pub struct DeadlockCompact {
pub thread_cycle: Vec<u64>,
pub thread_waiting_for_locks: Vec<(u64, u64)>,
pub timestamp: String,
}
fn parse_log_entry(entry: LogEntry) -> Result<Event> {
let event_code = match entry.event.as_str() {
"ThreadSpawn" => 0u8,
"ThreadExit" => 1u8,
"MutexSpawn" => 2u8,
"MutexExit" => 3u8,
"RwSpawn" => 4u8,
"RwExit" => 5u8,
"CondvarSpawn" => 6u8,
"CondvarExit" => 7u8,
"MutexAttempt" => 10u8,
"MutexAcquired" => 11u8,
"MutexReleased" => 12u8,
"RwReadAttempt" => 20u8,
"RwReadAcquired" => 21u8,
"RwReadReleased" => 22u8,
"RwWriteAttempt" => 23u8,
"RwWriteAcquired" => 24u8,
"RwWriteReleased" => 25u8,
"CondvarWaitBegin" => 30u8,
"CondvarWaitEnd" => 31u8,
"CondvarNotifyOne" => 32u8,
"CondvarNotifyAll" => 33u8,
other => anyhow::bail!("Invalid event type: '{}'", other),
};
let parent_id = entry.parent_id.unwrap_or(0);
let woken_thread = entry.woken_thread.unwrap_or(0);
let compact_event = (
entry.sequence,
entry.thread_id,
entry.lock_id,
event_code,
entry.timestamp,
parent_id,
woken_thread,
);
Ok(compact_event)
}
#[derive(Deserialize)]
struct DeadlockRecord {
deadlock: DeadlockInfo,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_log_entry() {
let log_entry = LogEntry {
sequence: 42,
thread_id: 1,
lock_id: 2,
event: "ThreadSpawn".to_string(),
timestamp: 1754931441.237695,
parent_id: Some(5),
woken_thread: None,
};
let result = parse_log_entry(log_entry).expect("Failed to parse log entry");
assert_eq!(result.0, 42); assert_eq!(result.1, 1); assert_eq!(result.2, 2); assert_eq!(result.3, 0); assert_eq!(result.5, 5); assert_eq!(result.6, 0); }
#[test]
fn test_unique_event_codes() {
let events = vec![
("ThreadSpawn", 0u8),
("ThreadExit", 1u8),
("MutexSpawn", 2u8),
("MutexExit", 3u8),
("RwSpawn", 4u8),
("RwExit", 5u8),
("CondvarSpawn", 6u8),
("CondvarExit", 7u8),
("MutexAttempt", 10u8),
("MutexAcquired", 11u8),
("MutexReleased", 12u8),
("RwReadAttempt", 20u8),
("RwReadAcquired", 21u8),
("RwReadReleased", 22u8),
("RwWriteAttempt", 23u8),
("RwWriteAcquired", 24u8),
("RwWriteReleased", 25u8),
("CondvarWaitBegin", 30u8),
("CondvarWaitEnd", 31u8),
("CondvarNotifyOne", 32u8),
("CondvarNotifyAll", 33u8),
];
for (event_name, expected_code) in events {
let log_entry = LogEntry {
sequence: 0,
thread_id: 1,
lock_id: 2,
event: event_name.to_string(),
timestamp: 1754931441.237695,
parent_id: None,
woken_thread: None,
};
let result = parse_log_entry(log_entry)
.unwrap_or_else(|_| panic!("Failed to parse {event_name}"));
assert_eq!(
result.3, expected_code,
"Event {event_name} should have code {expected_code}",
);
}
}
}