FDS/OS 1.0
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "fds-test-helpers"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
description = "Disposable VM workloads; never installed in FDS images"
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
libc = "0.2"
|
||||
|
||||
[[bin]]
|
||||
name = "m6-client"
|
||||
path = "cartridge-client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "m7-writer"
|
||||
path = "data-writer.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "m11-stress"
|
||||
path = "usb-stress.rs"
|
||||
@@ -0,0 +1,52 @@
|
||||
// Test-only static ARM client. Never installed in a production image.
|
||||
use clap::Parser;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
os::unix::net::UnixStream,
|
||||
process::{Command, ExitCode},
|
||||
time::Duration,
|
||||
};
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Exercise the cartridge socket with slow, oversize, or a JSON request")]
|
||||
struct Cli {
|
||||
mode: String,
|
||||
}
|
||||
fn main() -> ExitCode {
|
||||
let mode = Cli::parse().mode;
|
||||
let socket = "/run/fds/control.sock";
|
||||
let result = (|| -> std::io::Result<()> {
|
||||
let mut stream = UnixStream::connect(socket)?;
|
||||
stream.set_read_timeout(Some(Duration::from_secs(8)))?;
|
||||
if mode == "slow" {
|
||||
stream.write_all(b"{")?;
|
||||
assert!(Command::new("fds").arg("bays").status()?.success());
|
||||
println!("FDS_SLOW_CLIENT_OK");
|
||||
} else if mode == "oversize" {
|
||||
let _ = stream.write_all(&vec![b'x'; 65537]);
|
||||
let mut bytes = Vec::new();
|
||||
let read = stream.read_to_end(&mut bytes);
|
||||
assert!(bytes.is_empty());
|
||||
assert!(
|
||||
read.is_ok() || read.unwrap_err().kind() == std::io::ErrorKind::ConnectionReset
|
||||
);
|
||||
println!("FDS_OVERSIZE_OK");
|
||||
} else {
|
||||
stream.write_all(mode.as_bytes())?;
|
||||
stream.write_all(b"\n")?;
|
||||
let mut text = String::new();
|
||||
stream.take(65537).read_to_string(&mut text)?;
|
||||
if text.is_empty() {
|
||||
return Err(std::io::Error::other("peer rejected"));
|
||||
}
|
||||
println!("{text}");
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
match result {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("test-client: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Test-only workload: sustained dirty writes plus a child that ignores TERM.
|
||||
use clap::{Parser, ValueEnum};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{Seek, SeekFrom, Write},
|
||||
process::Command,
|
||||
};
|
||||
unsafe extern "C" {
|
||||
fn signal(number: i32, handler: usize) -> usize;
|
||||
}
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Write disposable DATA stress output")]
|
||||
struct Cli {
|
||||
role: Option<Role>,
|
||||
}
|
||||
#[derive(Clone, ValueEnum)]
|
||||
enum Role {
|
||||
Child,
|
||||
}
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
unsafe {
|
||||
signal(15, 1);
|
||||
}
|
||||
if matches!(cli.role, Some(Role::Child)) {
|
||||
loop {
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
fs::write(
|
||||
"/data/process-status",
|
||||
fs::read("/proc/self/status").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let child = Command::new(std::env::current_exe().unwrap())
|
||||
.arg("child")
|
||||
.spawn()
|
||||
.unwrap();
|
||||
fs::write("/data/child-pid", child.id().to_string()).unwrap();
|
||||
let mut file = File::create("/data/stress.bin").unwrap();
|
||||
let buffer: Vec<u8> = (0..1024 * 1024).map(|i| (i % 256) as u8).collect();
|
||||
let mut blocks = 0_u64;
|
||||
loop {
|
||||
if blocks % 64 == 0 {
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
}
|
||||
if let Err(error) = file.write_all(&buffer) {
|
||||
eprintln!("write fault: {error}");
|
||||
return;
|
||||
}
|
||||
blocks += 1;
|
||||
fs::write("/data/progress.next", blocks.to_string()).unwrap();
|
||||
fs::rename("/data/progress.next", "/data/progress").unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Test-only, unprivileged concurrent direct reads and durable DATA writes.
|
||||
use clap::Parser;
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{Read, Seek, SeekFrom, Write},
|
||||
os::unix::fs::OpenOptionsExt,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Barrier},
|
||||
time::Instant,
|
||||
};
|
||||
#[repr(align(16384))]
|
||||
struct Block([u8; 65536]);
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Read eleven cartridge fixtures while writing disposable DATA")]
|
||||
struct Cli {
|
||||
#[arg(required = true, num_args = 11)]
|
||||
paths: Vec<PathBuf>,
|
||||
}
|
||||
fn main() {
|
||||
let paths = Cli::parse().paths;
|
||||
let barrier = Arc::new(Barrier::new(12));
|
||||
let origin = Instant::now();
|
||||
let mut jobs = Vec::new();
|
||||
for (index, path) in paths.into_iter().enumerate() {
|
||||
let barrier = barrier.clone();
|
||||
jobs.push(std::thread::spawn(move || {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.custom_flags(libc::O_DIRECT)
|
||||
.open(path)
|
||||
.unwrap(); // O_DIRECT
|
||||
let mut block = Box::new(Block([0; 65536]));
|
||||
barrier.wait();
|
||||
let start = origin.elapsed().as_nanos();
|
||||
for _ in 0..4 {
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
for _ in 0..128 {
|
||||
file.read_exact(&mut block.0).unwrap();
|
||||
assert!(block.0.iter().all(|b| *b == (index + 2) as u8));
|
||||
}
|
||||
}
|
||||
(
|
||||
index + 2,
|
||||
start,
|
||||
origin.elapsed().as_nanos(),
|
||||
32 * 1024 * 1024,
|
||||
)
|
||||
}));
|
||||
}
|
||||
let writer = std::thread::spawn(move || {
|
||||
let mut file = File::create("/data/stress.bin").unwrap();
|
||||
let mut block = [0_u8; 65536];
|
||||
for (i, byte) in block.iter_mut().enumerate() {
|
||||
*byte = (i % 256) as u8;
|
||||
}
|
||||
barrier.wait();
|
||||
let start = origin.elapsed().as_nanos();
|
||||
for _ in 0..1024 {
|
||||
file.write_all(&block).unwrap();
|
||||
}
|
||||
file.sync_all().unwrap();
|
||||
(1, start, origin.elapsed().as_nanos(), 64 * 1024 * 1024)
|
||||
});
|
||||
let mut results: Vec<_> = jobs.into_iter().map(|j| j.join().unwrap()).collect();
|
||||
results.push(writer.join().unwrap());
|
||||
for (bay, start, end, bytes) in results {
|
||||
println!("{{\"bay\":{bay},\"start_ns\":{start},\"end_ns\":{end},\"bytes\":{bytes}}}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user