]>
Commit | Line | Data |
---|---|---|
caea8d61 DM |
1 | use std::path::PathBuf; |
2 | use std::sync::Arc; | |
3 | ||
4 | use anyhow::{Error}; | |
5 | use serde_json::Value; | |
dde18bbb | 6 | use serde::Serialize; |
caea8d61 DM |
7 | |
8 | use proxmox::api::{ApiMethod, RpcEnvironment}; | |
dde18bbb DM |
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 | }, | |
b2362a12 | 18 | router::ReturnType, |
a37c8d24 | 19 | schema::ApiType, |
dde18bbb | 20 | }; |
caea8d61 | 21 | |
2b7f8dd5 WB |
22 | use pbs_client::tools::key_source::get_encryption_key_password; |
23 | use pbs_client::{BackupRepository, BackupWriter}; | |
0889806a | 24 | use pbs_datastore::{CryptConfig, KeyDerivationConfig, load_and_decrypt_key}; |
ed208076 | 25 | use pbs_datastore::data_blob::{DataBlob, DataChunkBuilder}; |
caea8d61 | 26 | |
caea8d61 DM |
27 | use 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 | |
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 | }, | |
baae780c DM |
62 | "verify": { |
63 | type: Speed, | |
64 | }, | |
dde18bbb DM |
65 | }, |
66 | )] | |
67 | #[derive(Copy, Clone, Serialize)] | |
68 | /// Benchmark Results | |
69 | struct 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 |
84 | static 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 | |
135 | pub 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(¶m).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(¶m); |
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 | |
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)?; | |
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 | ||
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> { | |
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 |
252 | fn 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 | } |