mz_debug/
docker_dumper.rs
1use std::fs::{File, create_dir_all};
19use std::io::Write;
20use std::path::PathBuf;
21use std::time::Duration;
22
23use mz_ore::retry::{self, RetryResult};
24use tracing::{error, info};
25
26use crate::utils::format_base_path;
27use crate::{ContainerDumper, Context};
28
29static DOCKER_RESOURCE_DUMP_TIMEOUT: Duration = Duration::from_secs(30);
30
31pub struct DockerDumper {
32 container_id: String,
33 directory_path: PathBuf,
34}
35
36impl DockerDumper {
37 pub fn new(context: &Context, container_id: String) -> Self {
38 Self {
39 directory_path: format_base_path(context.start_time)
40 .join("docker")
41 .join(&container_id),
42 container_id,
43 }
44 }
45
46 async fn execute_docker_command(
48 &self,
49 args: &[String],
50 ) -> Result<(Vec<u8>, Vec<u8>), anyhow::Error> {
51 retry::Retry::default()
52 .max_duration(DOCKER_RESOURCE_DUMP_TIMEOUT)
53 .retry_async(|_| {
54 let args = args.to_vec();
55 async move {
56 let output = tokio::process::Command::new("docker")
57 .args(&args)
58 .output()
59 .await;
60
61 match output {
62 Ok(output) if output.status.success() => {
63 RetryResult::Ok((output.stdout, output.stderr))
64 }
65 Ok(output) => {
66 let err_msg = format!(
67 "Docker command failed: {:#}. Retrying...",
68 String::from_utf8_lossy(&output.stderr)
69 );
70 error!("{}", err_msg);
71 RetryResult::RetryableErr(anyhow::anyhow!(err_msg))
72 }
73 Err(err) => {
74 let err_msg = format!("Failed to execute Docker command: {:#}", err);
75 error!("{}", err_msg);
76 RetryResult::RetryableErr(anyhow::anyhow!(err_msg))
77 }
78 }
79 }
80 })
81 .await
82 }
83
84 async fn dump_logs(&self) -> Result<(), anyhow::Error> {
85 let (stdout, stderr) = self
86 .execute_docker_command(&["logs".to_string(), self.container_id.to_string()])
87 .await?;
88
89 write_output(stdout, &self.directory_path, "logs-stdout.txt")?;
90 write_output(stderr, &self.directory_path, "logs-stderr.txt")?;
91
92 Ok(())
93 }
94
95 async fn dump_inspect(&self) -> Result<(), anyhow::Error> {
96 let (stdout, _) = self
97 .execute_docker_command(&["inspect".to_string(), self.container_id.to_string()])
98 .await?;
99
100 write_output(stdout, &self.directory_path, "inspect.txt")?;
101
102 Ok(())
103 }
104
105 async fn dump_stats(&self) -> Result<(), anyhow::Error> {
106 let (stdout, _) = self
107 .execute_docker_command(&[
108 "stats".to_string(),
109 "--no-stream".to_string(),
110 self.container_id.to_string(),
111 ])
112 .await?;
113
114 write_output(stdout, &self.directory_path, "stats.txt")?;
115
116 Ok(())
117 }
118
119 async fn dump_top(&self) -> Result<(), anyhow::Error> {
120 let (stdout, _) = self
121 .execute_docker_command(&["top".to_string(), self.container_id.to_string()])
122 .await?;
123
124 write_output(stdout, &self.directory_path, "top.txt")?;
125
126 Ok(())
127 }
128}
129
130impl ContainerDumper for DockerDumper {
131 async fn dump_container_resources(&self) {
132 let _ = self.dump_logs().await;
133 let _ = self.dump_inspect().await;
134 let _ = self.dump_stats().await;
135 let _ = self.dump_top().await;
136 }
137}
138
139fn write_output(
141 output: Vec<u8>,
142 directory_path: &PathBuf,
143 file_name: &str,
144) -> Result<(), anyhow::Error> {
145 create_dir_all(&directory_path)?;
146 let file_path = directory_path.join(file_name);
147 let mut file = File::create(&file_path)?;
148 file.write_all(&output)?;
149 info!("Exported {}", file_path.display());
150 Ok(())
151}