Skip to main content

start_command/
attached_diagnostics.rs

1//! Attached runs use the same sampler and penalty monitor as detached watchers.
2use 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, _) = crate::exit_evidence::from_log(Some(&evidence));
115        DiagnosticFacts {
116            memory: cgroup_memory::parse_cgroup_memory_sample(&sample),
117            evidence: crate::exit_evidence::evidence_json(Some(&evidence), main),
118        }
119    }
120}