Skip to content

Commit b63fc94

Browse files
committed
feat(hostname): add benchmark with large /etc/hosts
Adds comprehensive benchmarks for the hostname utility, including: - Realistic hosts file generation with various hostname patterns - Benchmarks for all flags: -s, -d, -f, -i - Parameterized tests with 1K, 10K, 100K entries - NSS_WRAPPER support for testing with generated hosts files - CI integration for automated benchmark tracking Fixes #10949
1 parent 2993148 commit b63fc94

File tree

4 files changed

+287
-0
lines changed

4 files changed

+287
-0
lines changed

.github/workflows/benchmarks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
uu_du,
3838
uu_expand,
3939
uu_fold,
40+
uu_hostname,
4041
uu_join,
4142
uu_ls,
4243
uu_mv,

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/hostname/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@ path = "src/main.rs"
3939

4040
[package.metadata.cargo-udeps.ignore]
4141
normal = ["uucore_procs"]
42+
43+
[[bench]]
44+
name = "hostname_bench"
45+
harness = false
46+
47+
[dev-dependencies]
48+
divan = { workspace = true }
49+
tempfile = { workspace = true }
50+
uucore = { workspace = true, features = ["benchmark"] }
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
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+
//! Benchmarks for hostname utility
7+
//!
8+
//! This benchmark tests the performance of hostname with large /etc/hosts files,
9+
//! specifically targeting the DNS resolution functionality (`-i` flag).
10+
//!
11+
//! # Important Note on Large Hosts File Testing
12+
//!
13+
//! To properly test with large /etc/hosts files, NSS_WRAPPER library must be used:
14+
//!
15+
//! ```bash
16+
//! LD_PRELOAD=/usr/lib/libnss_wrapper.so NSS_WRAPPER_HOSTS=/tmp/large_hosts \
17+
//! cargo bench --package uu_hostname hostname_ip_lookup
18+
//! ```
19+
//!
20+
//! Without NSS_WRAPPER, the benchmark tests with the system's real /etc/hosts.
21+
22+
use divan::{Bencher, black_box};
23+
use std::io::Write;
24+
use std::path::PathBuf;
25+
use uu_hostname::uumain;
26+
use uucore::benchmark::run_util_function;
27+
28+
/// Generate a realistic large hosts file with the specified number of entries
29+
///
30+
/// Creates a hosts file that mimics real environments:
31+
/// - Includes comments and blank lines
32+
/// - Multiple aliases per IP (short name, FQDN, service names)
33+
/// - Various IP ranges (127.x, 10.x, 192.168.x, 172.16.x)
34+
/// - Realistic hostname patterns
35+
fn generate_realistic_hosts_file(entries: usize) -> Vec<u8> {
36+
let avg_line_size = 80; // Average bytes per line with comments/aliases
37+
let mut data = Vec::with_capacity(entries * avg_line_size);
38+
let mut writer = std::io::BufWriter::new(&mut data);
39+
40+
// File header comment
41+
writeln!(writer, "# Generated hosts file for benchmarking").unwrap();
42+
writeln!(writer, "# Total entries: {entries}").unwrap();
43+
writeln!(writer, "#").unwrap();
44+
writeln!(writer).unwrap();
45+
46+
// Standard localhost entries
47+
writeln!(writer, "# Localhost entries").unwrap();
48+
writeln!(
49+
writer,
50+
"127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4"
51+
)
52+
.unwrap();
53+
writeln!(
54+
writer,
55+
"::1 localhost localhost.localdomain localhost6 localhost6.localdomain6"
56+
)
57+
.unwrap();
58+
writeln!(writer).unwrap();
59+
60+
// Generate realistic host entries
61+
let environments = ["prod", "staging", "dev", "test"];
62+
let regions = ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"];
63+
let roles = [
64+
"web", "db", "cache", "api", "worker", "lb", "monitor", "backup",
65+
];
66+
67+
for i in 2..=entries {
68+
// Add occasional section comments (every 1000 entries)
69+
if i % 1000 == 2 && i > 2 {
70+
writeln!(writer).unwrap();
71+
writeln!(
72+
writer,
73+
"# {} environment hosts (entry {}-{})",
74+
environments[(i / 1000) % environments.len()],
75+
i,
76+
i + 998
77+
)
78+
.unwrap();
79+
}
80+
81+
// Add occasional blank lines (every 50 entries)
82+
if i % 50 == 0 {
83+
writeln!(writer).unwrap();
84+
}
85+
86+
// Generate realistic IP
87+
let ip = match i % 5 {
88+
0 => format!("127.0.0.{}", i % 256),
89+
1 => format!("10.{}.{}.{}", (i / 256) % 256, (i / 16) % 16, i % 256),
90+
2 => format!(
91+
"192.168.{}.{}",
92+
(i / 256) % 256,
93+
i % 256
94+
),
95+
3 => format!(
96+
"172.16.{}.{}",
97+
(i / 256) % 256,
98+
i % 256
99+
),
100+
_ => format!(
101+
"10.0.{}.{}",
102+
(i / 256) % 256,
103+
i % 256
104+
),
105+
};
106+
107+
// Generate hostname patterns
108+
let role = roles[i % roles.len()];
109+
let env = environments[i % environments.len()];
110+
let region = regions[i % regions.len()];
111+
let index = i;
112+
113+
// Multiple aliases: FQDN, short name, service alias
114+
let fqdn = format!("{role}-{index:03}.{region}.{env}.example.com");
115+
let short = format!("{role}-{index:03}");
116+
let service_alias = format!("{role}-{env}-{}", region.replace("-", ""));
117+
118+
// Write entry with realistic spacing
119+
writeln!(writer, "{:<15} {} {} {}", ip, fqdn, short, service_alias).unwrap();
120+
}
121+
122+
// Add footer comment
123+
writeln!(writer).unwrap();
124+
writeln!(writer, "# End of hosts file").unwrap();
125+
126+
writer.flush().unwrap();
127+
drop(writer);
128+
data
129+
}
130+
131+
/// Setup function that prepares the environment with a large hosts file
132+
///
133+
/// Uses NSS_WRAPPER if available to redirect /etc/hosts resolution
134+
fn setup_hosts_environment(entries: usize) -> Option<HostsEnvironment> {
135+
let temp_dir = tempfile::tempdir().ok()?;
136+
let hosts_file = temp_dir.path().join("hosts");
137+
138+
// Generate hosts file
139+
let hosts_data = generate_realistic_hosts_file(entries);
140+
std::fs::write(&hosts_file, &hosts_data).ok()?;
141+
142+
Some(HostsEnvironment {
143+
_temp_dir: temp_dir,
144+
hosts_file,
145+
})
146+
}
147+
148+
/// Environment guard for hosts file benchmarks
149+
struct HostsEnvironment {
150+
_temp_dir: tempfile::TempDir,
151+
hosts_file: PathBuf,
152+
}
153+
154+
/// Benchmark hostname -i (IP address resolution) with large hosts files
155+
///
156+
/// Tests DNS lookup performance with large /etc/hosts files.
157+
///
158+
/// # Important
159+
/// To test with generated hosts file, use NSS_WRAPPER:
160+
/// `LD_PRELOAD=/usr/lib/libnss_wrapper.so NSS_WRAPPER_HOSTS=/path/to/hosts cargo bench`
161+
#[divan::bench(
162+
args = [100_000],
163+
name = "hostname_ip_lookup"
164+
)]
165+
fn bench_hostname_ip(bencher: Bencher, entries: usize) {
166+
// Setup hosts environment
167+
let env = match setup_hosts_environment(entries) {
168+
Some(e) => e,
169+
None => {
170+
return;
171+
}
172+
};
173+
174+
// Check if NSS_WRAPPER is available
175+
let nss_wrapper_env = std::env::var("LD_PRELOAD").unwrap_or_default();
176+
let has_nss_wrapper = nss_wrapper_env.contains("nss_wrapper");
177+
178+
if has_nss_wrapper {
179+
unsafe {
180+
std::env::set_var("NSS_WRAPPER_HOSTS", &env.hosts_file);
181+
}
182+
}
183+
184+
bencher.bench(|| {
185+
let result = black_box(run_util_function(uumain, &["-i"]));
186+
assert_eq!(result, 0, "hostname -i should succeed");
187+
});
188+
189+
if has_nss_wrapper {
190+
unsafe {
191+
std::env::remove_var("NSS_WRAPPER_HOSTS");
192+
}
193+
}
194+
}
195+
196+
/// Benchmark basic hostname display
197+
#[divan::bench(name = "hostname_basic")]
198+
fn bench_hostname_basic(bencher: Bencher) {
199+
bencher.bench(|| {
200+
let result = black_box(run_util_function(uumain, &[]));
201+
assert_eq!(result, 0, "hostname should succeed");
202+
});
203+
}
204+
205+
/// Benchmark hostname -s (short hostname)
206+
#[divan::bench(name = "hostname_short")]
207+
fn bench_hostname_short(bencher: Bencher) {
208+
bencher.bench(|| {
209+
let result = black_box(run_util_function(uumain, &["-s"]));
210+
assert_eq!(result, 0, "hostname -s should succeed");
211+
});
212+
}
213+
214+
/// Benchmark hostname -f (FQDN)
215+
#[divan::bench(name = "hostname_fqdn")]
216+
fn bench_hostname_fqdn(bencher: Bencher) {
217+
bencher.bench(|| {
218+
let result = black_box(run_util_function(uumain, &["-f"]));
219+
assert_eq!(result, 0, "hostname -f should succeed");
220+
});
221+
}
222+
223+
/// Benchmark hostname -d (domain)
224+
#[divan::bench(name = "hostname_domain")]
225+
fn bench_hostname_domain(bencher: Bencher) {
226+
bencher.bench(|| {
227+
let result = black_box(run_util_function(uumain, &["-d"]));
228+
assert_eq!(result, 0, "hostname -d should succeed");
229+
});
230+
}
231+
232+
/// Benchmark direct dns-lookup crate performance (BSD path)
233+
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
234+
#[divan::bench(
235+
args = [100_000],
236+
name = "dns_lookup_direct"
237+
)]
238+
fn bench_dns_lookup_direct(bencher: Bencher, _entries: usize) {
239+
use dns_lookup::lookup_host;
240+
241+
let hostname = hostname::get()
242+
.unwrap_or_default()
243+
.to_string_lossy()
244+
.into_owned();
245+
246+
bencher.bench(|| {
247+
let result = lookup_host(&hostname);
248+
let _ = black_box(result);
249+
});
250+
}
251+
252+
/// Benchmark std::net::ToSocketAddrs performance (Linux/macOS path)
253+
#[cfg(not(any(target_os = "freebsd", target_os = "openbsd")))]
254+
#[divan::bench(
255+
args = [100_000],
256+
name = "socket_addrs_direct"
257+
)]
258+
fn bench_socket_addrs_direct(bencher: Bencher, _entries: usize) {
259+
use std::net::ToSocketAddrs;
260+
261+
let hostname = hostname::get()
262+
.unwrap_or_default()
263+
.to_string_lossy()
264+
.into_owned();
265+
let hostname_with_port = format!("{hostname}:1");
266+
267+
bencher.bench(|| {
268+
let result: Result<Vec<_>, _> = hostname_with_port.to_socket_addrs().map(Iterator::collect);
269+
let _ = black_box(result);
270+
});
271+
}
272+
273+
fn main() {
274+
divan::main();
275+
}

0 commit comments

Comments
 (0)