]>
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 | |
bbdda58b DM |
22 | use pbs_tools::crypt_config::CryptConfig; |
23 | use pbs_config::key_config::{KeyDerivationConfig, load_and_decrypt_key}; | |
2b7f8dd5 WB |
24 | use pbs_client::tools::key_source::get_encryption_key_password; |
25 | use pbs_client::{BackupRepository, BackupWriter}; | |
ed208076 | 26 | use pbs_datastore::data_blob::{DataBlob, DataChunkBuilder}; |
caea8d61 | 27 | |
caea8d61 DM |
28 | use 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 | |
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 | }, | |
baae780c DM |
63 | "verify": { |
64 | type: Speed, | |
65 | }, | |
dde18bbb DM |
66 | }, |
67 | )] | |
68 | #[derive(Copy, Clone, Serialize)] | |
69 | /// Benchmark Results | |
70 | struct 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 |
85 | static 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 | |
136 | pub 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(¶m).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(¶m); |
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 | |
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)?; | |
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 | ||
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> { | |
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 |
253 | fn 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 | } |