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