|
| 1 | +// This file is part of the uutils coreutils package. |
| 2 | +// |
| 3 | +// For the full copyright and license information, please view the LICENSE |
| 4 | +// file that was distributed with this source code. |
| 5 | + |
| 6 | +use divan::{Bencher, black_box}; |
| 7 | +use std::fs::{self, File}; |
| 8 | +use std::io::Write; |
| 9 | +use std::path::Path; |
| 10 | +use tempfile::TempDir; |
| 11 | +use uu_ls::uumain; |
| 12 | +use uucore::benchmark::run_util_function; |
| 13 | + |
| 14 | +/// Helper to run ls with given arguments on a directory |
| 15 | +fn bench_ls_with_args(bencher: Bencher, temp_dir: &TempDir, args: &[&str]) { |
| 16 | + let temp_path_str = temp_dir.path().to_str().unwrap(); |
| 17 | + let mut full_args = vec!["-R"]; |
| 18 | + full_args.extend_from_slice(args); |
| 19 | + full_args.push(temp_path_str); |
| 20 | + |
| 21 | + bencher.bench(|| { |
| 22 | + black_box(run_util_function(uumain, &full_args)); |
| 23 | + }); |
| 24 | +} |
| 25 | + |
| 26 | +/// Create a deterministic directory tree for benchmarking ls -R performance |
| 27 | +fn create_directory_tree( |
| 28 | + base_dir: &Path, |
| 29 | + depth: usize, |
| 30 | + dirs_per_level: usize, |
| 31 | + files_per_dir: usize, |
| 32 | +) -> std::io::Result<()> { |
| 33 | + if depth == 0 { |
| 34 | + return Ok(()); |
| 35 | + } |
| 36 | + |
| 37 | + // Create files in current directory |
| 38 | + for file_idx in 0..files_per_dir { |
| 39 | + let file_path = base_dir.join(format!("file_{file_idx:04}.txt")); |
| 40 | + let mut file = File::create(&file_path)?; |
| 41 | + writeln!(file, "This is file {file_idx} at depth {depth}")?; |
| 42 | + } |
| 43 | + |
| 44 | + // Create subdirectories and recurse |
| 45 | + for dir_idx in 0..dirs_per_level { |
| 46 | + let dir_path = base_dir.join(format!("subdir_{dir_idx:04}")); |
| 47 | + fs::create_dir(&dir_path)?; |
| 48 | + create_directory_tree(&dir_path, depth - 1, dirs_per_level, files_per_dir)?; |
| 49 | + } |
| 50 | + |
| 51 | + Ok(()) |
| 52 | +} |
| 53 | + |
| 54 | +/// Create a wide directory tree (many files/dirs at shallow depth) |
| 55 | +fn create_wide_tree(base_dir: &Path, total_files: usize, total_dirs: usize) -> std::io::Result<()> { |
| 56 | + // Create many files in root |
| 57 | + for file_idx in 0..total_files { |
| 58 | + let file_path = base_dir.join(format!("wide_file_{file_idx:06}.txt")); |
| 59 | + let mut file = File::create(&file_path)?; |
| 60 | + writeln!(file, "Wide tree file {file_idx}")?; |
| 61 | + } |
| 62 | + |
| 63 | + // Create many directories with few files each |
| 64 | + let files_per_subdir = 5; |
| 65 | + for dir_idx in 0..total_dirs { |
| 66 | + let dir_path = base_dir.join(format!("wide_dir_{dir_idx:06}")); |
| 67 | + fs::create_dir(&dir_path)?; |
| 68 | + |
| 69 | + for file_idx in 0..files_per_subdir { |
| 70 | + let file_path = dir_path.join(format!("file_{file_idx}.txt")); |
| 71 | + let mut file = File::create(&file_path)?; |
| 72 | + writeln!(file, "File {file_idx} in wide dir {dir_idx}")?; |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + Ok(()) |
| 77 | +} |
| 78 | + |
| 79 | +/// Create a deep directory tree (few files/dirs but deep nesting) |
| 80 | +fn create_deep_tree(base_dir: &Path, depth: usize, files_per_level: usize) -> std::io::Result<()> { |
| 81 | + let mut current_dir = base_dir.to_path_buf(); |
| 82 | + |
| 83 | + for level in 0..depth { |
| 84 | + // Create files at this level |
| 85 | + for file_idx in 0..files_per_level { |
| 86 | + let file_path = current_dir.join(format!("deep_file_{level}_{file_idx}.txt")); |
| 87 | + let mut file = File::create(&file_path)?; |
| 88 | + writeln!(file, "File {file_idx} at depth level {level}")?; |
| 89 | + } |
| 90 | + |
| 91 | + // Create next level directory |
| 92 | + if level < depth - 1 { |
| 93 | + let next_dir = current_dir.join(format!("level_{:04}", level + 1)); |
| 94 | + fs::create_dir(&next_dir)?; |
| 95 | + current_dir = next_dir; |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + Ok(()) |
| 100 | +} |
| 101 | + |
| 102 | +/// Create a tree with mixed file types and permissions for comprehensive testing |
| 103 | +fn create_mixed_tree(base_dir: &Path) -> std::io::Result<()> { |
| 104 | + use std::os::unix::fs::PermissionsExt; |
| 105 | + |
| 106 | + let extensions = ["txt", "log", "dat", "tmp", "bak", "cfg"]; |
| 107 | + let sizes = [0, 100, 1024, 10240]; |
| 108 | + |
| 109 | + for (i, ext) in extensions.iter().enumerate() { |
| 110 | + for (j, &size) in sizes.iter().enumerate() { |
| 111 | + let file_path = base_dir.join(format!("mixed_file_{i}_{j}.{ext}")); |
| 112 | + let mut file = File::create(&file_path)?; |
| 113 | + |
| 114 | + if size > 0 { |
| 115 | + let content = "x".repeat(size); |
| 116 | + file.write_all(content.as_bytes())?; |
| 117 | + } |
| 118 | + |
| 119 | + let perms = fs::Permissions::from_mode(match (i + j) % 4 { |
| 120 | + 0 => 0o644, |
| 121 | + 1 => 0o755, |
| 122 | + 2 => 0o600, |
| 123 | + _ => 0o444, |
| 124 | + }); |
| 125 | + fs::set_permissions(&file_path, perms)?; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + // Create some subdirectories |
| 130 | + for i in 0..5 { |
| 131 | + let dir_path = base_dir.join(format!("mixed_subdir_{i}")); |
| 132 | + fs::create_dir(&dir_path)?; |
| 133 | + |
| 134 | + for j in 0..3 { |
| 135 | + let file_path = dir_path.join(format!("sub_file_{j}.txt")); |
| 136 | + let mut file = File::create(&file_path)?; |
| 137 | + writeln!(file, "File {j} in subdir {i}")?; |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + Ok(()) |
| 142 | +} |
| 143 | + |
| 144 | +/// Benchmark ls -R on balanced directory tree |
| 145 | +#[divan::bench(args = [(3, 4, 8), (4, 3, 6), (5, 2, 10)])] |
| 146 | +fn ls_recursive_balanced_tree( |
| 147 | + bencher: Bencher, |
| 148 | + (depth, dirs_per_level, files_per_dir): (usize, usize, usize), |
| 149 | +) { |
| 150 | + let temp_dir = TempDir::new().unwrap(); |
| 151 | + create_directory_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir).unwrap(); |
| 152 | + bench_ls_with_args(bencher, &temp_dir, &[]); |
| 153 | +} |
| 154 | + |
| 155 | +/// Benchmark ls -R -a -l on balanced directory tree (tests PR #8728 optimization) |
| 156 | +#[divan::bench(args = [(3, 4, 8), (4, 3, 6), (5, 2, 10)])] |
| 157 | +fn ls_recursive_long_all_balanced_tree( |
| 158 | + bencher: Bencher, |
| 159 | + (depth, dirs_per_level, files_per_dir): (usize, usize, usize), |
| 160 | +) { |
| 161 | + let temp_dir = TempDir::new().unwrap(); |
| 162 | + create_directory_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir).unwrap(); |
| 163 | + bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]); |
| 164 | +} |
| 165 | + |
| 166 | +/// Benchmark ls -R on wide directory structures |
| 167 | +#[divan::bench(args = [(1000, 200), (5000, 500), (10000, 1000)])] |
| 168 | +fn ls_recursive_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) { |
| 169 | + let temp_dir = TempDir::new().unwrap(); |
| 170 | + create_wide_tree(temp_dir.path(), total_files, total_dirs).unwrap(); |
| 171 | + bench_ls_with_args(bencher, &temp_dir, &[]); |
| 172 | +} |
| 173 | + |
| 174 | +/// Benchmark ls -R -a -l on wide directory structures |
| 175 | +#[divan::bench(args = [(1000, 200), (5000, 500)])] |
| 176 | +fn ls_recursive_long_all_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) { |
| 177 | + let temp_dir = TempDir::new().unwrap(); |
| 178 | + create_wide_tree(temp_dir.path(), total_files, total_dirs).unwrap(); |
| 179 | + bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]); |
| 180 | +} |
| 181 | + |
| 182 | +/// Benchmark ls -R on deep directory structures |
| 183 | +#[divan::bench(args = [(20, 3), (50, 2), (100, 1)])] |
| 184 | +fn ls_recursive_deep_tree(bencher: Bencher, (depth, files_per_level): (usize, usize)) { |
| 185 | + let temp_dir = TempDir::new().unwrap(); |
| 186 | + create_deep_tree(temp_dir.path(), depth, files_per_level).unwrap(); |
| 187 | + bench_ls_with_args(bencher, &temp_dir, &[]); |
| 188 | +} |
| 189 | + |
| 190 | +/// Benchmark ls -R -a -l on deep directory structures |
| 191 | +#[divan::bench(args = [(20, 3), (50, 2)])] |
| 192 | +fn ls_recursive_long_all_deep_tree(bencher: Bencher, (depth, files_per_level): (usize, usize)) { |
| 193 | + let temp_dir = TempDir::new().unwrap(); |
| 194 | + create_deep_tree(temp_dir.path(), depth, files_per_level).unwrap(); |
| 195 | + bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]); |
| 196 | +} |
| 197 | + |
| 198 | +/// Benchmark ls -R on mixed file types (comprehensive real-world test) |
| 199 | +#[divan::bench] |
| 200 | +fn ls_recursive_mixed_tree(bencher: Bencher) { |
| 201 | + let temp_dir = TempDir::new().unwrap(); |
| 202 | + create_mixed_tree(temp_dir.path()).unwrap(); |
| 203 | + |
| 204 | + for i in 0..10 { |
| 205 | + let subdir = temp_dir.path().join(format!("mixed_branch_{i}")); |
| 206 | + fs::create_dir(&subdir).unwrap(); |
| 207 | + create_mixed_tree(&subdir).unwrap(); |
| 208 | + } |
| 209 | + |
| 210 | + bench_ls_with_args(bencher, &temp_dir, &[]); |
| 211 | +} |
| 212 | + |
| 213 | +/// Benchmark ls -R -a -l on mixed file types (most comprehensive test) |
| 214 | +#[divan::bench] |
| 215 | +fn ls_recursive_long_all_mixed_tree(bencher: Bencher) { |
| 216 | + let temp_dir = TempDir::new().unwrap(); |
| 217 | + create_mixed_tree(temp_dir.path()).unwrap(); |
| 218 | + |
| 219 | + for i in 0..10 { |
| 220 | + let subdir = temp_dir.path().join(format!("mixed_branch_{i}")); |
| 221 | + fs::create_dir(&subdir).unwrap(); |
| 222 | + create_mixed_tree(&subdir).unwrap(); |
| 223 | + } |
| 224 | + |
| 225 | + bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]); |
| 226 | +} |
| 227 | + |
| 228 | +fn main() { |
| 229 | + divan::main(); |
| 230 | +} |
0 commit comments