use std::fs::File;
use std::io::{BufReader, Write};
use std::ops::Deref;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::mpsc::{sync_channel, TrySendError};
use std::sync::{Arc, Mutex};
use std::thread;
use structopt::StructOpt;
use slb::{fileblocks, sharder};
#[derive(Debug, StructOpt)]
#[structopt(name = "slb", about = "Performs streaming load balancing.")]
struct Opt {
#[structopt(long)]
mapper: Option<String>,
#[structopt(long)]
folder: String,
#[structopt(long)]
infile: Vec<PathBuf>,
#[structopt(long)]
outprefix: PathBuf,
#[structopt(long)]
bufsize: Option<usize>,
#[structopt(long)]
verbose: bool,
#[structopt(long)]
nthreads: Option<usize>,
}
fn main() {
let opt = Opt::from_args();
let verbose = opt.verbose;
let nthreads = opt.nthreads.unwrap_or(num_cpus::get_physical());
let mapper_cmd = opt.mapper.as_deref().unwrap_or("cat");
let folder_cmd = &opt.folder;
let bufsize = opt.bufsize.unwrap_or(64) * 1024;
let queuesize = 256;
assert!(!opt.infile.is_empty());
let read_chunk_size = 16 * 1024;
let chunks = fileblocks::chunkify_multiple(&opt.infile, nthreads, read_chunk_size);
let nthreads = chunks.len(); assert!(nthreads >= 1);
let mut mapper_processes: Vec<_> = chunks
.iter()
.enumerate()
.map(|(i, chunk)| {
Command::new("/bin/bash")
.arg("-c")
.arg(format!(
"head -c {} | /bin/bash -c '{}'",
chunk.nbytes(),
mapper_cmd
))
.stdin(chunk.file())
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("error spawn map child {}: {}", i, err))
})
.collect();
let mapper_outputs: Vec<_> = mapper_processes
.iter_mut()
.map(|child| child.stdout.take().unwrap())
.collect();
let (txs, rxs): (Vec<_>, Vec<_>) = (0..nthreads).map(|_| sync_channel(queuesize)).unzip();
let lines_sent = vec![0usize; nthreads];
let lines_blocking = vec![0usize; nthreads];
let stats = Arc::new(Mutex::new((lines_sent, lines_blocking)));
let txs_ref = Arc::new(txs);
let mapper_output_threads: Vec<_> = mapper_outputs
.into_iter()
.map(|output| {
let txs_ref_clone = Arc::clone(&txs_ref);
let stats = Arc::clone(&stats);
thread::spawn(move || {
let output = BufReader::new(output);
let txs_ref_local = txs_ref_clone.deref();
let mut lines_sent = vec![0usize; nthreads];
let mut lines_blocking = vec![0usize; nthreads];
sharder::shard(output, nthreads, bufsize, |ix, buf| {
lines_sent[ix] += 1;
if let Err(TrySendError::Full(buf)) = txs_ref_local[ix].try_send(buf) {
lines_blocking[ix] += 1;
txs_ref_local[ix].send(buf).expect("send");
}
});
let mut guard = stats.lock().unwrap();
for i in 0..nthreads {
let ref mut sends = guard.0;
sends[i] += lines_sent[i];
let ref mut blocks = guard.1;
blocks[i] += lines_blocking[i];
}
})
})
.collect();
let folder_processes: Vec<_> = (0..nthreads)
.map(|i| {
let outprefix = opt.outprefix.clone();
let width = format!("{}", nthreads - 1).len();
let suffix = format!("{:0>width$}", i, width = width);
let mut fname = outprefix.file_name().expect("file name").to_owned();
fname.push(&suffix);
let path = outprefix.with_file_name(fname);
let file = File::create(&path).expect("write file");
Command::new("/bin/bash")
.arg("-c")
.arg(folder_cmd)
.stdin(Stdio::piped())
.stdout(file)
.spawn()
.unwrap_or_else(|err| panic!("error spawn fold child {}: {}", i, err))
})
.collect();
let folder_input_output_threads: Vec<_> = folder_processes
.into_iter()
.zip(rxs.into_iter())
.map(|(mut child, rx)| {
thread::spawn(move || {
let mut child_stdin = child.stdin.take().expect("child stdin");
while let Ok(lines) = rx.recv() {
child_stdin.write_all(&lines).expect("write lines");
}
drop(child_stdin);
assert!(child.wait().expect("wait").success());
})
})
.collect();
mapper_processes
.into_iter()
.for_each(|mut child| assert!(child.wait().expect("wait").success()));
mapper_output_threads
.into_iter()
.for_each(|handle| handle.join().expect("map output join"));
let txs = Arc::try_unwrap(txs_ref).expect("final reference");
drop(txs);
folder_input_output_threads
.into_iter()
.for_each(|handle| handle.join().expect("fold join"));
let stats = Arc::try_unwrap(stats).expect("final reference");
let (lines_sent, lines_blocking) = stats.into_inner().unwrap();
if verbose {
println!("sent {:?}\nblock {:?}", lines_sent, lines_blocking);
}
}