Skip to content

Commit 88945b5

Browse files
committed
Add progress report to client snapshot download in json output
This is done using a new type, `DownloadProgressReporter`, that wrap a indicatif progress bar that give to use all the data that we need to print, thank you indicatif ! Those print are limited to one every 333ms.
1 parent bfe3867 commit 88945b5

File tree

6 files changed

+122
-30
lines changed

6 files changed

+122
-30
lines changed

mithril-client/src/aggregator_client/http_client.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
use std::{path::Path, sync::Arc};
2-
31
use async_recursion::async_recursion;
42
use async_trait::async_trait;
53
use futures::StreamExt;
6-
use indicatif::ProgressBar;
74
use reqwest::{Client, Response, StatusCode};
85
use semver::Version;
96
use slog_scope::debug;
7+
use std::{path::Path, sync::Arc};
108
use thiserror::Error;
119
use tokio::{fs, io::AsyncWriteExt, sync::RwLock};
1210

@@ -15,6 +13,8 @@ use mockall::automock;
1513

1614
use mithril_common::{StdError, MITHRIL_API_VERSION_HEADER};
1715

16+
use crate::utils::DownloadProgressReporter;
17+
1818
/// Error tied with the Aggregator client
1919
#[derive(Error, Debug)]
2020
pub enum AggregatorHTTPClientError {
@@ -56,7 +56,7 @@ pub trait AggregatorClient: Sync + Send {
5656
&self,
5757
url: &str,
5858
filepath: &Path,
59-
progress_bar: ProgressBar,
59+
progress_reporter: DownloadProgressReporter,
6060
) -> Result<(), AggregatorHTTPClientError>;
6161

6262
/// Test if the given URL points to a valid location & existing content.
@@ -183,7 +183,7 @@ impl AggregatorClient for AggregatorHTTPClient {
183183
&self,
184184
url: &str,
185185
filepath: &Path,
186-
progress_bar: ProgressBar,
186+
progress_reporter: DownloadProgressReporter,
187187
) -> Result<(), AggregatorHTTPClientError> {
188188
let response = self.get(url).await?;
189189
let mut local_file = fs::File::create(filepath).await.map_err(|e| {
@@ -215,7 +215,7 @@ impl AggregatorClient for AggregatorHTTPClient {
215215
}
216216
})?;
217217
downloaded_bytes += chunk.len() as u64;
218-
progress_bar.set_position(downloaded_bytes);
218+
progress_reporter.report(downloaded_bytes);
219219
}
220220

221221
Ok(())

mithril-client/src/aggregator_client/snapshot_client.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
//! This module contains a struct to exchange snapshot information with the Aggregator
22
3+
use slog_scope::warn;
34
use std::{
45
path::{Path, PathBuf},
56
sync::Arc,
67
};
8+
use thiserror::Error;
79

8-
use indicatif::ProgressBar;
910
use mithril_common::{
1011
entities::Snapshot,
1112
messages::{SnapshotListItemMessage, SnapshotListMessage, SnapshotMessage},
1213
StdResult,
1314
};
14-
use slog_scope::warn;
15-
use thiserror::Error;
1615

17-
use super::AggregatorClient;
16+
use crate::aggregator_client::AggregatorClient;
17+
use crate::utils::DownloadProgressReporter;
1818

1919
/// Error for the Snapshot client
2020
#[derive(Error, Debug)]
@@ -64,7 +64,7 @@ impl SnapshotClient {
6464
&self,
6565
snapshot: &Snapshot,
6666
download_dir: &Path,
67-
progress_bar: ProgressBar,
67+
progress_reporter: DownloadProgressReporter,
6868
) -> StdResult<PathBuf> {
6969
let filepath = PathBuf::new()
7070
.join(download_dir)
@@ -74,7 +74,7 @@ impl SnapshotClient {
7474
if self.http_client.probe(url).await.is_ok() {
7575
match self
7676
.http_client
77-
.download(url, &filepath, progress_bar)
77+
.download(url, &filepath, progress_reporter)
7878
.await
7979
{
8080
Ok(()) => return Ok(filepath),

mithril-client/src/commands/snapshot/download.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use std::{path::PathBuf, sync::Arc};
2-
31
use clap::Parser;
42
use config::{builder::DefaultState, Config, ConfigBuilder};
5-
use indicatif::ProgressDrawTarget;
3+
use std::{path::PathBuf, sync::Arc};
4+
65
use mithril_common::{messages::FromMessageAdapter, StdResult};
76

8-
use crate::{dependencies::DependenciesBuilder, FromSnapshotMessageAdapter};
7+
use crate::{
8+
dependencies::DependenciesBuilder, utils::ProgressOutputType, FromSnapshotMessageAdapter,
9+
};
910

1011
/// Clap command to download the snapshot and verify the certificate.
1112
#[derive(Parser, Debug, Clone)]
@@ -36,17 +37,17 @@ impl SnapshotDownloadCommand {
3637
let snapshot_service = dependencies_builder.get_snapshot_service().await?;
3738
let snapshot_entity =
3839
FromSnapshotMessageAdapter::adapt(snapshot_service.show(&self.digest).await?);
39-
let progress_target = if self.json {
40-
ProgressDrawTarget::hidden()
40+
let progress_output_type = if self.json {
41+
ProgressOutputType::JsonReporter
4142
} else {
42-
ProgressDrawTarget::stdout()
43+
ProgressOutputType::TTY
4344
};
4445
let filepath = snapshot_service
4546
.download(
4647
&snapshot_entity,
4748
&self.download_dir,
4849
&config.get_string("genesis_verification_key")?,
49-
progress_target,
50+
progress_output_type,
5051
)
5152
.await?;
5253

mithril-client/src/services/snapshot.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::Context;
22
use async_trait::async_trait;
33
use futures::Future;
4-
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle};
4+
use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle};
55
use slog_scope::{debug, warn};
66
use std::{
77
fmt::Write,
@@ -24,7 +24,9 @@ use mithril_common::{
2424

2525
use crate::{
2626
aggregator_client::{AggregatorHTTPClientError, CertificateClient, SnapshotClient},
27-
utils::{SnapshotUnpacker, SnapshotUnpackerError},
27+
utils::{
28+
DownloadProgressReporter, ProgressOutputType, SnapshotUnpacker, SnapshotUnpackerError,
29+
},
2830
};
2931

3032
/// [SnapshotService] related errors.
@@ -72,7 +74,7 @@ pub trait SnapshotService: Sync + Send {
7274
snapshot_entity: &SignedEntity<Snapshot>,
7375
pathdir: &Path,
7476
genesis_verification_key: &str,
75-
progress_target: ProgressDrawTarget,
77+
progress_output_type: ProgressOutputType,
7678
) -> StdResult<PathBuf>;
7779
}
7880

@@ -210,12 +212,12 @@ impl SnapshotService for MithrilClientSnapshotService {
210212
snapshot_entity: &SignedEntity<Snapshot>,
211213
download_dir: &Path,
212214
genesis_verification_key: &str,
213-
progress_target: ProgressDrawTarget,
215+
progress_output_type: ProgressOutputType,
214216
) -> StdResult<PathBuf> {
215217
debug!("Snapshot service: download.");
216218

217219
let db_dir = download_dir.join("db");
218-
let progress_bar = MultiProgress::with_draw_target(progress_target);
220+
let progress_bar = MultiProgress::with_draw_target(progress_output_type.into());
219221
progress_bar.println("1/7 - Checking local disk info…")?;
220222
let unpacker = SnapshotUnpacker;
221223

@@ -246,7 +248,11 @@ impl SnapshotService for MithrilClientSnapshotService {
246248
.progress_chars("#>-"));
247249
let snapshot_path = self
248250
.snapshot_client
249-
.download(&snapshot_entity.artifact, download_dir, pb)
251+
.download(
252+
&snapshot_entity.artifact,
253+
download_dir,
254+
DownloadProgressReporter::new(pb, progress_output_type),
255+
)
250256
.await
251257
.with_context(|| format!("Could not download file in '{}'", download_dir.display()))?;
252258

@@ -550,7 +556,7 @@ mod tests {
550556
&snapshot,
551557
&test_path,
552558
&genesis_verification_key.to_json_hex().unwrap(),
553-
ProgressDrawTarget::hidden(),
559+
ProgressOutputType::Hidden,
554560
)
555561
.await
556562
.expect("Snapshot download should succeed.");
@@ -590,7 +596,7 @@ mod tests {
590596
&snapshot,
591597
&test_path,
592598
&genesis_verification_key.to_json_hex().unwrap(),
593-
ProgressDrawTarget::hidden(),
599+
ProgressOutputType::Hidden,
594600
)
595601
.await
596602
.expect("Snapshot download should succeed.");
@@ -636,7 +642,7 @@ mod tests {
636642
&signed_entity,
637643
&test_path,
638644
&genesis_verification_key.to_json_hex().unwrap(),
639-
ProgressDrawTarget::hidden(),
645+
ProgressOutputType::Hidden,
640646
)
641647
.await
642648
.expect_err("Snapshot digest comparison should fail.");
@@ -684,7 +690,7 @@ mod tests {
684690
&snapshot,
685691
&test_path,
686692
&genesis_verification_key.to_json_hex().unwrap(),
687-
ProgressDrawTarget::hidden(),
693+
ProgressOutputType::Hidden,
688694
)
689695
.await
690696
.expect_err("Snapshot download should fail.");

mithril-client/src/utils/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Utilities module
22
//! This module contains tools needed mostly in services layers.
33
4+
mod progress_reporter;
45
mod unpacker;
56

7+
pub use progress_reporter::*;
68
pub use unpacker::*;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use indicatif::{ProgressBar, ProgressDrawTarget};
2+
use slog_scope::warn;
3+
use std::{
4+
sync::RwLock,
5+
time::{Duration, Instant},
6+
};
7+
8+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9+
/// Output type of a [ProgressReporter]
10+
pub enum ProgressOutputType {
11+
/// Output to json
12+
JsonReporter,
13+
/// Output to tty
14+
TTY,
15+
/// No output
16+
Hidden,
17+
}
18+
19+
impl From<ProgressOutputType> for ProgressDrawTarget {
20+
fn from(value: ProgressOutputType) -> Self {
21+
match value {
22+
ProgressOutputType::JsonReporter => ProgressDrawTarget::hidden(),
23+
ProgressOutputType::TTY => ProgressDrawTarget::stdout(),
24+
ProgressOutputType::Hidden => ProgressDrawTarget::hidden(),
25+
}
26+
}
27+
}
28+
29+
/// Wrapper of a indicatif [ProgressBar] to allow reporting to json.
30+
pub struct DownloadProgressReporter {
31+
progress_bar: ProgressBar,
32+
output_type: ProgressOutputType,
33+
last_json_report_instant: RwLock<Option<Instant>>,
34+
}
35+
36+
impl DownloadProgressReporter {
37+
/// Instanciate a new progress reporter
38+
pub fn new(progress_bar: ProgressBar, output_type: ProgressOutputType) -> Self {
39+
Self {
40+
progress_bar,
41+
output_type,
42+
last_json_report_instant: RwLock::new(None),
43+
}
44+
}
45+
46+
/// Report the current progress
47+
pub fn report(&self, actual_position: u64) {
48+
self.progress_bar.set_position(actual_position);
49+
50+
if let ProgressOutputType::JsonReporter = self.output_type {
51+
let should_report = match self.get_remaining_time_since_last_json_report() {
52+
Some(remaining_time) => remaining_time > Duration::from_millis(333),
53+
None => true,
54+
};
55+
56+
if should_report {
57+
println!(
58+
r#"{{ "bytesDownloaded": {}, "bytesTotal": {}, "secondsLeft": {}.{}, "secondsElapsed": {}.{} }}"#,
59+
self.progress_bar.position(),
60+
self.progress_bar.length().unwrap_or(0),
61+
self.progress_bar.eta().as_secs(),
62+
self.progress_bar.eta().subsec_millis(),
63+
self.progress_bar.elapsed().as_secs(),
64+
self.progress_bar.elapsed().subsec_millis(),
65+
);
66+
67+
match self.last_json_report_instant.write() {
68+
Ok(mut instant) => *instant = Some(Instant::now()),
69+
Err(error) => {
70+
warn!("failed to update last json report instant, error: {error:?}")
71+
}
72+
};
73+
}
74+
};
75+
}
76+
77+
fn get_remaining_time_since_last_json_report(&self) -> Option<Duration> {
78+
match self.last_json_report_instant.read() {
79+
Ok(instant) => (*instant).map(|instant| instant.elapsed()),
80+
Err(_) => None,
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)