7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 1 | use std::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 2 | collections::BTreeMap, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 3 | fs, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 4 | io::{self, BufRead, Write}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 5 | ops::Sub, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 6 | path, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 7 | time::Duration, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 8 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 9 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 10 | use chrono::{DateTime, FixedOffset, TimeDelta}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 11 | use indexmap::{IndexMap, map::Entry}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 12 | use regex::RegexBuilder; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 13 | use url::{Position as UrlPosition, Url}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 14 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 15 | use crate::Result; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 16 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 17 | const OUTPUT_TEMPLATE: &str = include_str!("sync.template.html"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 18 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 19 | pub(super) fn run(log_path: path::PathBuf, output_path: path::PathBuf) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 20 | let find_sync = RegexBuilder::new( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 21 | r#" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 22 | # Datetime of the log line. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 23 | (?<datetime>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 24 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 25 | # Ensure it's about the `http_client` scope. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 26 | .*matrix_sdk::http_client |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 27 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 28 | # Ensure it's about a sync. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 29 | .*>\ssync_once\{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 30 | conn_id="(?<connection_id>[^"]+)" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 31 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 32 | # Since https://github.com/matrix-org/matrix-rust-sdk/pull/6118, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 33 | # we have `pos` and `timeout`. We must consider they are |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 34 | # optional. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 35 | ( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 36 | .*? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 37 | (\spos="(?<pos>[^"]+)")? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 38 | \stimeout=(?<timeout>\d+) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 39 | )? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 40 | \} |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 41 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 42 | # Let's capture some data about `send()`! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 43 | \s>\ssend\{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 44 | request_id="REQ-(?<request_id>\d+)" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 45 | \smethod=(?<method>\S+) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 46 | \suri="(?<uri>[^"]+)" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 47 | # If there is a `request_size`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 48 | (.*\srequest_size="(?<request_size>[^"]+)")? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 49 | # If this is a response, there is a `status`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 50 | (.*\sstatus=(?<status>\d+))? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 51 | # If there is a `response_size`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 52 | (.*\sresponse_size="(?<response_size>[^"]+)")? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 53 | "#, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 54 | ) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 55 | .ignore_whitespace(true) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 56 | .build() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 57 | .expect("Failed to build the `find_sync` regex"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 58 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 59 | let log_file = fs::File::open(&log_path)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 60 | let mut output_file = fs::File::create(&output_path)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 61 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 62 | let reader = io::BufReader::new(log_file); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 63 | let mut number_of_analysed_lines = 0; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 64 | let mut number_of_matched_lines = 0; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 65 | let mut smallest_start_at = None; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 66 | let mut largest_end_at = None; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 67 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 68 | let mut spans: BTreeMap<ConnectionId, IndexMap<RequestId, Span>> = BTreeMap::new(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 69 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 70 | for (line_nth, line) in reader.lines().enumerate().map(|(nth, line)| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 71 | ( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 72 | nth + 1, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 73 | line.unwrap_or_else(|error| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 74 | panic!("Failed to read line #{nth}\n{error}"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 75 | }), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 76 | ) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 77 | }) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 78 | number_of_analysed_lines += 1; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 79 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 80 | if let Some(captures) = find_sync.captures(&line) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 81 | number_of_matched_lines += 1; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 82 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 83 | let date_time = DateTime::parse_from_rfc3339( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 84 | captures.name("datetime").expect("Failed to capture `datetime`").as_str(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 85 | ) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 86 | .expect("Failed to parse `datetime`"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 87 | let connection_id = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 88 | captures.name("connection_id").expect("Failed to capture `connection_id`").as_str(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 89 | let pos = captures.name("pos").map(|pos| pos.as_str()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 90 | let timeout = captures.name("timeout").and_then(|timeout| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 91 | timeout.as_str().parse::<u64>().ok().map(Duration::from_millis) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 92 | }); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 93 | let request_id = captures |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 94 | .name("request_id") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 95 | .expect("Failed to capture `request_id`") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 96 | .as_str() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 97 | .parse() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 98 | .expect("Failed to parse `request_id`"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 99 | let method = captures.name("method").expect("Failed to capture `method`").as_str(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 100 | let uri = captures.name("uri").expect("Failed to capture `uri`").as_str(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 101 | let request_size = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 102 | captures.name("request_size").map(|request_size| request_size.as_str()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 103 | let response_size = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 104 | captures.name("response_size").map(|response_size| response_size.as_str()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 105 | let status = captures.name("status").and_then(|status| status.as_str().parse().ok()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 106 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 107 | if let Some(smallest_start_at_inner) = smallest_start_at { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 108 | if smallest_start_at_inner > date_time { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 109 | smallest_start_at = Some(date_time); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 110 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 111 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 112 | smallest_start_at = Some(date_time); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 113 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 114 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 115 | if let Some(largest_end_at_inner) = largest_end_at { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 116 | if largest_end_at_inner < date_time { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 117 | largest_end_at = Some(date_time); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 118 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 119 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 120 | largest_end_at = Some(date_time); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 121 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 122 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 123 | let spans_for_connection_id = spans.entry(connection_id.to_owned()).or_default(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 124 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 125 | match spans_for_connection_id.entry(request_id) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 126 | Entry::Vacant(entry) => { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 127 | entry.insert(Span { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 128 | status: None, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 129 | method: method.to_owned(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 130 | uri: uri.to_owned(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 131 | pos: pos.map(ToOwned::to_owned), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 132 | timeout, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 133 | request_size: request_size.map(ToOwned::to_owned), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 134 | response_size: response_size.map(ToOwned::to_owned), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 135 | start_at: date_time, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 136 | duration: TimeDelta::zero(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 137 | request_log_line: line_nth, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 138 | response_log_line: None, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 139 | }); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 140 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 141 | Entry::Occupied(mut entry) => { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 142 | let span = entry.get_mut(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 143 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 144 | if let Some(status) = status { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 145 | span.status = Some(status); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 146 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 147 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 148 | span.duration = date_time.sub(&span.start_at); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 149 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 150 | if let Some(request_size) = request_size { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 151 | span.request_size = Some(request_size.to_owned()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 152 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 153 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 154 | if let Some(response_size) = response_size { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 155 | span.response_size = Some(response_size.to_owned()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 156 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 157 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 158 | span.response_log_line = Some(line_nth); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 159 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 160 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 161 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 162 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 163 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 164 | let smallest_start_at = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 165 | smallest_start_at.map(|date_time| date_time.timestamp_millis()).unwrap_or_default(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 166 | let largest_end_at = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 167 | largest_end_at.map(|date_time| date_time.timestamp_millis()).unwrap_or_default(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 168 | let end_at = largest_end_at.saturating_sub(smallest_start_at).to_string(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 169 | let rows = spans |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 170 | .iter() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 171 | .flat_map(|(connection_id, spans)| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 172 | spans.iter().map( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 173 | |( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 174 | request_id, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 175 | Span { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 176 | status, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 177 | method, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 178 | uri, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 179 | pos, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 180 | timeout, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 181 | request_size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 182 | response_size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 183 | start_at, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 184 | duration, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 185 | request_log_line, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 186 | response_log_line |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 187 | }, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 188 | )| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 189 | let uri = Url::parse(uri); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 190 | let duration = duration.num_milliseconds().unsigned_abs(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 191 | let timeout: Option<u64> = timeout.map(|timeout| timeout.as_millis().try_into().expect("Failed to cast a u128 to u64")); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 192 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 193 | format!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 194 | " <tr id=\"{connection_id}-{request_id}\"> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 195 | <td><code>{connection_id}</code></td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 196 | <td><a href=\"#{connection_id}-{request_id}\" title=\"Permalink to this line\"><code>{request_id}</code></a></td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 197 | <td data-status-family=\"{status_family}\"><span>{status}</span></td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 198 | <td><code>{method}</code></td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 199 | <td title=\"{domain}\">{domain}</td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 200 | <td title=\"{path}\">{path}</td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 201 | <td>{timeout_label}</td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 202 | <td>{request_size}</td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 203 | <td>{response_size}</td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 204 | <td><time datetime=\"{date_time}\">{time}</time></td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 205 | <td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 206 | <div class=\"span\" style=\"--start-at: {start_at}; --timeout: {timeout}; --duration: {duration}\"><span>{duration_label}</span></div> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 207 | <details> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 208 | <summary><span class=\"hidden\">information</span></summary> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 209 | <ul> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 210 | <li>Request log line number: {request_log_line}</li> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 211 | <li>Response log line number: {response_log_line}</li> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 212 | <li>Sliding Sync <code>pos</code>: <span class=\"text-overflow\">{pos}</span></li> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 213 | </ul> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 214 | </details> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 215 | </td> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 216 | </tr> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 217 | ", |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 218 | connection_id = connection_id.clone(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 219 | status = status |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 220 | .map(|status| status.to_string()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 221 | .unwrap_or_else(|| "×".to_owned()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 222 | status_family = status |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 223 | .map(|status| (if status > 0 { status / 100 } else { 0 } ).to_string()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 224 | .unwrap_or_else(|| "cancelled".to_owned()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 225 | domain = uri |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 226 | .as_ref() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 227 | .map(|uri| uri[UrlPosition::BeforeHost..UrlPosition::BeforePath].to_owned()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 228 | .unwrap_or_default(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 229 | path = uri |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 230 | .as_ref() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 231 | .map(|uri| uri[UrlPosition::BeforePath..].to_owned()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 232 | .unwrap_or_default(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 233 | timeout_label = timeout |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 234 | .map(|timeout| format!("{timeout}ms")) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 235 | .unwrap_or_else(|| "(unknown)".to_owned()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 236 | timeout = timeout |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 237 | .map(|timeout| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 238 | // The response was received before hitting the timeout. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 239 | if duration < timeout { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 240 | 0 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 241 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 242 | timeout |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 243 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 244 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 245 | .unwrap_or(0), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 246 | request_size = request_size |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 247 | .clone() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 248 | .map(|request_size| request_size.to_string()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 249 | .unwrap_or_else(|| "".to_owned()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 250 | response_size = response_size |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 251 | .clone() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 252 | .map(|response_size| response_size.to_string()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 253 | .unwrap_or_else(|| "".to_owned()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 254 | date_time = start_at.format("%+"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 255 | time = start_at.format("%H:%M:%S%.3f"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 256 | start_at = start_at |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 257 | .timestamp_millis() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 258 | .saturating_sub(smallest_start_at), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 259 | duration_label = if duration > 0 { format!("{duration}ms") } else { "<em>cancelled</em>".to_owned() }, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 260 | response_log_line = response_log_line.map(|line| line.to_string()).unwrap_or_else(|| "(none)".to_owned()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 261 | pos = pos.clone().unwrap_or_else(|| "(unknown)".to_owned()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 262 | ) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 263 | }, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 264 | ) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 265 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 266 | .collect::<String>(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 267 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 268 | let output = OUTPUT_TEMPLATE |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 269 | .replace("{log_file}", &log_path.to_string_lossy()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 270 | .replace("{end_at}", &end_at) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 271 | .replace("{rows}", &rows); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 272 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 273 | output_file.write_all(output.as_bytes()).expect("Failed to write the output"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 274 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 275 | println!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 276 | "\nNumber of analysed log lines: {number_of_analysed_lines}\n\ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 277 | Number of matched lines: {number_of_matched_lines}\n\ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 278 | Output file: {output_path:?}\n\ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 279 | Done!" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 280 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 281 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 282 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 283 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 284 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 285 | type ConnectionId = String; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 286 | type RequestId = u32; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 287 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 288 | #[derive(Debug)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 289 | struct Span { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 290 | status: Option<u8>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 291 | method: String, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 292 | uri: String, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 293 | pos: Option<String>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 294 | timeout: Option<Duration>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 295 | request_size: Option<String>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 296 | response_size: Option<String>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 297 | start_at: DateTime<FixedOffset>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 298 | duration: TimeDelta, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 299 | request_log_line: usize, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 300 | response_log_line: Option<usize>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 301 | } |