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