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