Delay-load ETW profiling DLLs (#62647)
Since we're here, improve the UX by prompting for a path when you save rather than when recording begins. Fixes #62334 Release Notes: - N/A
This commit is contained in:
1 parent
8a54abc4b9
commit
0b1f4f4020
6 files changed
+169
-227
No files matched your search
Generated
-1
@@ -5987,7 +5987,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gpui",
|
||||
"log",
|
||||
"net",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -11,7 +11,6 @@ path = "etw_tracing.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
net.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
+163
-204
@@ -5,7 +5,6 @@ use gpui::{App, AppContext as _, DismissEvent, Global, actions};
|
||||
use std::fmt::Write as _;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use util::{ResultExt as _, defer};
|
||||
use windows::Win32::Foundation::{VARIANT_BOOL, VARIANT_FALSE};
|
||||
use windows::Win32::System::Com::{CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoInitializeEx};
|
||||
@@ -30,10 +29,17 @@ actions!(
|
||||
|
||||
struct EtwNotification;
|
||||
|
||||
enum EtwSessionState {
|
||||
Recording,
|
||||
ChoosingOutputPath,
|
||||
Stopping,
|
||||
}
|
||||
|
||||
struct EtwSessionHandle {
|
||||
writer: net::OwnedWriteHalf,
|
||||
_listener: net::UnixListener,
|
||||
socket_path: PathBuf,
|
||||
state: EtwSessionState,
|
||||
}
|
||||
|
||||
impl Drop for EtwSessionHandle {
|
||||
@@ -46,10 +52,6 @@ struct GlobalEtwSession(Option<EtwSessionHandle>);
|
||||
|
||||
impl Global for GlobalEtwSession {}
|
||||
|
||||
fn has_active_etw_session(cx: &App) -> bool {
|
||||
cx.global::<GlobalEtwSession>().0.is_some()
|
||||
}
|
||||
|
||||
fn show_etw_notification(cx: &mut App, message: impl Into<gpui::SharedString>) {
|
||||
let message = message.into();
|
||||
show_app_notification(NotificationId::unique::<EtwNotification>(), cx, move |cx| {
|
||||
@@ -57,59 +59,30 @@ fn show_etw_notification(cx: &mut App, message: impl Into<gpui::SharedString>) {
|
||||
});
|
||||
}
|
||||
|
||||
fn show_etw_notification_with_action(
|
||||
cx: &mut App,
|
||||
message: impl Into<gpui::SharedString>,
|
||||
button_label: impl Into<gpui::SharedString>,
|
||||
on_click: impl Fn(&mut gpui::Window, &mut gpui::Context<MessageNotification>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
let message = message.into();
|
||||
let button_label = button_label.into();
|
||||
let on_click = std::sync::Arc::new(on_click);
|
||||
show_app_notification(NotificationId::unique::<EtwNotification>(), cx, move |cx| {
|
||||
let message = message.clone();
|
||||
let button_label = button_label.clone();
|
||||
cx.new(|cx| {
|
||||
MessageNotification::new(message, cx)
|
||||
.primary_message(button_label)
|
||||
.primary_on_click_arc(on_click.clone())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn show_etw_status_notification(cx: &mut App, status: Result<StatusMessage>, output_path: PathBuf) {
|
||||
fn show_etw_status_notification(cx: &mut App, status: Result<StatusMessage>) {
|
||||
match status {
|
||||
Ok(StatusMessage::Stopped) => {
|
||||
let display_path = output_path.display().to_string();
|
||||
show_etw_notification_with_action(
|
||||
cx,
|
||||
format!("ETW trace saved to {display_path}"),
|
||||
"Show in File Manager",
|
||||
move |_window, cx| {
|
||||
cx.reveal_path(&output_path);
|
||||
cx.emit(DismissEvent);
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(StatusMessage::TimedOut) => {
|
||||
let display_path = output_path.display().to_string();
|
||||
show_etw_notification_with_action(
|
||||
cx,
|
||||
format!("ETW recording timed out. Trace saved to {display_path}"),
|
||||
"Show in File Manager",
|
||||
move |_window, cx| {
|
||||
cx.reveal_path(&output_path);
|
||||
cx.emit(DismissEvent);
|
||||
},
|
||||
);
|
||||
Ok(StatusMessage::Stopped { output_path }) => {
|
||||
let message = format!("ETW trace saved to {}", output_path.display());
|
||||
show_app_notification(NotificationId::unique::<EtwNotification>(), cx, move |cx| {
|
||||
let message = message.clone();
|
||||
let output_path = output_path.clone();
|
||||
cx.new(|cx| {
|
||||
MessageNotification::new(message, cx)
|
||||
.primary_message("Show in File Manager")
|
||||
.primary_on_click(move |_window, cx| {
|
||||
cx.reveal_path(&output_path);
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
Ok(StatusMessage::Cancelled) => {
|
||||
show_etw_notification(cx, "ETW recording cancelled");
|
||||
}
|
||||
Ok(_) => {
|
||||
Ok(StatusMessage::Error { message }) => {
|
||||
show_etw_notification(cx, format!("ETW recording failed: {message}"));
|
||||
}
|
||||
Ok(StatusMessage::Started) => {
|
||||
show_etw_notification(cx, "ETW recording ended unexpectedly");
|
||||
}
|
||||
Err(error) => {
|
||||
@@ -130,67 +103,113 @@ pub fn init(cx: &mut App) {
|
||||
});
|
||||
|
||||
cx.on_action(|_: &SaveEtwTrace, cx: &mut App| {
|
||||
let session = cx.global_mut::<GlobalEtwSession>().0.as_mut();
|
||||
let Some(session) = session else {
|
||||
show_etw_notification(cx, "No active ETW recording to stop");
|
||||
return;
|
||||
};
|
||||
match send_json(&mut session.writer, &Command::Save) {
|
||||
Ok(()) => {
|
||||
show_etw_notification(cx, "Stopping ETW recording...");
|
||||
}
|
||||
Err(error) => {
|
||||
show_etw_notification(cx, format!("Failed to stop ETW recording: {error:#}"));
|
||||
}
|
||||
}
|
||||
prompt_for_etw_output_path(cx);
|
||||
});
|
||||
|
||||
cx.on_action(|_: &CancelEtwTrace, cx: &mut App| {
|
||||
let session = cx.global_mut::<GlobalEtwSession>().0.as_mut();
|
||||
let Some(session) = session else {
|
||||
show_etw_notification(cx, "No active ETW recording to cancel");
|
||||
return;
|
||||
};
|
||||
match send_json(&mut session.writer, &Command::Cancel) {
|
||||
Ok(()) => {
|
||||
show_etw_notification(cx, "Cancelling ETW recording...");
|
||||
}
|
||||
Err(error) => {
|
||||
show_etw_notification(cx, format!("Failed to cancel ETW recording: {error:#}"));
|
||||
}
|
||||
}
|
||||
cancel_etw_recording(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn prompt_for_etw_output_path(cx: &mut App) {
|
||||
let Some(session) = cx.global_mut::<GlobalEtwSession>().0.as_mut() else {
|
||||
show_etw_notification(cx, "No active ETW recording to stop");
|
||||
return;
|
||||
};
|
||||
match &session.state {
|
||||
EtwSessionState::Recording => {
|
||||
session.state = EtwSessionState::ChoosingOutputPath;
|
||||
}
|
||||
EtwSessionState::ChoosingOutputPath => {
|
||||
show_etw_notification(cx, "ETW recording is already waiting for a save location");
|
||||
return;
|
||||
}
|
||||
EtwSessionState::Stopping => {
|
||||
show_etw_notification(cx, "ETW recording is already stopping");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let save_dialog = cx.prompt_for_new_path(&PathBuf::default(), Some("zed-trace.etl"));
|
||||
cx.spawn(async move |cx| {
|
||||
let picked = save_dialog.await.unwrap_or(Ok(None));
|
||||
cx.update(|cx| match picked {
|
||||
Ok(Some(output_path)) => save_etw_recording(output_path, cx),
|
||||
Ok(None) => resume_etw_recording(cx),
|
||||
Err(error) => {
|
||||
resume_etw_recording(cx);
|
||||
show_etw_notification(cx, format!("Failed to pick save location: {error:#}"));
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn save_etw_recording(output_path: PathBuf, cx: &mut App) {
|
||||
let Some(session) = cx.global_mut::<GlobalEtwSession>().0.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if !matches!(&session.state, EtwSessionState::ChoosingOutputPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
let command = Command::Save {
|
||||
output_path: output_path.clone(),
|
||||
};
|
||||
match send_json(&mut session.writer, &command) {
|
||||
Ok(()) => {
|
||||
session.state = EtwSessionState::Stopping;
|
||||
show_etw_notification(cx, "Stopping ETW recording...");
|
||||
}
|
||||
Err(error) => {
|
||||
session.state = EtwSessionState::Recording;
|
||||
show_etw_notification(cx, format!("Failed to stop ETW recording: {error:#}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_etw_recording(cx: &mut App) {
|
||||
let Some(session) = cx.global_mut::<GlobalEtwSession>().0.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if matches!(&session.state, EtwSessionState::ChoosingOutputPath) {
|
||||
session.state = EtwSessionState::Recording;
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_etw_recording(cx: &mut App) {
|
||||
let Some(session) = cx.global_mut::<GlobalEtwSession>().0.as_mut() else {
|
||||
show_etw_notification(cx, "No active ETW recording to cancel");
|
||||
return;
|
||||
};
|
||||
if matches!(&session.state, EtwSessionState::Stopping) {
|
||||
show_etw_notification(cx, "ETW recording is already stopping");
|
||||
return;
|
||||
}
|
||||
|
||||
match send_json(&mut session.writer, &Command::Cancel) {
|
||||
Ok(()) => {
|
||||
session.state = EtwSessionState::Stopping;
|
||||
show_etw_notification(cx, "Cancelling ETW recording...");
|
||||
}
|
||||
Err(error) => {
|
||||
session.state = EtwSessionState::Recording;
|
||||
show_etw_notification(cx, format!("Failed to cancel ETW recording: {error:#}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_etw_recording(cx: &mut App, heap_pid: Option<u32>) {
|
||||
if has_active_etw_session(cx) {
|
||||
if cx.global::<GlobalEtwSession>().0.is_some() {
|
||||
show_etw_notification(cx, "ETW recording is already in progress");
|
||||
return;
|
||||
}
|
||||
let save_dialog = cx.prompt_for_new_path(&PathBuf::default(), Some("zed-trace.etl"));
|
||||
cx.spawn(async move |cx| {
|
||||
let output_path = match save_dialog.await {
|
||||
Ok(Ok(Some(path))) => path,
|
||||
Ok(Ok(None)) => return,
|
||||
Ok(Err(error)) => {
|
||||
cx.update(|cx| {
|
||||
show_etw_notification(cx, format!("Failed to pick save location: {error:#}"));
|
||||
});
|
||||
return;
|
||||
}
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let result = cx
|
||||
.background_spawn(async move { launch_etw_recording(heap_pid, &output_path) })
|
||||
.background_spawn(async move { launch_etw_recording(heap_pid) })
|
||||
.await;
|
||||
|
||||
let EtwSession {
|
||||
output_path,
|
||||
stream,
|
||||
listener,
|
||||
socket_path,
|
||||
} = match result {
|
||||
let EtwSession { mut reader, handle } = match result {
|
||||
Ok(session) => session,
|
||||
Err(error) => {
|
||||
cx.update(|cx| {
|
||||
@@ -200,36 +219,24 @@ fn start_etw_recording(cx: &mut App, heap_pid: Option<u32>) {
|
||||
}
|
||||
};
|
||||
|
||||
let (read_half, write_half) = stream.into_inner().into_split();
|
||||
|
||||
cx.spawn(async |cx| {
|
||||
let status = cx
|
||||
.background_spawn(async move {
|
||||
recv_json(&mut BufReader::new(read_half))
|
||||
.context("Receive status from subprocess")
|
||||
})
|
||||
.await;
|
||||
cx.update(|cx| {
|
||||
cx.global_mut::<GlobalEtwSession>().0 = None;
|
||||
show_etw_status_notification(cx, status, output_path);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.global_mut::<GlobalEtwSession>().0 = Some(EtwSessionHandle {
|
||||
writer: write_half,
|
||||
_listener: listener,
|
||||
socket_path,
|
||||
});
|
||||
cx.global_mut::<GlobalEtwSession>().0 = Some(handle);
|
||||
show_etw_notification(cx, "ETW recording started");
|
||||
});
|
||||
|
||||
let status = cx
|
||||
.background_spawn(async move {
|
||||
recv_json(&mut reader).context("Receive status from subprocess")
|
||||
})
|
||||
.await;
|
||||
cx.update(|cx| {
|
||||
cx.global_mut::<GlobalEtwSession>().0 = None;
|
||||
show_etw_status_notification(cx, status);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
const RECORDING_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
const INSTANCE_NAME: &str = "Zed";
|
||||
|
||||
const BUILTIN_PROFILES: &[&str] = &[
|
||||
@@ -438,21 +445,16 @@ fn build_profile_collection(heap_pid: Option<u32>) -> Result<IProfileCollection>
|
||||
Ok(collection)
|
||||
}
|
||||
|
||||
pub fn record_etw_trace(
|
||||
heap_pid: Option<u32>,
|
||||
output_path: &Path,
|
||||
socket_path: &str,
|
||||
) -> Result<()> {
|
||||
pub fn record_etw_trace(heap_pid: Option<u32>, socket_path: &Path) -> Result<()> {
|
||||
unsafe {
|
||||
CoInitializeEx(None, COINIT_MULTITHREADED)
|
||||
.ok()
|
||||
.context("COM initialization failed")?;
|
||||
}
|
||||
|
||||
let socket_path = Path::new(socket_path);
|
||||
let mut stream = net::UnixStream::connect(socket_path).context("Connect to parent socket")?;
|
||||
|
||||
match record_etw_trace_inner(heap_pid, output_path, &mut stream) {
|
||||
match record_etw_trace_inner(heap_pid, &mut stream) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
send_json(
|
||||
@@ -467,11 +469,7 @@ pub fn record_etw_trace(
|
||||
}
|
||||
}
|
||||
|
||||
fn record_etw_trace_inner(
|
||||
heap_pid: Option<u32>,
|
||||
output_path: &Path,
|
||||
stream: &mut net::UnixStream,
|
||||
) -> Result<()> {
|
||||
fn record_etw_trace_inner(heap_pid: Option<u32>, stream: &mut net::UnixStream) -> Result<()> {
|
||||
let collection = build_profile_collection(heap_pid)?;
|
||||
let control_manager: IControlManager = create_wpr(&CControlManager)?;
|
||||
|
||||
@@ -497,7 +495,8 @@ fn record_etw_trace_inner(
|
||||
|
||||
send_json(stream, &StatusMessage::Started)?;
|
||||
|
||||
let (command, timed_out) = receive_command(stream)?;
|
||||
let command: Command =
|
||||
recv_json(&mut BufReader::new(&mut *stream)).context("Receive command from Zed")?;
|
||||
|
||||
match command {
|
||||
Command::Cancel => {
|
||||
@@ -511,7 +510,7 @@ fn record_etw_trace_inner(
|
||||
|
||||
send_json(stream, &StatusMessage::Cancelled).log_err();
|
||||
}
|
||||
Command::Save => {
|
||||
Command::Save { output_path } => {
|
||||
unsafe {
|
||||
control_manager
|
||||
.Save(
|
||||
@@ -524,76 +523,37 @@ fn record_etw_trace_inner(
|
||||
}
|
||||
cancel_guard.abort();
|
||||
|
||||
if timed_out {
|
||||
send_json(stream, &StatusMessage::TimedOut).log_err();
|
||||
} else {
|
||||
send_json(stream, &StatusMessage::Stopped).log_err();
|
||||
}
|
||||
send_json(stream, &StatusMessage::Stopped { output_path }).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_command(stream: &mut net::UnixStream) -> Result<(Command, bool)> {
|
||||
use std::os::windows::io::{AsRawSocket, AsSocket};
|
||||
use windows::Win32::Networking::WinSock::{SO_RCVTIMEO, SOL_SOCKET, setsockopt};
|
||||
|
||||
// Set a receive timeout so read_line returns an error after `timeout`.
|
||||
let millis = RECORDING_TIMEOUT.as_millis() as u32;
|
||||
let socket = stream.as_socket();
|
||||
let ret = unsafe {
|
||||
setsockopt(
|
||||
windows::Win32::Networking::WinSock::SOCKET(socket.as_raw_socket() as _),
|
||||
SOL_SOCKET,
|
||||
SO_RCVTIMEO,
|
||||
Some(&millis.to_ne_bytes()),
|
||||
)
|
||||
};
|
||||
if ret != 0 {
|
||||
bail!("Failed to set socket receive timeout: setsockopt returned {ret}");
|
||||
}
|
||||
|
||||
let mut reader = BufReader::new(&mut *stream);
|
||||
match recv_json::<Command>(&mut reader) {
|
||||
Ok(command) => Ok((command, false)),
|
||||
Err(error) => {
|
||||
log::warn!("Failed to receive ETW command, treating as timed-out Save: {error:#}");
|
||||
Ok((Command::Save, true))
|
||||
}
|
||||
}
|
||||
struct EtwSession {
|
||||
reader: BufReader<net::OwnedReadHalf>,
|
||||
handle: EtwSessionHandle,
|
||||
}
|
||||
|
||||
pub struct EtwSession {
|
||||
output_path: PathBuf,
|
||||
stream: BufReader<net::UnixStream>,
|
||||
listener: net::UnixListener,
|
||||
socket_path: PathBuf,
|
||||
}
|
||||
|
||||
pub fn launch_etw_recording(heap_pid: Option<u32>, output_path: &Path) -> Result<EtwSession> {
|
||||
fn launch_etw_recording(heap_pid: Option<u32>) -> Result<EtwSession> {
|
||||
let sock_path = std::env::temp_dir().join(format!("zed-etw-{}.sock", std::process::id()));
|
||||
|
||||
_ = std::fs::remove_file(&sock_path);
|
||||
let listener = net::UnixListener::bind(&sock_path).context("Bind Unix socket for ETW IPC")?;
|
||||
|
||||
let exe_path = std::env::current_exe().context("Failed to get current exe path")?;
|
||||
let pid_arg = heap_pid.map_or(-1i64, |pid| pid as i64);
|
||||
let heap_arg = heap_pid.map_or(String::new(), |pid| format!(" --etw-zed-pid {pid}"));
|
||||
let args = format!(
|
||||
"--record-etw-trace --etw-zed-pid {} --etw-output \"{}\" --etw-socket \"{}\"",
|
||||
pid_arg,
|
||||
output_path.display(),
|
||||
"--record-etw-trace{heap_arg} --etw-socket \"{}\"",
|
||||
sock_path.display(),
|
||||
);
|
||||
|
||||
use windows::Win32::UI::Shell::ShellExecuteW;
|
||||
use windows_core::PCWSTR;
|
||||
use windows_core::{HSTRING, PCWSTR};
|
||||
|
||||
let operation: Vec<u16> = "runas\0".encode_utf16().collect();
|
||||
let file: Vec<u16> = format!("{}\0", exe_path.to_string_lossy())
|
||||
.encode_utf16()
|
||||
.collect();
|
||||
let parameters: Vec<u16> = format!("{args}\0").encode_utf16().collect();
|
||||
let operation = HSTRING::from("runas");
|
||||
let file = HSTRING::from(exe_path.to_string_lossy().as_ref());
|
||||
let parameters = HSTRING::from(args);
|
||||
|
||||
let result = unsafe {
|
||||
ShellExecuteW(
|
||||
@@ -612,18 +572,10 @@ pub fn launch_etw_recording(heap_pid: Option<u32>, output_path: &Path) -> Result
|
||||
}
|
||||
|
||||
let (stream, _) = listener.accept().context("Accept subprocess connection")?;
|
||||
let (read_half, write_half) = stream.into_split();
|
||||
let mut reader = BufReader::new(read_half);
|
||||
|
||||
let mut session = EtwSession {
|
||||
output_path: output_path.to_path_buf(),
|
||||
stream: BufReader::new(stream),
|
||||
listener,
|
||||
socket_path: sock_path,
|
||||
};
|
||||
|
||||
let status: StatusMessage =
|
||||
recv_json(&mut session.stream).context("Wait for Started status")?;
|
||||
|
||||
match status {
|
||||
match recv_json(&mut reader).context("Wait for Started status")? {
|
||||
StatusMessage::Started => {}
|
||||
StatusMessage::Error { message } => {
|
||||
bail!("Subprocess reported error during start: {message}");
|
||||
@@ -633,23 +585,30 @@ pub fn launch_etw_recording(heap_pid: Option<u32>, output_path: &Path) -> Result
|
||||
}
|
||||
}
|
||||
|
||||
Ok(session)
|
||||
Ok(EtwSession {
|
||||
reader,
|
||||
handle: EtwSessionHandle {
|
||||
writer: write_half,
|
||||
_listener: listener,
|
||||
socket_path: sock_path,
|
||||
state: EtwSessionState::Recording,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum StatusMessage {
|
||||
enum StatusMessage {
|
||||
Started,
|
||||
Stopped,
|
||||
TimedOut,
|
||||
Stopped { output_path: PathBuf },
|
||||
Cancelled,
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Command {
|
||||
Save,
|
||||
enum Command {
|
||||
Save { output_path: PathBuf },
|
||||
Cancel,
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ fn main() {
|
||||
if cfg!(target_env = "msvc") {
|
||||
// todo(windows): This is to avoid stack overflow. Remove it when solved.
|
||||
println!("cargo:rustc-link-arg=/stack:{}", 8 * 1024 * 1024);
|
||||
println!("cargo:rustc-link-arg=/DELAYLOAD:windowsperformancerecordercontrol");
|
||||
println!("cargo:rustc-link-lib=delayimp");
|
||||
}
|
||||
|
||||
if cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64") {
|
||||
|
||||
+3
-18
@@ -226,22 +226,12 @@ fn main() {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if args.record_etw_trace {
|
||||
let zed_pid = args
|
||||
.etw_zed_pid
|
||||
.and_then(|pid| if pid >= 0 { Some(pid as u32) } else { None });
|
||||
let Some(output_path) = args.etw_output else {
|
||||
eprintln!("--etw-output is required for --record-etw-trace");
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
let Some(etw_socket) = args.etw_socket else {
|
||||
eprintln!("--etw-socket is required for --record-etw-trace");
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
if let Err(error) =
|
||||
etw_tracing::record_etw_trace(zed_pid, &output_path, etw_socket.as_str())
|
||||
{
|
||||
if let Err(error) = etw_tracing::record_etw_trace(args.etw_zed_pid, &etw_socket) {
|
||||
eprintln!("ETW trace recording failed: {error:#}");
|
||||
process::exit(1);
|
||||
}
|
||||
@@ -1788,18 +1778,13 @@ struct Args {
|
||||
|
||||
/// The PID of the Zed process to trace for heap analysis.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[arg(long, hide = true, allow_hyphen_values = true)]
|
||||
etw_zed_pid: Option<i64>,
|
||||
|
||||
/// Output path for the ETW trace file.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[arg(long, hide = true)]
|
||||
etw_output: Option<PathBuf>,
|
||||
etw_zed_pid: Option<u32>,
|
||||
|
||||
/// Unix socket path for IPC with the parent Zed process.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[arg(long, hide = true)]
|
||||
etw_socket: Option<String>,
|
||||
etw_socket: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -99,7 +99,7 @@ Open the command palette and run one of the following:
|
||||
- `zed: record etw trace`: records CPU, GPU, memory, and I/O activity
|
||||
- `zed: record etw trace with heap tracing`: includes heap allocation data for the Zed process
|
||||
|
||||
Zed will prompt you to choose a save location for the `.etl` file, then request administrator permission. Once granted, recording will begin.
|
||||
Zed will request administrator permission. Once granted, recording will begin.
|
||||
|
||||
### Saving or canceling
|
||||
|
||||
@@ -108,8 +108,6 @@ While a trace is recording, open the command palette and run one of the followin
|
||||
- `zed: save etw trace`: stops recording and saves the trace to disk
|
||||
- `zed: cancel etw trace`: stops recording without saving
|
||||
|
||||
Recordings automatically save after 60 seconds if not stopped manually.
|
||||
|
||||
## Contributor links
|
||||
|
||||
- [CONTRIBUTING.md](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md)
|
||||
|
||||
Reference in new issue
Block a user