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