1 use std
::path
::PathBuf
;
8 use proxmox
::api
::{ApiMethod, RpcEnvironment}
;
15 format_and_print_result_full
,
16 default_table_format_options
,
22 use pbs_client
::tools
::key_source
::get_encryption_key_password
;
23 use pbs_client
::{BackupRepository, BackupWriter}
;
24 use pbs_datastore
::{CryptConfig, KeyDerivationConfig, load_and_decrypt_key}
;
25 use pbs_datastore
::data_blob
::DataChunkBuilder
;
28 KEYFILE_SCHEMA
, REPO_URL_SCHEMA
,
29 extract_repository_from_value
,
35 #[derive(Copy, Clone, Serialize)]
38 /// The meassured speed in Bytes/second
39 #[serde(skip_serializing_if="Option::is_none")]
41 /// Top result we want to compare with
67 #[derive(Copy, Clone, Serialize)]
69 struct BenchmarkResult
{
72 /// SHA256 checksum computation speed
74 /// ZStd level 1 compression speed
76 /// ZStd level 1 decompression speed
78 /// AES256 GCM encryption speed
84 static BENCHMARK_RESULT_2020_TOP
: BenchmarkResult
= BenchmarkResult
{
87 top
: 1_000_000.0 * 1235.0, // TLS to localhost, AMD Ryzen 7 2700X
91 top
: 1_000_000.0 * 2022.0, // AMD Ryzen 7 2700X
95 top
: 1_000_000.0 * 752.0, // AMD Ryzen 7 2700X
99 top
: 1_000_000.0 * 1198.0, // AMD Ryzen 7 2700X
103 top
: 1_000_000.0 * 3645.0, // AMD Ryzen 7 2700X
107 top
: 1_000_000.0 * 758.0, // AMD Ryzen 7 2700X
115 schema
: REPO_URL_SCHEMA
,
119 description
: "Verbose output.",
124 schema
: KEYFILE_SCHEMA
,
128 schema
: OUTPUT_FORMAT
,
134 /// Run benchmark tests
135 pub async
fn benchmark(
138 _rpcenv
: &mut dyn RpcEnvironment
,
139 ) -> Result
<(), Error
> {
141 let repo
= extract_repository_from_value(¶m
).ok();
143 let keyfile
= param
["keyfile"].as_str().map(PathBuf
::from
);
145 let verbose
= param
["verbose"].as_bool().unwrap_or(false);
147 let output_format
= get_output_format(¶m
);
149 let crypt_config
= match keyfile
{
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
))
158 let mut benchmark_result
= BENCHMARK_RESULT_2020_TOP
;
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?
;
165 test_crypt_speed(&mut benchmark_result
, verbose
)?
;
167 render_result(&output_format
, &benchmark_result
)?
;
172 // print comparison table
175 benchmark_result
: &BenchmarkResult
,
176 ) -> Result
<(), Error
> {
178 let mut data
= serde_json
::to_value(benchmark_result
)?
;
179 let return_type
= ReturnType
::new(false, &BenchmarkResult
::API_SCHEMA
);
181 let render_speed
= |value
: &Value
, _record
: &Value
| -> Result
<String
, Error
> {
182 match value
["speed"].as_f64() {
183 None
=> Ok(String
::from("not tested")),
185 let top
= value
["top"].as_f64().unwrap();
186 Ok(format
!("{:.2} MB/s ({:.0}%)", speed
/1_000_000.0, (speed
*100.0)/top
))
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
));
212 format_and_print_result_full(&mut data
, &return_type
, output_format
, &options
);
217 async
fn test_upload_speed(
218 benchmark_result
: &mut BenchmarkResult
,
219 repo
: BackupRepository
,
220 crypt_config
: Option
<Arc
<CryptConfig
>>,
222 ) -> Result
<(), Error
> {
224 let backup_time
= proxmox
::tools
::time
::epoch_i64();
226 let client
= connect(&repo
)?
;
227 record_repository(&repo
);
229 if verbose { eprintln!("Connecting to backup server"); }
230 let client
= BackupWriter
::start(
232 crypt_config
.clone(),
241 if verbose { eprintln!("Start TLS speed test"); }
242 let speed
= client
.upload_speedtest(verbose
).await?
;
244 eprintln
!("TLS speed: {:.2} MB/s", speed
/1_000_000.0);
246 benchmark_result
.tls
.speed
= Some(speed
);
251 // test hash/crypt/compress speed
253 benchmark_result
: &mut BenchmarkResult
,
255 ) -> Result
<(), Error
> {
259 let kdf
= KeyDerivationConfig
::Scrypt
{
266 let testkey
= kdf
.derive_key(pw
)?
;
268 let crypt_config
= CryptConfig
::new(testkey
)?
;
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 {
275 let byte
= ((i
>> (j
<<3))&0xff) as u8;
276 random_data
.push(byte
);
280 assert_eq
!(random_data
.len(), 1024*1024);
282 let start_time
= std
::time
::Instant
::now();
286 openssl
::sha
::sha256(&random_data
);
287 bytes
+= random_data
.len();
288 if start_time
.elapsed().as_micros() > 1_000_000 { break; }
290 let speed
= (bytes
as f64)/start_time
.elapsed().as_secs_f64();
291 benchmark_result
.sha256
.speed
= Some(speed
);
293 eprintln
!("SHA256 speed: {:.2} MB/s", speed
/1_000_000.0);
296 let start_time
= std
::time
::Instant
::now();
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; }
305 let speed
= (bytes
as f64)/start_time
.elapsed().as_secs_f64();
306 benchmark_result
.compress
.speed
= Some(speed
);
308 eprintln
!("Compression speed: {:.2} MB/s", speed
/1_000_000.0);
311 let start_time
= std
::time
::Instant
::now();
313 let compressed_data
= {
314 let mut reader
= &random_data
[..];
315 zstd
::stream
::encode_all(&mut reader
, 1)?
320 let mut reader
= &compressed_data
[..];
321 let data
= zstd
::stream
::decode_all(&mut reader
)?
;
323 if start_time
.elapsed().as_micros() > 1_000_000 { break; }
325 let speed
= (bytes
as f64)/start_time
.elapsed().as_secs_f64();
326 benchmark_result
.decompress
.speed
= Some(speed
);
328 eprintln
!("Decompress speed: {:.2} MB/s", speed
/1_000_000.0);
331 let start_time
= std
::time
::Instant
::now();
335 let mut out
= Vec
::new();
336 crypt_config
.encrypt_to(&random_data
, &mut out
)?
;
337 bytes
+= random_data
.len();
338 if start_time
.elapsed().as_micros() > 1_000_000 { break; }
340 let speed
= (bytes
as f64)/start_time
.elapsed().as_secs_f64();
341 benchmark_result
.aes256_gcm
.speed
= Some(speed
);
343 eprintln
!("AES256/GCM speed: {:.2} MB/s", speed
/1_000_000.0);
346 let start_time
= std
::time
::Instant
::now();
348 let (chunk
, digest
) = DataChunkBuilder
::new(&random_data
)
354 chunk
.verify_unencrypted(random_data
.len(), &digest
)?
;
355 bytes
+= random_data
.len();
356 if start_time
.elapsed().as_micros() > 1_000_000 { break; }
358 let speed
= (bytes
as f64)/start_time
.elapsed().as_secs_f64();
359 benchmark_result
.verify
.speed
= Some(speed
);
361 eprintln
!("Verify speed: {:.2} MB/s", speed
/1_000_000.0);