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