use std::fs;
use std::io::{self, Read};
use super::bench_mem::bench_mem;
use super::compress_strategy::build_compression_parameters;
use super::config::{BenchConfig, MAX_MEMORY};
use super::decompress_binding::FrameDecompressor;
const LZ4_MAX_INPUT_SIZE: usize = 0x7E00_0000;
fn find_max_mem(required_mem: u64) -> usize {
const STEP: u64 = 64 * 1024 * 1024;
let mut mem = ((required_mem >> 26) + 1) << 26;
mem = mem.saturating_add(2 * STEP);
if mem > MAX_MEMORY as u64 {
mem = MAX_MEMORY as u64;
}
if mem > STEP {
mem -= STEP;
} else {
mem >>= 1;
}
if mem > STEP {
mem -= STEP;
} else {
mem >>= 1;
}
mem as usize
}
pub fn bench_c_level(
src: &[u8],
display_name: &str,
c_level: i32,
c_level_last: i32,
config: &BenchConfig,
dict: &[u8],
file_sizes: &[usize],
) -> io::Result<()> {
let display_name = if let Some(pos) = display_name.rfind('\\') {
&display_name[pos + 1..]
} else if let Some(pos) = display_name.rfind('/') {
&display_name[pos + 1..]
} else {
display_name
};
#[cfg(feature = "realtime-priority")]
{
unsafe {
libc::setpriority(libc::PRIO_PROCESS, 0, -20);
}
}
if config.display_level == 1 && config.additional_param == 0 {
eprintln!(
"bench {} {}: input {} bytes, {} seconds, {} KB blocks",
crate::LZ4_VERSION_STRING,
crate::LZ4_GIT_COMMIT_STRING,
src.len(),
config.nb_seconds,
config.block_size >> 10,
);
}
let c_level_last = c_level_last.max(c_level);
let mut bench_error = false;
for l in c_level..=c_level_last {
let mut strategy = build_compression_parameters(l, src.len(), src.len());
let mut decompressor = FrameDecompressor::new();
if let Err(e) = bench_mem(
src,
display_name,
config,
l,
&mut *strategy,
&mut decompressor,
dict,
file_sizes,
) {
eprintln!("bench error at level {}: {}", l, e);
bench_error = true;
}
}
if bench_error {
Err(io::Error::other("benchmark reported errors"))
} else {
Ok(())
}
}
pub fn load_files(
paths: &[&str],
buffer_size: usize,
config: &BenchConfig,
) -> io::Result<(Vec<u8>, Vec<usize>)> {
let mut buffer = vec![0u8; buffer_size];
let mut file_sizes = vec![0usize; paths.len()];
let mut pos: usize = 0;
let mut total_size: usize = 0;
let mut nb_files = paths.len();
for (n, path) in paths.iter().enumerate() {
if n >= nb_files {
break;
}
let meta = fs::metadata(path)
.map_err(|e| io::Error::new(e.kind(), format!("cannot stat {}: {}", path, e)))?;
if meta.is_dir() {
if config.display_level >= 2 {
eprintln!("Ignoring {} directory... ", path);
}
file_sizes[n] = 0;
continue;
}
let file_size_on_disk = meta.len() as usize;
if config.display_level >= 2 {
eprint!("Loading {}... \r", path);
}
let to_read = if file_size_on_disk > buffer_size - pos {
nb_files = n;
buffer_size - pos
} else {
file_size_on_disk
};
let mut f = fs::File::open(path).map_err(|e| {
io::Error::new(e.kind(), format!("impossible to open file {}: {}", path, e))
})?;
f.read_exact(&mut buffer[pos..pos + to_read])
.map_err(|e| io::Error::new(e.kind(), format!("could not read {}: {}", path, e)))?;
pos += to_read;
file_sizes[n] = to_read;
total_size += to_read;
}
if total_size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"no data to bench",
));
}
buffer.truncate(pos);
Ok((buffer, file_sizes))
}
pub fn bench_file_table(
file_names: &[&str],
c_level: i32,
c_level_last: i32,
dict: &[u8],
config: &BenchConfig,
) -> io::Result<()> {
let total_size_to_load: u64 = file_names
.iter()
.filter_map(|p| fs::metadata(p).ok())
.filter(|m| !m.is_dir())
.map(|m| m.len())
.sum();
let mut benched_size = find_max_mem(total_size_to_load.saturating_mul(3)) / 3;
if benched_size == 0 {
return Err(io::Error::other("not enough memory"));
}
if benched_size as u64 > total_size_to_load {
benched_size = total_size_to_load as usize;
}
if benched_size > LZ4_MAX_INPUT_SIZE {
benched_size = LZ4_MAX_INPUT_SIZE;
eprintln!(
"File(s) bigger than LZ4's max input size; testing {} MB only...",
benched_size >> 20
);
} else if (benched_size as u64) < total_size_to_load {
eprintln!(
"Not enough memory; testing {} MB only...",
benched_size >> 20
);
}
let (src_buffer, file_sizes) = load_files(file_names, benched_size, config)?;
let display_name = if file_names.len() > 1 {
format!(" {} files", file_names.len())
} else {
file_names[0].to_string()
};
bench_c_level(
&src_buffer,
&display_name,
c_level,
c_level_last,
config,
dict,
&file_sizes,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bench::config::BenchConfig;
#[test]
fn find_max_mem_sanity() {
let r = find_max_mem(1024 * 1024);
assert!(r > 0, "find_max_mem must return > 0 for non-trivial input");
assert!(r <= MAX_MEMORY, "find_max_mem must not exceed MAX_MEMORY");
}
#[test]
fn find_max_mem_large_input() {
let r = find_max_mem(u64::MAX / 2);
assert!(r <= MAX_MEMORY);
assert!(r > 0);
}
#[test]
fn find_max_mem_zero() {
let r = find_max_mem(0);
assert!(r > 0);
assert!(r <= MAX_MEMORY);
}
#[test]
fn bench_c_level_three_levels() {
let src: Vec<u8> = (0u8..128).cycle().take(64 * 1024).collect();
let mut config = BenchConfig::default();
config.set_nb_seconds(0); config.set_notification_level(0); let result = bench_c_level(&src, "test_input", 1, 3, &config, b"", &[]);
assert!(
result.is_ok(),
"bench_c_level should succeed: {:?}",
result.err()
);
}
#[test]
fn bench_c_level_clamped_when_last_lt_first() {
let src: Vec<u8> = (0u8..64).cycle().take(4096).collect();
let mut config = BenchConfig::default();
config.set_nb_seconds(0);
config.set_notification_level(0);
let result = bench_c_level(&src, "test", 3, 1, &config, b"", &[]);
assert!(result.is_ok(), "clamped level range should succeed");
}
#[test]
fn load_files_empty_returns_error() {
let config = BenchConfig::default();
let result = load_files(&[], 1024, &config);
assert!(result.is_err(), "empty file list should return Err");
let err = result.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn load_files_reads_file_content() {
use std::io::Write;
let mut tmp = tempfile::NamedTempFile::new().expect("tmp file");
let content = b"hello benchmark world!";
tmp.write_all(content).unwrap();
let path = tmp.path().to_str().unwrap().to_owned();
let config = BenchConfig::default();
let (buf, sizes) = load_files(&[&path], 4096, &config).expect("load should succeed");
assert_eq!(&buf[..], content);
assert_eq!(sizes[0], content.len());
}
#[test]
fn load_files_truncates_when_buffer_small() {
use std::io::Write;
let mut tmp = tempfile::NamedTempFile::new().unwrap();
let content = b"abcdefghijklmnopqrstuvwxyz";
tmp.write_all(content).unwrap();
let path = tmp.path().to_str().unwrap().to_owned();
let config = BenchConfig::default();
let (buf, sizes) =
load_files(&[&path], 10, &config).expect("truncated load should succeed");
assert_eq!(buf.len(), 10);
assert_eq!(sizes[0], 10);
assert_eq!(&buf[..], &content[..10]);
}
}