]> git.proxmox.com Git - proxmox-backup.git/blame - proxmox-backup-client/src/benchmark.rs
client: backup writer: only borrow http client
[proxmox-backup.git] / proxmox-backup-client / src / benchmark.rs
CommitLineData
caea8d61
DM
1use std::path::PathBuf;
2use std::sync::Arc;
3
f9a5beaa 4use anyhow::Error;
dde18bbb 5use serde::Serialize;
f9a5beaa 6use serde_json::Value;
caea8d61 7
6ef1b649 8use proxmox_router::{
dde18bbb 9 cli::{
f9a5beaa
TL
10 default_table_format_options, format_and_print_result_full, get_output_format,
11 ColumnConfig, OUTPUT_FORMAT,
dde18bbb 12 },
f9a5beaa 13 ApiMethod, RpcEnvironment,
dde18bbb 14};
f9a5beaa 15use proxmox_schema::{api, ApiType, ReturnType};
caea8d61 16
8c74349b 17use pbs_api_types::{BackupNamespace, BackupType};
2b7f8dd5
WB
18use pbs_client::tools::key_source::get_encryption_key_password;
19use pbs_client::{BackupRepository, BackupWriter};
ed208076 20use pbs_datastore::data_blob::{DataBlob, DataChunkBuilder};
1104d2a2 21use pbs_key_config::{load_and_decrypt_key, KeyDerivationConfig};
f9a5beaa 22use pbs_tools::crypt_config::CryptConfig;
caea8d61 23
caea8d61 24use crate::{
f9a5beaa 25 connect, extract_repository_from_value, record_repository, KEYFILE_SCHEMA, REPO_URL_SCHEMA,
caea8d61
DM
26};
27
dde18bbb
DM
28#[api()]
29#[derive(Copy, Clone, Serialize)]
30/// Speed test result
31struct Speed {
32 /// The meassured speed in Bytes/second
f9a5beaa 33 #[serde(skip_serializing_if = "Option::is_none")]
dde18bbb
DM
34 speed: Option<f64>,
35 /// Top result we want to compare with
36 top: f64,
37}
38
39#[api(
40 properties: {
41 "tls": {
42 type: Speed,
43 },
44 "sha256": {
45 type: Speed,
46 },
47 "compress": {
48 type: Speed,
49 },
50 "decompress": {
51 type: Speed,
52 },
53 "aes256_gcm": {
54 type: Speed,
55 },
baae780c
DM
56 "verify": {
57 type: Speed,
58 },
dde18bbb
DM
59 },
60)]
61#[derive(Copy, Clone, Serialize)]
62/// Benchmark Results
63struct BenchmarkResult {
64 /// TLS upload speed
65 tls: Speed,
3435f549 66 /// SHA256 checksum computation speed
dde18bbb
DM
67 sha256: Speed,
68 /// ZStd level 1 compression speed
69 compress: Speed,
70 /// ZStd level 1 decompression speed
71 decompress: Speed,
72 /// AES256 GCM encryption speed
73 aes256_gcm: Speed,
baae780c
DM
74 /// Verify speed
75 verify: Speed,
dde18bbb
DM
76}
77
f9a5beaa 78static BENCHMARK_RESULT_2020_TOP: BenchmarkResult = BenchmarkResult {
dde18bbb
DM
79 tls: Speed {
80 speed: None,
62c74d77 81 top: 1_000_000.0 * 1235.0, // TLS to localhost, AMD Ryzen 7 2700X
dde18bbb
DM
82 },
83 sha256: Speed {
84 speed: None,
baae780c 85 top: 1_000_000.0 * 2022.0, // AMD Ryzen 7 2700X
dde18bbb
DM
86 },
87 compress: Speed {
88 speed: None,
baae780c 89 top: 1_000_000.0 * 752.0, // AMD Ryzen 7 2700X
dde18bbb
DM
90 },
91 decompress: Speed {
92 speed: None,
baae780c 93 top: 1_000_000.0 * 1198.0, // AMD Ryzen 7 2700X
dde18bbb
DM
94 },
95 aes256_gcm: Speed {
96 speed: None,
baae780c
DM
97 top: 1_000_000.0 * 3645.0, // AMD Ryzen 7 2700X
98 },
99 verify: Speed {
100 speed: None,
101 top: 1_000_000.0 * 758.0, // AMD Ryzen 7 2700X
dde18bbb
DM
102 },
103};
104
caea8d61
DM
105#[api(
106 input: {
107 properties: {
108 repository: {
109 schema: REPO_URL_SCHEMA,
110 optional: true,
111 },
112 keyfile: {
113 schema: KEYFILE_SCHEMA,
114 optional: true,
115 },
dde18bbb
DM
116 "output-format": {
117 schema: OUTPUT_FORMAT,
118 optional: true,
119 },
caea8d61
DM
120 }
121 }
122)]
123/// Run benchmark tests
124pub async fn benchmark(
125 param: Value,
126 _info: &ApiMethod,
127 _rpcenv: &mut dyn RpcEnvironment,
128) -> Result<(), Error> {
dde18bbb 129 let repo = extract_repository_from_value(&param).ok();
caea8d61
DM
130
131 let keyfile = param["keyfile"].as_str().map(PathBuf::from);
132
dde18bbb
DM
133 let output_format = get_output_format(&param);
134
caea8d61
DM
135 let crypt_config = match keyfile {
136 None => None,
137 Some(path) => {
ff8945fd 138 let (key, _, _) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
caea8d61
DM
139 let crypt_config = CryptConfig::new(key)?;
140 Some(Arc::new(crypt_config))
141 }
142 };
143
dde18bbb
DM
144 let mut benchmark_result = BENCHMARK_RESULT_2020_TOP;
145
146 // do repo tests first, because this may prompt for a password
147 if let Some(repo) = repo {
4a2e4467 148 test_upload_speed(&mut benchmark_result, repo, crypt_config.clone()).await?;
dde18bbb
DM
149 }
150
4a2e4467 151 test_crypt_speed(&mut benchmark_result)?;
dde18bbb
DM
152
153 render_result(&output_format, &benchmark_result)?;
154
155 Ok(())
156}
157
158// print comparison table
f9a5beaa 159fn render_result(output_format: &str, benchmark_result: &BenchmarkResult) -> Result<(), Error> {
dde18bbb 160 let mut data = serde_json::to_value(benchmark_result)?;
b2362a12 161 let return_type = ReturnType::new(false, &BenchmarkResult::API_SCHEMA);
dde18bbb
DM
162
163 let render_speed = |value: &Value, _record: &Value| -> Result<String, Error> {
164 match value["speed"].as_f64() {
165 None => Ok(String::from("not tested")),
166 Some(speed) => {
167 let top = value["top"].as_f64().unwrap();
f9a5beaa
TL
168 Ok(format!(
169 "{:.2} MB/s ({:.0}%)",
170 speed / 1_000_000.0,
171 (speed * 100.0) / top
172 ))
dde18bbb
DM
173 }
174 }
175 };
176
177 let options = default_table_format_options()
f9a5beaa
TL
178 .column(
179 ColumnConfig::new("tls")
dde18bbb 180 .header("TLS (maximal backup upload speed)")
f9a5beaa
TL
181 .right_align(false)
182 .renderer(render_speed),
183 )
184 .column(
185 ColumnConfig::new("sha256")
3435f549 186 .header("SHA256 checksum computation speed")
f9a5beaa
TL
187 .right_align(false)
188 .renderer(render_speed),
189 )
190 .column(
191 ColumnConfig::new("compress")
dde18bbb 192 .header("ZStd level 1 compression speed")
f9a5beaa
TL
193 .right_align(false)
194 .renderer(render_speed),
195 )
196 .column(
197 ColumnConfig::new("decompress")
dde18bbb 198 .header("ZStd level 1 decompression speed")
f9a5beaa
TL
199 .right_align(false)
200 .renderer(render_speed),
201 )
202 .column(
203 ColumnConfig::new("verify")
baae780c 204 .header("Chunk verification speed")
f9a5beaa
TL
205 .right_align(false)
206 .renderer(render_speed),
207 )
208 .column(
209 ColumnConfig::new("aes256_gcm")
dde18bbb 210 .header("AES256 GCM encryption speed")
f9a5beaa
TL
211 .right_align(false)
212 .renderer(render_speed),
213 );
dde18bbb 214
b2362a12 215 format_and_print_result_full(&mut data, &return_type, output_format, &options);
dde18bbb
DM
216
217 Ok(())
218}
219
220async fn test_upload_speed(
221 benchmark_result: &mut BenchmarkResult,
222 repo: BackupRepository,
223 crypt_config: Option<Arc<CryptConfig>>,
dde18bbb 224) -> Result<(), Error> {
6ef1b649 225 let backup_time = proxmox_time::epoch_i64();
caea8d61 226
f3fde36b 227 let client = connect(&repo)?;
caea8d61
DM
228 record_repository(&repo);
229
4a2e4467 230 log::debug!("Connecting to backup server");
caea8d61 231 let client = BackupWriter::start(
fcea0794 232 &client,
caea8d61
DM
233 crypt_config.clone(),
234 repo.store(),
133d718f
WB
235 &BackupNamespace::root(),
236 &(BackupType::Host, "benchmark".to_string(), backup_time).into(),
caea8d61 237 false,
f9a5beaa
TL
238 true,
239 )
240 .await?;
caea8d61 241
4a2e4467
HL
242 log::debug!("Start TLS speed test");
243 let speed = client.upload_speedtest().await?;
caea8d61 244
4a2e4467 245 log::info!("TLS speed: {:.2} MB/s", speed / 1_000_000.0);
dde18bbb
DM
246
247 benchmark_result.tls.speed = Some(speed);
caea8d61
DM
248
249 Ok(())
250}
4327a846 251
dde18bbb 252// test hash/crypt/compress speed
4a2e4467 253fn test_crypt_speed(benchmark_result: &mut BenchmarkResult) -> Result<(), Error> {
4327a846
DM
254 let pw = b"test";
255
256 let kdf = KeyDerivationConfig::Scrypt {
257 n: 65536,
258 r: 8,
259 p: 1,
260 salt: Vec::new(),
261 };
262
263 let testkey = kdf.derive_key(pw)?;
264
265 let crypt_config = CryptConfig::new(testkey)?;
266
25877d05 267 //let random_data = proxmox_sys::linux::random_data(1024*1024)?;
baae780c 268 let mut random_data = vec![];
f9a5beaa
TL
269 // generate pseudo random byte sequence
270 for i in 0..256 * 1024 {
271 for j in 0..4 {
272 let byte = ((i >> (j << 3)) & 0xff) as u8;
273 random_data.push(byte);
baae780c 274 }
f9a5beaa 275 }
baae780c 276
f9a5beaa 277 assert_eq!(random_data.len(), 1024 * 1024);
4327a846
DM
278
279 let start_time = std::time::Instant::now();
280
281 let mut bytes = 0;
f9a5beaa 282 loop {
4327a846
DM
283 openssl::sha::sha256(&random_data);
284 bytes += random_data.len();
f9a5beaa
TL
285 if start_time.elapsed().as_micros() > 1_000_000 {
286 break;
287 }
4327a846 288 }
f9a5beaa 289 let speed = (bytes as f64) / start_time.elapsed().as_secs_f64();
dde18bbb 290 benchmark_result.sha256.speed = Some(speed);
4327a846 291
4a2e4467 292 log::info!("SHA256 speed: {:.2} MB/s", speed / 1_000_000.0);
4327a846
DM
293
294 let start_time = std::time::Instant::now();
295
296 let mut bytes = 0;
f9a5beaa 297 loop {
4327a846
DM
298 let mut reader = &random_data[..];
299 zstd::stream::encode_all(&mut reader, 1)?;
300 bytes += random_data.len();
f9a5beaa
TL
301 if start_time.elapsed().as_micros() > 3_000_000 {
302 break;
303 }
4327a846 304 }
f9a5beaa 305 let speed = (bytes as f64) / start_time.elapsed().as_secs_f64();
dde18bbb 306 benchmark_result.compress.speed = Some(speed);
4327a846 307
4a2e4467 308 log::info!("Compression speed: {:.2} MB/s", speed / 1_000_000.0);
4327a846
DM
309
310 let start_time = std::time::Instant::now();
311
312 let compressed_data = {
313 let mut reader = &random_data[..];
314 zstd::stream::encode_all(&mut reader, 1)?
315 };
316
317 let mut bytes = 0;
f9a5beaa 318 loop {
4327a846
DM
319 let mut reader = &compressed_data[..];
320 let data = zstd::stream::decode_all(&mut reader)?;
321 bytes += data.len();
f9a5beaa
TL
322 if start_time.elapsed().as_micros() > 1_000_000 {
323 break;
324 }
4327a846 325 }
f9a5beaa 326 let speed = (bytes as f64) / start_time.elapsed().as_secs_f64();
dde18bbb 327 benchmark_result.decompress.speed = Some(speed);
4327a846 328
4a2e4467 329 log::info!("Decompress speed: {:.2} MB/s", speed / 1_000_000.0);
4327a846
DM
330
331 let start_time = std::time::Instant::now();
332
333 let mut bytes = 0;
f9a5beaa 334 loop {
4327a846 335 let mut out = Vec::new();
ed208076 336 DataBlob::encrypt_benchmark(&crypt_config, &random_data, &mut out)?;
4327a846 337 bytes += random_data.len();
f9a5beaa
TL
338 if start_time.elapsed().as_micros() > 1_000_000 {
339 break;
340 }
4327a846 341 }
f9a5beaa 342 let speed = (bytes as f64) / start_time.elapsed().as_secs_f64();
dde18bbb 343 benchmark_result.aes256_gcm.speed = Some(speed);
4327a846 344
4a2e4467 345 log::info!("AES256/GCM speed: {:.2} MB/s", speed / 1_000_000.0);
baae780c
DM
346
347 let start_time = std::time::Instant::now();
348
f9a5beaa 349 let (chunk, digest) = DataChunkBuilder::new(&random_data).compress(true).build()?;
baae780c
DM
350
351 let mut bytes = 0;
f9a5beaa 352 loop {
baae780c
DM
353 chunk.verify_unencrypted(random_data.len(), &digest)?;
354 bytes += random_data.len();
f9a5beaa
TL
355 if start_time.elapsed().as_micros() > 1_000_000 {
356 break;
357 }
baae780c 358 }
f9a5beaa 359 let speed = (bytes as f64) / start_time.elapsed().as_secs_f64();
baae780c
DM
360 benchmark_result.verify.speed = Some(speed);
361
4a2e4467 362 log::info!("Verify speed: {:.2} MB/s", speed / 1_000_000.0);
baae780c 363
4327a846
DM
364 Ok(())
365}