start_command/
attached_diagnostics.rs1use crate::cgroup_memory::{self, CgroupMemory};
3use crate::execution_control::{CommandRunner, SystemCommandRunner};
4use crate::isolation::isolation_log::{append_log_file, shell_quote};
5use std::path::PathBuf;
6use std::process::{Child, Command, Stdio};
7
8#[derive(Debug, Clone)]
9pub struct DiagnosticFacts {
10 pub memory: Option<CgroupMemory>,
11 pub evidence: serde_json::Value,
12}
13
14pub struct AttachedDiagnostics {
15 worker: Option<Child>,
16 file: PathBuf,
17}
18
19impl AttachedDiagnostics {
20 pub fn start(name: &str, options: &crate::isolation::IsolationOptions) -> Self {
21 let file = std::env::temp_dir().join(format!("start-attached-{}", uuid::Uuid::new_v4()));
22 let script = format!(
23 "{}; {}; read __start_stop; {}; {}; printf '%s' \"$__start_command_cgroup\" > {}; printf '%s' \"$__start_command_memory_unavailable\" > {}",
24 cgroup_memory::build_cgroup_sampler_start_snippet(name),
25 {
26 let cpu = crate::cpu_penalty_monitor::start_snippet_with_options(name, options);
27 if cpu.is_empty() {
28 ":".into()
29 } else {
30 cpu
31 }
32 },
33 cgroup_memory::build_cgroup_sampler_stop_snippet(),
34 crate::cpu_penalty_monitor::stop_snippet(),
35 shell_quote(&file.to_string_lossy()),
36 shell_quote(&format!("{}.reason", file.to_string_lossy()))
37 );
38 let worker = {
39 Command::new("sh")
40 .args(["-c", &script])
41 .stdin(Stdio::piped())
42 .stdout(Stdio::null())
43 .stderr(Stdio::null())
44 .spawn()
45 .ok()
46 };
47 Self { worker, file }
48 }
49
50 pub fn finish(mut self, name: &str, log: Option<&PathBuf>) -> DiagnosticFacts {
51 let started = self.worker.is_some();
52 if let Some(mut worker) = self.worker.take() {
53 drop(worker.stdin.take());
54 let _ = worker.wait();
55 }
56 let sample = std::fs::read_to_string(&self.file).unwrap_or_default();
57 let reason_file = PathBuf::from(format!("{}.reason", self.file.to_string_lossy()));
58 let unavailable = std::fs::read_to_string(&reason_file)
59 .ok()
60 .filter(|s| !s.is_empty())
61 .unwrap_or_else(|| {
62 if started {
63 "sampler unavailable or stopped before a reading".into()
64 } else {
65 "sampler could not start (sh unavailable)".into()
66 }
67 });
68 let _ = std::fs::remove_file(&self.file);
69 let _ = std::fs::remove_file(reason_file);
70 let facts = crate::docker_cleanup::read_docker_container_state(name);
71 let evidence = facts
72 .as_ref()
73 .map(|facts| {
74 crate::exit_evidence::collect(
75 name,
76 &sample,
77 facts.finished_at.as_deref().unwrap_or(""),
78 &facts.exit_code.unwrap_or(-1).to_string(),
79 if facts.oom_killed == Some(true) {
80 "true"
81 } else {
82 "false"
83 },
84 &SystemCommandRunner,
85 )
86 })
87 .unwrap_or_else(|| {
88 "Exit evidence: unavailable (container could not be inspected)".into()
89 });
90 let limit = SystemCommandRunner.run(
91 &crate::docker_cleanup::docker_command().to_string_lossy(),
92 &[
93 "inspect".into(),
94 "-f".into(),
95 "{{.HostConfig.Memory}}".into(),
96 name.into(),
97 ],
98 );
99 let memory_line =
100 cgroup_memory::format_cgroup_memory_log_line(&sample).unwrap_or_else(|| {
101 format!(
102 "Memory: unavailable ({}) memory.limit={} (HostConfig)",
103 unavailable,
104 if limit.success {
105 limit.stdout.trim()
106 } else {
107 "unknown"
108 }
109 )
110 });
111 if let Some(log) = log {
112 append_log_file(log, &format!("{}\n{}\n", memory_line, evidence));
113 }
114 let (main, daemon) = crate::exit_evidence::from_log(Some(&evidence));
115 DiagnosticFacts {
116 memory: cgroup_memory::parse_cgroup_memory_sample(&sample),
117 evidence: serde_json::json!({"mainOom": main, "daemonRestart": daemon}),
118 }
119 }
120}