]> git.proxmox.com Git - proxmox-backup.git/blame - 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
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
bbdda58b
DM
22use pbs_tools::crypt_config::CryptConfig;
23use pbs_config::key_config::{KeyDerivationConfig, load_and_decrypt_key};
2b7f8dd5
WB
24use pbs_client::tools::key_source::get_encryption_key_password;
25use pbs_client::{BackupRepository, BackupWriter};
ed208076 26use pbs_datastore::data_blob::{DataBlob, DataChunkBuilder};
caea8d61 27
caea8d61
DM
28use crate::{
29 KEYFILE_SCHEMA, REPO_URL_SCHEMA,
30 extract_repository_from_value,
caea8d61
DM
31 record_repository,
32 connect,
33};
34
dde18bbb
DM
35#[api()]
36#[derive(Copy, Clone, Serialize)]
37/// Speed test result
38struct 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 },
baae780c
DM
63 "verify": {
64 type: Speed,
65 },
dde18bbb
DM
66 },
67)]
68#[derive(Copy, Clone, Serialize)]
69/// Benchmark Results
70struct BenchmarkResult {
71 /// TLS upload speed
72 tls: Speed,
3435f549 73 /// SHA256 checksum computation speed
dde18bbb
DM
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,
baae780c
DM
81 /// Verify speed
82 verify: Speed,
dde18bbb
DM
83}
84
dde18bbb
DM
85static BENCHMARK_RESULT_2020_TOP: BenchmarkResult = BenchmarkResult {
86 tls: Speed {
87 speed: None,
62c74d77 88 top: 1_000_000.0 * 1235.0, // TLS to localhost, AMD Ryzen 7 2700X
dde18bbb
DM
89 },
90 sha256: Speed {
91 speed: None,
baae780c 92 top: 1_000_000.0 * 2022.0, // AMD Ryzen 7 2700X
dde18bbb
DM
93 },
94 compress: Speed {
95 speed: None,
baae780c 96 top: 1_000_000.0 * 752.0, // AMD Ryzen 7 2700X
dde18bbb
DM
97 },
98 decompress: Speed {
99 speed: None,
baae780c 100 top: 1_000_000.0 * 1198.0, // AMD Ryzen 7 2700X
dde18bbb
DM
101 },
102 aes256_gcm: Speed {
103 speed: None,
baae780c
DM
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
dde18bbb
DM
109 },
110};
111
caea8d61
DM
112#[api(
113 input: {
114 properties: {
115 repository: {
116 schema: REPO_URL_SCHEMA,
117 optional: true,
118 },
323b2f3d
DM
119 verbose: {
120 description: "Verbose output.",
121 type: bool,
122 optional: true,
123 },
caea8d61
DM
124 keyfile: {
125 schema: KEYFILE_SCHEMA,
126 optional: true,
127 },
dde18bbb
DM
128 "output-format": {
129 schema: OUTPUT_FORMAT,
130 optional: true,
131 },
caea8d61
DM
132 }
133 }
134)]
135/// Run benchmark tests
136pub async fn benchmark(
137 param: Value,
138 _info: &ApiMethod,
139 _rpcenv: &mut dyn RpcEnvironment,
140) -> Result<(), Error> {
141
dde18bbb 142 let repo = extract_repository_from_value(&param).ok();
caea8d61
DM
143
144 let keyfile = param["keyfile"].as_str().map(PathBuf::from);
145
323b2f3d
DM
146 let verbose = param["verbose"].as_bool().unwrap_or(false);
147
dde18bbb
DM
148 let output_format = get_output_format(&param);
149
caea8d61
DM
150 let crypt_config = match keyfile {
151 None => None,
152 Some(path) => {
ff8945fd 153 let (key, _, _) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
caea8d61
DM
154 let crypt_config = CryptConfig::new(key)?;
155 Some(Arc::new(crypt_config))
156 }
157 };
158
dde18bbb
DM
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
174fn render_result(
175 output_format: &str,
176 benchmark_result: &BenchmarkResult,
177) -> Result<(), Error> {
178
179 let mut data = serde_json::to_value(benchmark_result)?;
b2362a12 180 let return_type = ReturnType::new(false, &BenchmarkResult::API_SCHEMA);
dde18bbb
DM
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")
3435f549 197 .header("SHA256 checksum computation speed")
dde18bbb
DM
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))
baae780c
DM
205 .column(ColumnConfig::new("verify")
206 .header("Chunk verification speed")
207 .right_align(false).renderer(render_speed))
208 .column(ColumnConfig::new("aes256_gcm")
dde18bbb
DM
209 .header("AES256 GCM encryption speed")
210 .right_align(false).renderer(render_speed));
211
212
b2362a12 213 format_and_print_result_full(&mut data, &return_type, output_format, &options);
dde18bbb
DM
214
215 Ok(())
216}
217
218async fn test_upload_speed(
219 benchmark_result: &mut BenchmarkResult,
220 repo: BackupRepository,
221 crypt_config: Option<Arc<CryptConfig>>,
222 verbose: bool,
223) -> Result<(), Error> {
4327a846 224
6a7be83e 225 let backup_time = proxmox::tools::time::epoch_i64();
caea8d61 226
f3fde36b 227 let client = connect(&repo)?;
caea8d61
DM
228 record_repository(&repo);
229
dde18bbb 230 if verbose { eprintln!("Connecting to backup server"); }
caea8d61
DM
231 let client = BackupWriter::start(
232 client,
233 crypt_config.clone(),
234 repo.store(),
235 "host",
323b2f3d 236 "benchmark",
caea8d61
DM
237 backup_time,
238 false,
61d7b501 239 true
caea8d61
DM
240 ).await?;
241
dde18bbb 242 if verbose { eprintln!("Start TLS speed test"); }
323b2f3d 243 let speed = client.upload_speedtest(verbose).await?;
caea8d61 244
dde18bbb
DM
245 eprintln!("TLS speed: {:.2} MB/s", speed/1_000_000.0);
246
247 benchmark_result.tls.speed = Some(speed);
caea8d61
DM
248
249 Ok(())
250}
4327a846 251
dde18bbb
DM
252// test hash/crypt/compress speed
253fn test_crypt_speed(
254 benchmark_result: &mut BenchmarkResult,
255 _verbose: bool,
256) -> Result<(), Error> {
4327a846
DM
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
baae780c
DM
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);
4327a846
DM
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();
dde18bbb 292 benchmark_result.sha256.speed = Some(speed);
4327a846 293
ea368a06 294 eprintln!("SHA256 speed: {:.2} MB/s", speed/1_000_000.0);
4327a846
DM
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();
dde18bbb 304 if start_time.elapsed().as_micros() > 3_000_000 { break; }
4327a846
DM
305 }
306 let speed = (bytes as f64)/start_time.elapsed().as_secs_f64();
dde18bbb 307 benchmark_result.compress.speed = Some(speed);
4327a846 308
ea368a06 309 eprintln!("Compression speed: {:.2} MB/s", speed/1_000_000.0);
4327a846
DM
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();
dde18bbb 327 benchmark_result.decompress.speed = Some(speed);
4327a846 328
ea368a06 329 eprintln!("Decompress speed: {:.2} MB/s", speed/1_000_000.0);
4327a846
DM
330
331
332 let start_time = std::time::Instant::now();
333
334 let mut bytes = 0;
335 loop {
336 let mut out = Vec::new();
ed208076 337 DataBlob::encrypt_benchmark(&crypt_config, &random_data, &mut out)?;
4327a846
DM
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();
dde18bbb 342 benchmark_result.aes256_gcm.speed = Some(speed);
4327a846 343
ea368a06 344 eprintln!("AES256/GCM speed: {:.2} MB/s", speed/1_000_000.0);
4327a846 345
baae780c
DM
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
ea368a06 362 eprintln!("Verify speed: {:.2} MB/s", speed/1_000_000.0);
baae780c 363
4327a846
DM
364 Ok(())
365}