mirror of
https://github.com/YGGverse/aquatic.git
synced 2026-03-31 17:55:36 +00:00
Merge pull request #166 from greatest-ape/work-2024-01-01
udp: distribute swarm responses among socket workers; improve aquatic_bencher
This commit is contained in:
commit
8c8489960c
10 changed files with 409 additions and 179 deletions
|
|
@ -7,6 +7,8 @@
|
||||||
#### Added
|
#### Added
|
||||||
|
|
||||||
* Add `aquatic_peer_id` crate with peer client information logic
|
* Add `aquatic_peer_id` crate with peer client information logic
|
||||||
|
* Add `aquatic_bencher` crate for automated benchmarking of aquatic and other
|
||||||
|
BitTorrent trackers
|
||||||
|
|
||||||
### aquatic_udp
|
### aquatic_udp
|
||||||
|
|
||||||
|
|
@ -22,6 +24,8 @@
|
||||||
* Reuse allocations in swarm response channel
|
* Reuse allocations in swarm response channel
|
||||||
* Remove config key `network.poll_event_capacity`
|
* Remove config key `network.poll_event_capacity`
|
||||||
* Harden ConnectionValidator to make IP spoofing even more costly
|
* Harden ConnectionValidator to make IP spoofing even more costly
|
||||||
|
* Distribute announce responses from swarm workers over socket workers to
|
||||||
|
decrease performance loss due to underutilized threads
|
||||||
|
|
||||||
### aquatic_http
|
### aquatic_http
|
||||||
|
|
||||||
|
|
|
||||||
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -135,10 +135,12 @@ dependencies = [
|
||||||
"aquatic_udp",
|
"aquatic_udp",
|
||||||
"aquatic_udp_load_test",
|
"aquatic_udp_load_test",
|
||||||
"clap 4.4.11",
|
"clap 4.4.11",
|
||||||
|
"humanize-bytes",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.1.0",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools 0.12.0",
|
"itertools 0.12.0",
|
||||||
"nonblock",
|
"nonblock",
|
||||||
|
"num-format",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -1397,6 +1399,15 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humanize-bytes"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c8f5a0ffae64f844e5e311016d1d8184dd496c7136af420f665a877ac2f0681"
|
||||||
|
dependencies = [
|
||||||
|
"smartstring",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hwloc"
|
name = "hwloc"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -2604,6 +2615,17 @@ version = "1.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smartstring"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"static_assertions",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "snafu"
|
name = "snafu"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,11 @@ aquatic_udp_load_test = { optional = true, workspace = true }
|
||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
humanize-bytes = "1"
|
||||||
indexmap = "2"
|
indexmap = "2"
|
||||||
indoc = "2"
|
indoc = "2"
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
|
num-format = "0.4"
|
||||||
nonblock = "0.2"
|
nonblock = "0.2"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
|
||||||
230
crates/bencher/src/html.rs
Normal file
230
crates/bencher/src/html.rs
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
use humanize_bytes::humanize_bytes_binary;
|
||||||
|
use indexmap::{IndexMap, IndexSet};
|
||||||
|
use indoc::formatdoc;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use num_format::{Locale, ToFormattedString};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
run::ProcessStats,
|
||||||
|
set::{LoadTestRunResults, TrackerCoreCountResults},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn html_best_results(results: &[TrackerCoreCountResults]) -> String {
|
||||||
|
let mut all_implementation_names = IndexSet::new();
|
||||||
|
|
||||||
|
for core_count_results in results {
|
||||||
|
all_implementation_names.extend(
|
||||||
|
core_count_results
|
||||||
|
.implementations
|
||||||
|
.iter()
|
||||||
|
.map(|r| r.name.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data_rows = Vec::new();
|
||||||
|
|
||||||
|
for core_count_results in results {
|
||||||
|
let best_results = core_count_results
|
||||||
|
.implementations
|
||||||
|
.iter()
|
||||||
|
.map(|implementation| (implementation.name.clone(), implementation.best_result()))
|
||||||
|
.collect::<IndexMap<_, _>>();
|
||||||
|
|
||||||
|
let best_results_for_all_implementations = all_implementation_names
|
||||||
|
.iter()
|
||||||
|
.map(|name| best_results.get(name).cloned().flatten())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let data_row = format!(
|
||||||
|
"
|
||||||
|
<tr>
|
||||||
|
<th>{}</th>
|
||||||
|
{}
|
||||||
|
</tr>
|
||||||
|
",
|
||||||
|
core_count_results.core_count,
|
||||||
|
best_results_for_all_implementations
|
||||||
|
.into_iter()
|
||||||
|
.map(|result| {
|
||||||
|
if let Some(r) = result {
|
||||||
|
format!(
|
||||||
|
r#"<td><span title="{}, avg cpu utilization: {}%">{}</span></td>"#,
|
||||||
|
r.tracker_info,
|
||||||
|
r.tracker_process_stats.avg_cpu_utilization,
|
||||||
|
r.average_responses.to_formatted_string(&Locale::en),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"<td>-</td>".to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("\n"),
|
||||||
|
);
|
||||||
|
|
||||||
|
data_rows.push(data_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"
|
||||||
|
<h2>Best results</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>CPU cores</th>
|
||||||
|
{}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
",
|
||||||
|
all_implementation_names
|
||||||
|
.iter()
|
||||||
|
.map(|name| format!("<th>{name}</th>"))
|
||||||
|
.join("\n"),
|
||||||
|
data_rows.join("\n")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn html_all_runs(all_results: &[TrackerCoreCountResults]) -> String {
|
||||||
|
let mut all_implementation_names = IndexSet::new();
|
||||||
|
|
||||||
|
for core_count_results in all_results {
|
||||||
|
all_implementation_names.extend(
|
||||||
|
core_count_results
|
||||||
|
.implementations
|
||||||
|
.iter()
|
||||||
|
.map(|r| r.name.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct R {
|
||||||
|
core_count: usize,
|
||||||
|
avg_responses: Option<u64>,
|
||||||
|
tracker_keys: IndexMap<String, String>,
|
||||||
|
tracker_vcpus: String,
|
||||||
|
tracker_stats: Option<ProcessStats>,
|
||||||
|
load_test_keys: IndexMap<String, String>,
|
||||||
|
load_test_vcpus: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
let mut results_by_implementation: IndexMap<String, Vec<R>> = Default::default();
|
||||||
|
|
||||||
|
for implementation_name in all_implementation_names {
|
||||||
|
let results = results_by_implementation
|
||||||
|
.entry(implementation_name.clone())
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
let mut tracker_key_names: IndexSet<String> = Default::default();
|
||||||
|
let mut load_test_key_names: IndexSet<String> = Default::default();
|
||||||
|
|
||||||
|
for r in all_results {
|
||||||
|
for i in r
|
||||||
|
.implementations
|
||||||
|
.iter()
|
||||||
|
.filter(|i| i.name == implementation_name)
|
||||||
|
{
|
||||||
|
for c in i.configurations.iter() {
|
||||||
|
for l in c.load_tests.iter() {
|
||||||
|
match l {
|
||||||
|
LoadTestRunResults::Success(l) => {
|
||||||
|
tracker_key_names.extend(l.tracker_keys.keys().cloned());
|
||||||
|
load_test_key_names.extend(l.load_test_keys.keys().cloned());
|
||||||
|
|
||||||
|
results.push(R {
|
||||||
|
core_count: r.core_count,
|
||||||
|
avg_responses: Some(l.average_responses),
|
||||||
|
tracker_keys: l.tracker_keys.clone(),
|
||||||
|
tracker_vcpus: l.tracker_vcpus.as_cpu_list(),
|
||||||
|
tracker_stats: Some(l.tracker_process_stats),
|
||||||
|
load_test_keys: l.load_test_keys.clone(),
|
||||||
|
load_test_vcpus: l.load_test_vcpus.as_cpu_list(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
LoadTestRunResults::Failure(l) => {
|
||||||
|
tracker_key_names.extend(l.tracker_keys.keys().cloned());
|
||||||
|
load_test_key_names.extend(l.load_test_keys.keys().cloned());
|
||||||
|
|
||||||
|
results.push(R {
|
||||||
|
core_count: r.core_count,
|
||||||
|
avg_responses: None,
|
||||||
|
tracker_keys: l.tracker_keys.clone(),
|
||||||
|
tracker_vcpus: l.tracker_vcpus.as_cpu_list(),
|
||||||
|
tracker_stats: None,
|
||||||
|
load_test_keys: l.load_test_keys.clone(),
|
||||||
|
load_test_vcpus: l.load_test_vcpus.as_cpu_list(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_str(&formatdoc! {
|
||||||
|
"
|
||||||
|
<h2>Results for {implementation}</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Cores</th>
|
||||||
|
<th>Responses</th>
|
||||||
|
{tracker_key_names}
|
||||||
|
<th>Tracker avg CPU</th>
|
||||||
|
<th>Tracker peak RSS</th>
|
||||||
|
<th>Tracker vCPUs</th>
|
||||||
|
{load_test_key_names}
|
||||||
|
<th>Load test vCPUs</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{body}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
",
|
||||||
|
implementation = implementation_name,
|
||||||
|
tracker_key_names = tracker_key_names.iter()
|
||||||
|
.map(|name| format!("<th>{}</th>", name))
|
||||||
|
.join("\n"),
|
||||||
|
load_test_key_names = load_test_key_names.iter()
|
||||||
|
.map(|name| format!("<th>Load test {}</th>", name))
|
||||||
|
.join("\n"),
|
||||||
|
body = results.into_iter().map(|r| {
|
||||||
|
formatdoc! {
|
||||||
|
"
|
||||||
|
<tr>
|
||||||
|
<td>{cores}</td>
|
||||||
|
<td>{avg_responses}</td>
|
||||||
|
{tracker_key_values}
|
||||||
|
<td>{cpu}%</td>
|
||||||
|
<td>{mem}</td>
|
||||||
|
<td>{tracker_vcpus}</td>
|
||||||
|
{load_test_key_values}
|
||||||
|
<td>{load_test_vcpus}</td>
|
||||||
|
</tr>
|
||||||
|
",
|
||||||
|
cores = r.core_count,
|
||||||
|
avg_responses = r.avg_responses.map(|v| v.to_formatted_string(&Locale::en))
|
||||||
|
.unwrap_or_else(|| "-".to_string()),
|
||||||
|
tracker_key_values = tracker_key_names.iter().map(|name| {
|
||||||
|
format!("<td>{}</td>", r.tracker_keys.get(name).cloned().unwrap_or_else(|| "-".to_string()))
|
||||||
|
}).join("\n"),
|
||||||
|
cpu = r.tracker_stats.map(|stats| stats.avg_cpu_utilization.to_string())
|
||||||
|
.unwrap_or_else(|| "-".to_string()),
|
||||||
|
mem = r.tracker_stats
|
||||||
|
.map(|stats| humanize_bytes_binary!(stats.peak_rss_bytes).to_string())
|
||||||
|
.unwrap_or_else(|| "-".to_string()),
|
||||||
|
tracker_vcpus = r.tracker_vcpus,
|
||||||
|
load_test_key_values = load_test_key_names.iter().map(|name| {
|
||||||
|
format!("<td>{}</td>", r.load_test_keys.get(name).cloned().unwrap_or_else(|| "-".to_string()))
|
||||||
|
}).join("\n"),
|
||||||
|
load_test_vcpus = r.load_test_vcpus,
|
||||||
|
}
|
||||||
|
}).join("\n")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
pub mod html;
|
||||||
pub mod protocols;
|
pub mod protocols;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod set;
|
pub mod set;
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ impl UdpCommand {
|
||||||
ChihayaUdpRunner::new(),
|
ChihayaUdpRunner::new(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
load_test_runs: simple_load_test_runs(cpu_mode, &[1, 2, 4, 6]),
|
load_test_runs: simple_load_test_runs(cpu_mode, &[1, 2, 4, 6, 8]),
|
||||||
},
|
},
|
||||||
2 => SetConfig {
|
2 => SetConfig {
|
||||||
implementations: indexmap! {
|
implementations: indexmap! {
|
||||||
|
|
@ -106,7 +106,7 @@ impl UdpCommand {
|
||||||
ChihayaUdpRunner::new(),
|
ChihayaUdpRunner::new(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
load_test_runs: simple_load_test_runs(cpu_mode, &[1, 2, 4, 6]),
|
load_test_runs: simple_load_test_runs(cpu_mode, &[1, 2, 4, 6, 8]),
|
||||||
},
|
},
|
||||||
3 => SetConfig {
|
3 => SetConfig {
|
||||||
implementations: indexmap! {
|
implementations: indexmap! {
|
||||||
|
|
@ -161,54 +161,49 @@ impl UdpCommand {
|
||||||
implementations: indexmap! {
|
implementations: indexmap! {
|
||||||
UdpTracker::Aquatic => vec![
|
UdpTracker::Aquatic => vec![
|
||||||
AquaticUdpRunner::new(7, 1),
|
AquaticUdpRunner::new(7, 1),
|
||||||
AquaticUdpRunner::new(14, 1),
|
|
||||||
AquaticUdpRunner::new(6, 2),
|
AquaticUdpRunner::new(6, 2),
|
||||||
AquaticUdpRunner::new(12, 2),
|
AquaticUdpRunner::new(12, 2),
|
||||||
|
AquaticUdpRunner::new(5, 3),
|
||||||
],
|
],
|
||||||
UdpTracker::OpenTracker => vec![
|
UdpTracker::OpenTracker => vec![
|
||||||
OpenTrackerUdpRunner::new(8),
|
OpenTrackerUdpRunner::new(8),
|
||||||
OpenTrackerUdpRunner::new(16),
|
OpenTrackerUdpRunner::new(16),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
load_test_runs: simple_load_test_runs(cpu_mode, &[4, 8, 12]),
|
load_test_runs: simple_load_test_runs(cpu_mode, &[4, 8, 12, 16]),
|
||||||
},
|
},
|
||||||
12 => SetConfig {
|
12 => SetConfig {
|
||||||
implementations: indexmap! {
|
implementations: indexmap! {
|
||||||
UdpTracker::Aquatic => vec![
|
UdpTracker::Aquatic => vec![
|
||||||
AquaticUdpRunner::new(11, 1),
|
|
||||||
AquaticUdpRunner::new(22, 1),
|
|
||||||
AquaticUdpRunner::new(10, 2),
|
AquaticUdpRunner::new(10, 2),
|
||||||
AquaticUdpRunner::new(20, 2),
|
|
||||||
AquaticUdpRunner::new(9, 3),
|
AquaticUdpRunner::new(9, 3),
|
||||||
AquaticUdpRunner::new(18, 3),
|
|
||||||
AquaticUdpRunner::new(8, 4),
|
AquaticUdpRunner::new(8, 4),
|
||||||
AquaticUdpRunner::new(16, 4),
|
AquaticUdpRunner::new(16, 4),
|
||||||
|
AquaticUdpRunner::new(9, 5),
|
||||||
],
|
],
|
||||||
UdpTracker::OpenTracker => vec![
|
UdpTracker::OpenTracker => vec![
|
||||||
OpenTrackerUdpRunner::new(12),
|
OpenTrackerUdpRunner::new(12),
|
||||||
OpenTrackerUdpRunner::new(24),
|
OpenTrackerUdpRunner::new(24),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
load_test_runs: simple_load_test_runs(cpu_mode, &[8, 12, 16]),
|
load_test_runs: simple_load_test_runs(cpu_mode, &[8, 12, 16, 24]),
|
||||||
},
|
},
|
||||||
16 => SetConfig {
|
16 => SetConfig {
|
||||||
implementations: indexmap! {
|
implementations: indexmap! {
|
||||||
UdpTracker::Aquatic => vec![
|
UdpTracker::Aquatic => vec![
|
||||||
AquaticUdpRunner::new(14, 2),
|
|
||||||
AquaticUdpRunner::new(28, 2),
|
|
||||||
AquaticUdpRunner::new(13, 3),
|
AquaticUdpRunner::new(13, 3),
|
||||||
AquaticUdpRunner::new(26, 3),
|
|
||||||
AquaticUdpRunner::new(12, 4),
|
AquaticUdpRunner::new(12, 4),
|
||||||
AquaticUdpRunner::new(24, 4),
|
|
||||||
AquaticUdpRunner::new(11, 5),
|
AquaticUdpRunner::new(11, 5),
|
||||||
AquaticUdpRunner::new(22, 5),
|
AquaticUdpRunner::new(10, 6),
|
||||||
|
AquaticUdpRunner::new(20, 6),
|
||||||
|
AquaticUdpRunner::new(9, 7),
|
||||||
],
|
],
|
||||||
UdpTracker::OpenTracker => vec![
|
UdpTracker::OpenTracker => vec![
|
||||||
OpenTrackerUdpRunner::new(16),
|
OpenTrackerUdpRunner::new(16),
|
||||||
OpenTrackerUdpRunner::new(32),
|
OpenTrackerUdpRunner::new(32),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
load_test_runs: simple_load_test_runs(cpu_mode, &[8, 12, 16]),
|
load_test_runs: simple_load_test_runs(cpu_mode, &[8, 12, 16, 24]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,9 +163,8 @@ impl<C> RunConfig<C> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let avg_responses = {
|
let avg_responses = {
|
||||||
static RE: Lazy<Regex> = Lazy::new(|| {
|
static RE: Lazy<Regex> =
|
||||||
Regex::new(r"Average responses per second: ([0-9]+\.?[0-9]+)").unwrap()
|
Lazy::new(|| Regex::new(r"Average responses per second: ([0-9]+)").unwrap());
|
||||||
});
|
|
||||||
|
|
||||||
let opt_avg_responses = RE
|
let opt_avg_responses = RE
|
||||||
.captures_iter(&load_test_stdout)
|
.captures_iter(&load_test_stdout)
|
||||||
|
|
@ -175,7 +174,7 @@ impl<C> RunConfig<C> {
|
||||||
|
|
||||||
avg_responses.to_string()
|
avg_responses.to_string()
|
||||||
})
|
})
|
||||||
.and_then(|v| v.parse::<f32>().ok());
|
.and_then(|v| v.parse::<u64>().ok());
|
||||||
|
|
||||||
if let Some(avg_responses) = opt_avg_responses {
|
if let Some(avg_responses) = opt_avg_responses {
|
||||||
avg_responses
|
avg_responses
|
||||||
|
|
@ -199,7 +198,7 @@ impl<C> RunConfig<C> {
|
||||||
|
|
||||||
pub struct RunSuccessResults {
|
pub struct RunSuccessResults {
|
||||||
pub tracker_process_stats: ProcessStats,
|
pub tracker_process_stats: ProcessStats,
|
||||||
pub avg_responses: f32,
|
pub avg_responses: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -316,7 +315,7 @@ impl<C> std::fmt::Display for RunErrorResults<C> {
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ProcessStats {
|
pub struct ProcessStats {
|
||||||
pub avg_cpu_utilization: f32,
|
pub avg_cpu_utilization: f32,
|
||||||
pub peak_rss_kb: f32,
|
pub peak_rss_bytes: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ProcessStats {
|
impl FromStr for ProcessStats {
|
||||||
|
|
@ -325,9 +324,12 @@ impl FromStr for ProcessStats {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut parts = s.trim().split_whitespace();
|
let mut parts = s.trim().split_whitespace();
|
||||||
|
|
||||||
|
let avg_cpu_utilization = parts.next().ok_or(())?.parse().map_err(|_| ())?;
|
||||||
|
let peak_rss_kb: f32 = parts.next().ok_or(())?.parse().map_err(|_| ())?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
avg_cpu_utilization: parts.next().ok_or(())?.parse().map_err(|_| ())?,
|
avg_cpu_utilization,
|
||||||
peak_rss_kb: parts.next().ok_or(())?.parse().map_err(|_| ())?,
|
peak_rss_bytes: (peak_rss_kb * 1000.0) as u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use indexmap::{IndexMap, IndexSet};
|
use humanize_bytes::humanize_bytes_binary;
|
||||||
use itertools::Itertools;
|
use indexmap::IndexMap;
|
||||||
|
use num_format::{Locale, ToFormattedString};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{CpuDirection, CpuMode, TaskSetCpuList},
|
common::{CpuDirection, CpuMode, TaskSetCpuList},
|
||||||
|
html::{html_all_runs, html_best_results},
|
||||||
run::{ProcessRunner, ProcessStats, RunConfig},
|
run::{ProcessRunner, ProcessStats, RunConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -106,21 +108,22 @@ pub fn run_sets<C, F, I>(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
html_summary(&results);
|
println!("{}", html_all_runs(&results));
|
||||||
|
println!("{}", html_best_results(&results));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TrackerCoreCountResults {
|
pub struct TrackerCoreCountResults {
|
||||||
core_count: usize,
|
pub core_count: usize,
|
||||||
implementations: Vec<ImplementationResults>,
|
pub implementations: Vec<ImplementationResults>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ImplementationResults {
|
pub struct ImplementationResults {
|
||||||
name: String,
|
pub name: String,
|
||||||
configurations: Vec<TrackerConfigurationResults>,
|
pub configurations: Vec<TrackerConfigurationResults>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImplementationResults {
|
impl ImplementationResults {
|
||||||
fn best_result(&self) -> Option<LoadTestRunResultsSuccess> {
|
pub fn best_result(&self) -> Option<LoadTestRunResultsSuccess> {
|
||||||
self.configurations
|
self.configurations
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|c| c.best_result())
|
.filter_map(|c| c.best_result())
|
||||||
|
|
@ -135,7 +138,7 @@ impl ImplementationResults {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TrackerConfigurationResults {
|
pub struct TrackerConfigurationResults {
|
||||||
load_tests: Vec<LoadTestRunResults>,
|
pub load_tests: Vec<LoadTestRunResults>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrackerConfigurationResults {
|
impl TrackerConfigurationResults {
|
||||||
|
|
@ -185,40 +188,48 @@ impl LoadTestRunResults {
|
||||||
);
|
);
|
||||||
|
|
||||||
let load_test_runner = load_test_gen(workers);
|
let load_test_runner = load_test_gen(workers);
|
||||||
// let load_test_keys = load_test_runner.keys();
|
let load_test_keys = load_test_runner.keys();
|
||||||
|
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
tracker_runner: tracker_process.clone(),
|
tracker_runner: tracker_process.clone(),
|
||||||
tracker_vcpus: tracker_vcpus.clone(),
|
tracker_vcpus: tracker_vcpus.clone(),
|
||||||
load_test_runner,
|
load_test_runner,
|
||||||
load_test_vcpus,
|
load_test_vcpus: load_test_vcpus.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match run_config.run(command) {
|
match run_config.run(command) {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
println!("- Average responses per second: {}", r.avg_responses);
|
println!(
|
||||||
|
"- Average responses per second: {}",
|
||||||
|
r.avg_responses.to_formatted_string(&Locale::en)
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"- Average tracker CPU utilization: {}%",
|
"- Average tracker CPU utilization: {}%",
|
||||||
r.tracker_process_stats.avg_cpu_utilization,
|
r.tracker_process_stats.avg_cpu_utilization,
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"- Peak tracker RSS: {} kB",
|
"- Peak tracker RSS: {}",
|
||||||
r.tracker_process_stats.peak_rss_kb
|
humanize_bytes_binary!(r.tracker_process_stats.peak_rss_bytes)
|
||||||
);
|
);
|
||||||
|
|
||||||
LoadTestRunResults::Success(LoadTestRunResultsSuccess {
|
LoadTestRunResults::Success(LoadTestRunResultsSuccess {
|
||||||
average_responses: r.avg_responses,
|
average_responses: r.avg_responses,
|
||||||
// tracker_keys: tracker_process.keys(),
|
tracker_keys: tracker_process.keys(),
|
||||||
tracker_info: tracker_process.info(),
|
tracker_info: tracker_process.info(),
|
||||||
tracker_process_stats: r.tracker_process_stats,
|
tracker_process_stats: r.tracker_process_stats,
|
||||||
// load_test_keys,
|
tracker_vcpus,
|
||||||
|
load_test_keys,
|
||||||
|
load_test_vcpus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(results) => {
|
Err(results) => {
|
||||||
println!("\nRun failed:\n{:#}\n", results);
|
println!("\nRun failed:\n{:#}\n", results);
|
||||||
|
|
||||||
LoadTestRunResults::Failure(LoadTestRunResultsFailure {
|
LoadTestRunResults::Failure(LoadTestRunResultsFailure {
|
||||||
// load_test_keys
|
tracker_keys: tracker_process.keys(),
|
||||||
|
tracker_vcpus,
|
||||||
|
load_test_keys,
|
||||||
|
load_test_vcpus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -227,89 +238,18 @@ impl LoadTestRunResults {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LoadTestRunResultsSuccess {
|
pub struct LoadTestRunResultsSuccess {
|
||||||
average_responses: f32,
|
pub average_responses: u64,
|
||||||
// tracker_keys: IndexMap<String, String>,
|
pub tracker_keys: IndexMap<String, String>,
|
||||||
tracker_info: String,
|
pub tracker_info: String,
|
||||||
tracker_process_stats: ProcessStats,
|
pub tracker_process_stats: ProcessStats,
|
||||||
// load_test_keys: IndexMap<String, String>,
|
pub tracker_vcpus: TaskSetCpuList,
|
||||||
|
pub load_test_keys: IndexMap<String, String>,
|
||||||
|
pub load_test_vcpus: TaskSetCpuList,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LoadTestRunResultsFailure {
|
pub struct LoadTestRunResultsFailure {
|
||||||
// load_test_keys: IndexMap<String, String>,
|
pub tracker_keys: IndexMap<String, String>,
|
||||||
}
|
pub tracker_vcpus: TaskSetCpuList,
|
||||||
|
pub load_test_keys: IndexMap<String, String>,
|
||||||
pub fn html_summary(results: &[TrackerCoreCountResults]) {
|
pub load_test_vcpus: TaskSetCpuList,
|
||||||
let mut all_implementation_names = IndexSet::new();
|
|
||||||
|
|
||||||
for core_count_results in results {
|
|
||||||
all_implementation_names.extend(
|
|
||||||
core_count_results
|
|
||||||
.implementations
|
|
||||||
.iter()
|
|
||||||
.map(|r| r.name.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut data_rows = Vec::new();
|
|
||||||
|
|
||||||
for core_count_results in results {
|
|
||||||
let best_results = core_count_results
|
|
||||||
.implementations
|
|
||||||
.iter()
|
|
||||||
.map(|implementation| (implementation.name.clone(), implementation.best_result()))
|
|
||||||
.collect::<IndexMap<_, _>>();
|
|
||||||
|
|
||||||
let best_results_for_all_implementations = all_implementation_names
|
|
||||||
.iter()
|
|
||||||
.map(|name| best_results.get(name).cloned().flatten())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let data_row = format!(
|
|
||||||
"
|
|
||||||
<tr>
|
|
||||||
<th>{}</th>
|
|
||||||
{}
|
|
||||||
</tr>
|
|
||||||
",
|
|
||||||
core_count_results.core_count,
|
|
||||||
best_results_for_all_implementations
|
|
||||||
.into_iter()
|
|
||||||
.map(|result| {
|
|
||||||
if let Some(r) = result {
|
|
||||||
format!(
|
|
||||||
r#"<td><span title="{}, avg cpu utilization: {}%">{}</span></td>"#,
|
|
||||||
r.tracker_info,
|
|
||||||
r.tracker_process_stats.avg_cpu_utilization,
|
|
||||||
r.average_responses,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
"<td>-</td>".to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join("\n"),
|
|
||||||
);
|
|
||||||
|
|
||||||
data_rows.push(data_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>CPU cores</th>
|
|
||||||
{}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
",
|
|
||||||
all_implementation_names
|
|
||||||
.iter()
|
|
||||||
.map(|name| format!("<th>{name}</th>"))
|
|
||||||
.join("\n"),
|
|
||||||
data_rows.join("\n")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,13 +171,17 @@ impl ConnectedRequestSender {
|
||||||
|
|
||||||
pub struct ConnectedResponseSender {
|
pub struct ConnectedResponseSender {
|
||||||
senders: Vec<thingbuf::mpsc::blocking::Sender<ConnectedResponseWithAddr, Recycler>>,
|
senders: Vec<thingbuf::mpsc::blocking::Sender<ConnectedResponseWithAddr, Recycler>>,
|
||||||
|
to_any_last_index_picked: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectedResponseSender {
|
impl ConnectedResponseSender {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
senders: Vec<thingbuf::mpsc::blocking::Sender<ConnectedResponseWithAddr, Recycler>>,
|
senders: Vec<thingbuf::mpsc::blocking::Sender<ConnectedResponseWithAddr, Recycler>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { senders }
|
Self {
|
||||||
|
senders,
|
||||||
|
to_any_last_index_picked: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_send_ref_to(
|
pub fn try_send_ref_to(
|
||||||
|
|
@ -193,6 +197,23 @@ impl ConnectedResponseSender {
|
||||||
) -> Result<SendRef<ConnectedResponseWithAddr>, thingbuf::mpsc::errors::Closed> {
|
) -> Result<SendRef<ConnectedResponseWithAddr>, thingbuf::mpsc::errors::Closed> {
|
||||||
self.senders[index.0].send_ref()
|
self.senders[index.0].send_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_ref_to_any(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<SendRef<ConnectedResponseWithAddr>, thingbuf::mpsc::errors::Closed> {
|
||||||
|
let start = self.to_any_last_index_picked + 1;
|
||||||
|
|
||||||
|
for i in (start..start + self.senders.len()).map(|i| i % self.senders.len()) {
|
||||||
|
if let Ok(sender) = self.senders[i].try_send_ref() {
|
||||||
|
self.to_any_last_index_picked = i;
|
||||||
|
|
||||||
|
return Ok(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.to_any_last_index_picked = start % self.senders.len();
|
||||||
|
self.send_ref_to(SocketWorkerIndex(self.to_any_last_index_picked))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ConnectedResponseReceiver =
|
pub type ConnectedResponseReceiver =
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub fn run_swarm_worker(
|
||||||
state: State,
|
state: State,
|
||||||
server_start_instant: ServerStartInstant,
|
server_start_instant: ServerStartInstant,
|
||||||
request_receiver: Receiver<(SocketWorkerIndex, ConnectedRequest, CanonicalSocketAddr)>,
|
request_receiver: Receiver<(SocketWorkerIndex, ConnectedRequest, CanonicalSocketAddr)>,
|
||||||
response_sender: ConnectedResponseSender,
|
mut response_sender: ConnectedResponseSender,
|
||||||
statistics_sender: Sender<StatisticsMessage>,
|
statistics_sender: Sender<StatisticsMessage>,
|
||||||
worker_index: SwarmWorkerIndex,
|
worker_index: SwarmWorkerIndex,
|
||||||
) {
|
) {
|
||||||
|
|
@ -43,14 +43,16 @@ pub fn run_swarm_worker(
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Ok((sender_index, request, src)) = request_receiver.recv_timeout(timeout) {
|
if let Ok((sender_index, request, src)) = request_receiver.recv_timeout(timeout) {
|
||||||
// It is OK to block here as long as we don't do blocking sends
|
// It is OK to block here as long as we don't also do blocking
|
||||||
// in socket workers, which could cause a deadlock
|
// sends in socket workers (doing both could cause a deadlock)
|
||||||
match response_sender.send_ref_to(sender_index) {
|
|
||||||
Ok(mut send_ref) => {
|
|
||||||
send_ref.addr = src;
|
|
||||||
|
|
||||||
match (request, src.get().ip()) {
|
match (request, src.get().ip()) {
|
||||||
(ConnectedRequest::Announce(request), IpAddr::V4(ip)) => {
|
(ConnectedRequest::Announce(request), IpAddr::V4(ip)) => {
|
||||||
|
// It doesn't matter which socket worker receives announce responses
|
||||||
|
let mut send_ref = response_sender
|
||||||
|
.send_ref_to_any()
|
||||||
|
.expect("swarm response channel is closed");
|
||||||
|
|
||||||
|
send_ref.addr = src;
|
||||||
send_ref.kind = ConnectedResponseKind::AnnounceIpv4;
|
send_ref.kind = ConnectedResponseKind::AnnounceIpv4;
|
||||||
|
|
||||||
torrents
|
torrents
|
||||||
|
|
@ -69,6 +71,12 @@ pub fn run_swarm_worker(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(ConnectedRequest::Announce(request), IpAddr::V6(ip)) => {
|
(ConnectedRequest::Announce(request), IpAddr::V6(ip)) => {
|
||||||
|
// It doesn't matter which socket worker receives announce responses
|
||||||
|
let mut send_ref = response_sender
|
||||||
|
.send_ref_to_any()
|
||||||
|
.expect("swarm response channel is closed");
|
||||||
|
|
||||||
|
send_ref.addr = src;
|
||||||
send_ref.kind = ConnectedResponseKind::AnnounceIpv6;
|
send_ref.kind = ConnectedResponseKind::AnnounceIpv6;
|
||||||
|
|
||||||
torrents
|
torrents
|
||||||
|
|
@ -87,22 +95,27 @@ pub fn run_swarm_worker(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(ConnectedRequest::Scrape(request), IpAddr::V4(_)) => {
|
(ConnectedRequest::Scrape(request), IpAddr::V4(_)) => {
|
||||||
|
let mut send_ref = response_sender
|
||||||
|
.send_ref_to(sender_index)
|
||||||
|
.expect("swarm response channel is closed");
|
||||||
|
|
||||||
|
send_ref.addr = src;
|
||||||
send_ref.kind = ConnectedResponseKind::Scrape;
|
send_ref.kind = ConnectedResponseKind::Scrape;
|
||||||
|
|
||||||
torrents.ipv4.scrape(request, &mut send_ref.scrape);
|
torrents.ipv4.scrape(request, &mut send_ref.scrape);
|
||||||
}
|
}
|
||||||
(ConnectedRequest::Scrape(request), IpAddr::V6(_)) => {
|
(ConnectedRequest::Scrape(request), IpAddr::V6(_)) => {
|
||||||
|
let mut send_ref = response_sender
|
||||||
|
.send_ref_to(sender_index)
|
||||||
|
.expect("swarm response channel is closed");
|
||||||
|
|
||||||
|
send_ref.addr = src;
|
||||||
send_ref.kind = ConnectedResponseKind::Scrape;
|
send_ref.kind = ConnectedResponseKind::Scrape;
|
||||||
|
|
||||||
torrents.ipv6.scrape(request, &mut send_ref.scrape);
|
torrents.ipv6.scrape(request, &mut send_ref.scrape);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Err(_) => {
|
|
||||||
panic!("swarm response channel closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run periodic tasks
|
// Run periodic tasks
|
||||||
if iter_counter % 128 == 0 {
|
if iter_counter % 128 == 0 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue