Fix HAR capture missing API requests under heavy traffic (#1135)
The CDP event broadcast buffer (256 events) was too small for pages with many concurrent API requests, causing silent event drops. Modern SPAs routinely fire 100+ API calls during page load, generating 300+ CDP network events that would overflow the buffer between drain cycles. Changes: - Increase CDP broadcast buffer from 256 to 4096 (event channel) and 512 to 4096 (raw channel) - Reduce background drain interval from 500ms to 100ms - Handle Network.loadingFailed events in HAR recording - Enable Network.enable on cross-origin iframe sessions during HAR recording and request tracking - Allow Network events from iframe sessions through the session filter - Log a warning when buffer overflow occurs instead of silently dropping Fixes #1128 Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
This commit is contained in:
@@ -562,6 +562,12 @@ impl DaemonState {
|
||||
.client
|
||||
.send_command_no_params("Accessibility.enable", Some(iframe_sid.as_str()))
|
||||
.await;
|
||||
if self.har_recording || self.request_tracking {
|
||||
let _ = mgr
|
||||
.client
|
||||
.send_command_no_params("Network.enable", Some(iframe_sid.as_str()))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
for sid in &drained.detached_iframe_sessions {
|
||||
@@ -727,7 +733,17 @@ impl DaemonState {
|
||||
false
|
||||
};
|
||||
|
||||
if !session_matches {
|
||||
// Allow Network events from cross-origin iframe sessions
|
||||
// when HAR recording or request tracking is active.
|
||||
let iframe_network_event = !session_matches
|
||||
&& (self.har_recording || self.request_tracking)
|
||||
&& event.method.starts_with("Network.")
|
||||
&& event
|
||||
.session_id
|
||||
.as_ref()
|
||||
.is_some_and(|sid| self.iframe_sessions.values().any(|v| v == sid));
|
||||
|
||||
if !session_matches && !iframe_network_event {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -963,6 +979,33 @@ impl DaemonState {
|
||||
}
|
||||
}
|
||||
}
|
||||
"Network.loadingFailed" if self.har_recording => {
|
||||
let request_id = event
|
||||
.params
|
||||
.get("requestId")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let timestamp = event.params.get("timestamp").and_then(|v| v.as_f64());
|
||||
let error_text = event
|
||||
.params
|
||||
.get("errorText")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Failed");
|
||||
if let Some(entry) = self
|
||||
.har_entries
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|e| e.request_id == request_id)
|
||||
{
|
||||
if entry.status.is_none() {
|
||||
entry.status = Some(0);
|
||||
entry.status_text = error_text.to_string();
|
||||
}
|
||||
if let Some(ts) = timestamp {
|
||||
entry.loading_finished_timestamp = Some(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
"Page.screencastFrame" => {
|
||||
// Frame broadcasting and acks are handled in real-time by the
|
||||
// stream server's background CDP event loop. Here we just
|
||||
@@ -1008,7 +1051,10 @@ impl DaemonState {
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::TryRecvError::Empty) => break,
|
||||
Err(broadcast::error::TryRecvError::Lagged(_)) => continue,
|
||||
Err(broadcast::error::TryRecvError::Lagged(n)) => {
|
||||
eprintln!("[agent-browser] Warning: CDP event buffer overflowed, {} events dropped. Network requests may be missing from HAR output.", n);
|
||||
continue;
|
||||
}
|
||||
Err(broadcast::error::TryRecvError::Closed) => {
|
||||
self.event_rx = None;
|
||||
break;
|
||||
@@ -5854,6 +5900,14 @@ async fn handle_har_start(state: &mut DaemonState) -> Result<Value, String> {
|
||||
mgr.client
|
||||
.send_command_no_params("Network.enable", Some(&session_id))
|
||||
.await?;
|
||||
// Also enable Network on cross-origin iframe sessions so their
|
||||
// requests are captured in the HAR output.
|
||||
for iframe_sid in state.iframe_sessions.values() {
|
||||
let _ = mgr
|
||||
.client
|
||||
.send_command_no_params("Network.enable", Some(iframe_sid.as_str()))
|
||||
.await;
|
||||
}
|
||||
state.har_recording = true;
|
||||
state.har_entries.clear();
|
||||
Ok(json!({ "started": true }))
|
||||
|
||||
@@ -87,8 +87,8 @@ impl CdpClient {
|
||||
let ws_tx = Arc::new(Mutex::new(ws_tx));
|
||||
|
||||
let pending: PendingMap = Arc::new(Mutex::new(HashMap::new()));
|
||||
let (event_tx, _) = broadcast::channel(256);
|
||||
let (raw_tx, _) = broadcast::channel(512);
|
||||
let (event_tx, _) = broadcast::channel(4096);
|
||||
let (raw_tx, _) = broadcast::channel(4096);
|
||||
|
||||
let pending_clone = pending.clone();
|
||||
let event_tx_clone = event_tx.clone();
|
||||
|
||||
@@ -156,7 +156,7 @@ async fn run_socket_server(
|
||||
let (reset_tx, mut reset_rx) = mpsc::channel::<()>(64);
|
||||
let reset_tx = idle_timeout_ms.map(|_| Arc::new(reset_tx));
|
||||
|
||||
let mut drain_interval = tokio::time::interval(Duration::from_millis(500));
|
||||
let mut drain_interval = tokio::time::interval(Duration::from_millis(100));
|
||||
drain_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
|
||||
loop {
|
||||
|
||||
Reference in New Issue
Block a user