staccs's picture
ds4-winhip: Windows/HIP 7.2 build tree, launcher, supervisor, bench + notes
fda8632 verified
Raw History Blame Contribute Delete
4.85 kB
use super::BashJob;
use std::fs::File;
use std::io::Read;
const HEAD_BYTES: usize = 8 * 1024;
const HEAD_LINES: i32 = 100;
const TAIL_BYTES: usize = 32 * 1024;
const PROGRESS_TAIL_LINES: i32 = 4;
const FINAL_TAIL_LINES: i32 = 20;
pub(super) fn observation(job: &mut BashJob) -> Vec<u8> {
job.poll();
let first = !job.observed_once;
let (bytes, display_lines, last_nl) = file_stats(&job.path);
let elapsed = job.start.elapsed().as_secs_f64();
let mut out = if job.running {
format!(
"bash job={} pid={} status=running elapsed_sec={elapsed:.1} timeout_sec={:.0}\n",
job.id, job.pid, job.timeout_sec
)
} else {
format!(
"bash job={} pid={} status=done elapsed_sec={elapsed:.1} timed_out={}\nexit_status={}\n",
job.id,
job.pid,
i32::from(job.timed_out),
job.exit_status
)
}
.into_bytes();
if bytes == 0 {
out.extend_from_slice(b"<output>\n</output>\n");
} else if first {
let (head, shown, byte_limited) = read_head(&job.path);
let truncated = byte_limited || display_lines > shown;
if !job.running && !truncated {
out.extend_from_slice(b"<output>\n");
out.extend_from_slice(&head);
if !head.is_empty() && !head.ends_with(b"\n") {
out.push(b'\n');
}
out.extend_from_slice(b"</output>\n");
} else {
append_path_head(&mut out, &job.path, bytes, display_lines, &head);
}
} else {
let tail_lines = if job.running {
PROGRESS_TAIL_LINES
} else {
FINAL_TAIL_LINES
};
let tail = read_tail(&job.path, tail_lines);
let path = job.path.display();
out.extend_from_slice(
format!("output_path={path} ({bytes} bytes, {display_lines} lines)\n").as_bytes(),
);
out.extend_from_slice(format!("<tail -{tail_lines} {path}>\n").as_bytes());
out.extend_from_slice(&tail);
if !tail.is_empty() && !tail.ends_with(b"\n") {
out.push(b'\n');
}
out.extend_from_slice(b"</tail>\n");
}
if job.running {
out.extend_from_slice(
format!(
"\nUse bash_status job={} to get info before refresh time; use bash_stop job={} to stop execution\n",
job.id, job.id
)
.as_bytes(),
);
}
job.observed_once = true;
let _ = (last_nl,);
out
}
fn file_stats(path: &std::path::Path) -> (usize, i32, bool) {
let Ok(data) = std::fs::read(path) else {
return (0, 0, true);
};
if data.is_empty() {
return (0, 0, true);
}
let newlines = data.iter().filter(|byte| **byte == b'\n').count() as i32;
let last_nl = data.last() == Some(&b'\n');
(data.len(), newlines + i32::from(!last_nl), last_nl)
}
fn read_head(path: &std::path::Path) -> (Vec<u8>, i32, bool) {
let Ok(mut file) = File::open(path) else {
return (b"<failed to reopen output file>\n".to_vec(), 0, false);
};
let mut out = Vec::new();
let mut buf = [0u8; 1];
let mut lines = 0i32;
while lines < HEAD_LINES && out.len() < HEAD_BYTES {
match file.read(&mut buf) {
Ok(0) => break,
Ok(_) => {
out.push(buf[0]);
if buf[0] == b'\n' {
lines += 1;
}
}
Err(_) => break,
}
}
let byte_limited = out.len() >= HEAD_BYTES;
let shown = lines + i32::from(!out.is_empty() && !out.ends_with(b"\n"));
(out, shown, byte_limited)
}
fn read_tail(path: &std::path::Path, max_lines: i32) -> Vec<u8> {
let Ok(data) = std::fs::read(path) else {
return b"<failed to reopen output file>\n".to_vec();
};
if data.is_empty() {
return Vec::new();
}
let start = data.len().saturating_sub(TAIL_BYTES);
let window = &data[start..];
let mut newlines = 0;
let mut cut = 0;
for (index, byte) in window.iter().enumerate().rev() {
if *byte == b'\n' {
newlines += 1;
if newlines > max_lines {
cut = index + 1;
break;
}
}
}
window[cut..].to_vec()
}
fn append_path_head(
out: &mut Vec<u8>,
path: &std::path::Path,
bytes: usize,
lines: i32,
head: &[u8],
) {
let shown = path.display();
out.extend_from_slice(
format!("output_path={shown} ({bytes} bytes, {lines} lines)\n").as_bytes(),
);
out.extend_from_slice(format!("<head -{HEAD_LINES} {shown}>\n").as_bytes());
out.extend_from_slice(head);
if !head.is_empty() && !head.ends_with(b"\n") {
out.push(b'\n');
}
out.extend_from_slice(b"</head>\n");
}