public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Shan Shaji <s.shaji@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH proxmox-backup 5/6] fix #7024: cli: add more information inside the backup progress logs
Date: Mon, 13 Jul 2026 10:15:28 +0200	[thread overview]
Message-ID: <20260713081529.62859-6-s.shaji@proxmox.com> (raw)
In-Reply-To: <20260713081529.62859-1-s.shaji@proxmox.com>

previously, the progress logs only reported the processed stream size
and uploaded size. To improve visibility additional information from
`PxarArchiverProgressStats` and `UploadCounters` has been included in
the logs.

To access the upload statistics, a new callback field has been added to
the UploadOptions struct that accepts UploadCounters and TimeSpan as
parameters. Additionally, to include pxar metadata in the logs, the
`PxarArchiverProgressStats` instance is now created earlier and shared
between the archiver and the progress logger callback.

Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=7024

Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
 pbs-client/src/backup_stats.rs                |  4 +-
 pbs-client/src/backup_writer.rs               | 24 ++++-----
 pbs-client/src/lib.rs                         |  2 +-
 pbs-client/src/pxar/create.rs                 | 17 +++---
 pbs-client/src/pxar/mod.rs                    |  3 +-
 pbs-client/src/pxar_backup_stream.rs          | 13 ++++-
 proxmox-backup-client/src/main.rs             | 53 +++++++++++++++++--
 .../src/proxmox_restore_daemon/api.rs         |  1 +
 pxar-bin/src/main.rs                          |  1 +
 tests/catar.rs                                |  1 +
 10 files changed, 89 insertions(+), 30 deletions(-)

diff --git a/pbs-client/src/backup_stats.rs b/pbs-client/src/backup_stats.rs
index 6aceee331..a1dc7ea7b 100644
--- a/pbs-client/src/backup_stats.rs
+++ b/pbs-client/src/backup_stats.rs
@@ -42,7 +42,7 @@ impl UploadStats {
 
 /// Atomic counters for accounting upload stream progress information
 #[derive(Clone)]
-pub(crate) struct UploadCounters {
+pub struct UploadCounters {
     injected_chunk_count: Arc<AtomicUsize>,
     known_chunk_count: Arc<AtomicUsize>,
     total_chunk_count: Arc<AtomicUsize>,
@@ -99,7 +99,7 @@ impl UploadCounters {
     }
 
     #[inline(always)]
-    pub(crate) fn total_stream_len(&self) -> usize {
+    pub fn total_stream_len(&self) -> usize {
         self.total_stream_len.load(Ordering::SeqCst)
     }
 
diff --git a/pbs-client/src/backup_writer.rs b/pbs-client/src/backup_writer.rs
index 34d8db90b..8ae0cffe8 100644
--- a/pbs-client/src/backup_writer.rs
+++ b/pbs-client/src/backup_writer.rs
@@ -45,6 +45,8 @@ impl Drop for BackupWriter {
     }
 }
 
+type ProgressLoggerCallback = Box<dyn Fn(&UploadCounters, &TimeSpan) + Send + Sync>;
+
 /// Options for uploading blobs/streams to the server
 #[derive(Default, Clone)]
 pub struct UploadOptions {
@@ -52,6 +54,7 @@ pub struct UploadOptions {
     pub compress: bool,
     pub encrypt: bool,
     pub index_type: IndexType,
+    pub progress_logging_callback: Option<Arc<ProgressLoggerCallback>>,
 }
 
 /// Index type for upload options.
@@ -371,11 +374,11 @@ impl BackupWriter {
         let upload_stats = Self::upload_merged_chunk_stream(
             self.h2.clone(),
             wid,
-            archive_name,
             prefix,
             stream,
             index_csum_2,
             counters_readonly,
+            options.progress_logging_callback,
         )
         .await?;
 
@@ -476,7 +479,7 @@ impl BackupWriter {
             },
             options.compress,
             injections,
-            archive_name,
+            options.progress_logging_callback,
         )
         .await?;
 
@@ -768,7 +771,7 @@ impl BackupWriter {
         crypt_config: Option<Arc<CryptConfig>>,
         compress: bool,
         injections: Option<std::sync::mpsc::Receiver<InjectChunks>>,
-        archive: &BackupArchiveName,
+        progress_logging_callback: Option<Arc<ProgressLoggerCallback>>,
     ) -> impl Future<Output = Result<UploadStats, Error>> {
         let mut counters = UploadCounters::new();
         let counters_readonly = counters.clone();
@@ -849,22 +852,22 @@ impl BackupWriter {
         Self::upload_merged_chunk_stream(
             h2,
             wid,
-            archive,
             prefix,
             stream,
             index_csum_2,
             counters_readonly,
+            progress_logging_callback,
         )
     }
 
     fn upload_merged_chunk_stream(
         h2: H2Client,
         wid: u64,
-        archive: &BackupArchiveName,
         prefix: &str,
         stream: impl Stream<Item = Result<MergedChunkInfo, Error>>,
         index_csum: Arc<Mutex<Option<Sha256>>>,
         counters: UploadCounters,
+        progress_logging_callback: Option<Arc<ProgressLoggerCallback>>,
     ) -> impl Future<Output = Result<UploadStats, Error>> {
         let append_chunk_path = format!("{prefix}_index");
         let upload_chunk_path = format!("{prefix}_chunk");
@@ -874,20 +877,13 @@ impl BackupWriter {
         let (upload_queue, upload_result) =
             Self::append_chunk_queue(h2.clone(), wid, append_chunk_path, counters.clone());
 
-        let progress_handle = if archive.ends_with(".img.fidx")
-            || archive.ends_with(".pxar.didx")
-            || archive.ends_with(".ppxar.didx")
-        {
+        let progress_handle = if let Some(callback) = progress_logging_callback {
             let counters = counters.clone();
             Some(tokio::spawn(async move {
                 loop {
                     tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
-
-                    let size = HumanByte::from(counters.total_stream_len());
-                    let size_uploaded = HumanByte::from(counters.uploaded_len());
                     let elapsed = TimeSpan::from(start_time.elapsed());
-
-                    info!("processed {size} in {elapsed}, uploaded {size_uploaded}");
+                    callback(&counters, &elapsed);
                 }
             }))
         } else {
diff --git a/pbs-client/src/lib.rs b/pbs-client/src/lib.rs
index 139f4f4b5..6bf6cc4e6 100644
--- a/pbs-client/src/lib.rs
+++ b/pbs-client/src/lib.rs
@@ -43,7 +43,7 @@ mod chunk_stream;
 pub use chunk_stream::{ChunkStream, FixedChunkStream, InjectionData};
 
 mod backup_stats;
-pub use backup_stats::BackupStats;
+pub use backup_stats::{BackupStats, UploadCounters};
 
 mod log_throttle;
 pub use log_throttle::LogThrottle;
diff --git a/pbs-client/src/pxar/create.rs b/pbs-client/src/pxar/create.rs
index ed7ca92f2..7012a88dd 100644
--- a/pbs-client/src/pxar/create.rs
+++ b/pbs-client/src/pxar/create.rs
@@ -151,7 +151,7 @@ pub(crate) struct HardLinkInfo {
 }
 
 #[derive(Default)]
-struct PxarArchiverProgressStats {
+pub struct PxarArchiverProgressStats {
     files_reused_count: AtomicU64,
     files_hardlink_count: AtomicU64,
     files_reencoded_count: AtomicU64,
@@ -169,17 +169,17 @@ impl PxarArchiverProgressStats {
     }
 
     #[inline(always)]
-    pub(crate) fn total_reencoded_size(&self) -> u64 {
+    pub fn total_reencoded_size(&self) -> u64 {
         self.total_reencoded_size.load(Ordering::Acquire)
     }
 
     #[inline(always)]
-    pub(crate) fn files_reused_count(&self) -> u64 {
+    pub fn files_reused_count(&self) -> u64 {
         self.files_reused_count.load(Ordering::Acquire)
     }
 
     #[inline(always)]
-    pub(crate) fn total_reused_payload_size(&self) -> u64 {
+    pub fn total_reused_payload_size(&self) -> u64 {
         self.total_reused_payload_size.load(Ordering::Acquire)
     }
 
@@ -197,7 +197,7 @@ impl PxarArchiverProgressStats {
     }
 
     #[inline(always)]
-    pub(crate) fn total_files_processed(&self) -> u64 {
+    pub fn total_files_processed(&self) -> u64 {
         self.files_reused_count() + self.total_hardlink_count() + self.files_reencoded_count()
     }
 
@@ -269,7 +269,7 @@ struct Archiver {
     // an older client without the encode-time check is detected here and the affected file is
     // re-encoded instead of reused.
     last_reusable_offset: Option<u64>,
-    progress_stats: PxarArchiverProgressStats,
+    progress_stats: Arc<PxarArchiverProgressStats>,
     split_archive: bool,
 }
 
@@ -297,6 +297,7 @@ pub async fn create_archive<T, F>(
     options: PxarCreateOptions,
     forced_boundaries: Option<mpsc::Sender<InjectChunks>>,
     suggested_boundaries: Option<mpsc::Sender<u64>>,
+    archiver_progress_stats: Option<Arc<PxarArchiverProgressStats>>,
 ) -> Result<(), Error>
 where
     T: SeqWrite + Send,
@@ -380,7 +381,7 @@ where
         previous_payload_index,
         cache: PxarLookaheadCache::new(options.max_cache_size),
         last_reusable_offset: None,
-        progress_stats: PxarArchiverProgressStats::default(),
+        progress_stats: archiver_progress_stats.unwrap_or_default(),
         split_archive,
     };
 
@@ -2144,7 +2145,7 @@ mod tests {
                 suggested_boundaries: Some(suggested_boundaries),
                 cache: PxarLookaheadCache::new(None),
                 last_reusable_offset: None,
-                progress_stats: PxarArchiverProgressStats::default(),
+                progress_stats: Arc::new(PxarArchiverProgressStats::default()),
                 split_archive: true,
             };
 
diff --git a/pbs-client/src/pxar/mod.rs b/pbs-client/src/pxar/mod.rs
index b88fac33f..d6820d01a 100644
--- a/pbs-client/src/pxar/mod.rs
+++ b/pbs-client/src/pxar/mod.rs
@@ -58,7 +58,8 @@ mod flags;
 pub use flags::Flags;
 
 pub use create::{
-    MetadataArchiveReader, PxarCreateOptions, PxarPrevRef, PxarWriters, create_archive,
+    MetadataArchiveReader, PxarArchiverProgressStats, PxarCreateOptions, PxarPrevRef, PxarWriters,
+    create_archive,
 };
 pub use extract::{
     ErrorHandler, OverwriteFlags, PxarExtractContext, PxarExtractOptions, create_tar, create_zip,
diff --git a/pbs-client/src/pxar_backup_stream.rs b/pbs-client/src/pxar_backup_stream.rs
index 430eee01f..d1b46cf8a 100644
--- a/pbs-client/src/pxar_backup_stream.rs
+++ b/pbs-client/src/pxar_backup_stream.rs
@@ -20,6 +20,7 @@ use proxmox_log::debug;
 use pbs_datastore::catalog::{BackupCatalogWriter, CatalogWriter};
 
 use crate::inject_reused_chunks::InjectChunks;
+use crate::pxar::PxarArchiverProgressStats;
 use crate::pxar::create::PxarWriters;
 
 /// Stream implementation to encode and upload .pxar archives.
@@ -50,6 +51,7 @@ impl PxarBackupStream {
         options: crate::pxar::PxarCreateOptions,
         boundaries: Option<mpsc::Sender<InjectChunks>>,
         separate_payload_stream: bool,
+        archiver_progress_stats: Option<Arc<PxarArchiverProgressStats>>,
     ) -> Result<(Self, Option<Self>), Error> {
         let buffer_size = 256 * 1024;
 
@@ -102,6 +104,7 @@ impl PxarBackupStream {
                 options,
                 boundaries,
                 suggested_boundaries_tx,
+                archiver_progress_stats,
             )
             .await
             {
@@ -145,10 +148,18 @@ impl PxarBackupStream {
         options: crate::pxar::PxarCreateOptions,
         boundaries: Option<mpsc::Sender<InjectChunks>>,
         separate_payload_stream: bool,
+        archiver_progress_stats: Option<Arc<PxarArchiverProgressStats>>,
     ) -> Result<(Self, Option<Self>), Error> {
         let dir = nix::dir::Dir::open(dirname, OFlag::O_DIRECTORY, Mode::empty())?;
 
-        Self::new(dir, catalog, options, boundaries, separate_payload_stream)
+        Self::new(
+            dir,
+            catalog,
+            options,
+            boundaries,
+            separate_payload_stream,
+            archiver_progress_stats,
+        )
     }
 }
 
diff --git a/proxmox-backup-client/src/main.rs b/proxmox-backup-client/src/main.rs
index 5c52b7917..8ce5540d5 100644
--- a/proxmox-backup-client/src/main.rs
+++ b/proxmox-backup-client/src/main.rs
@@ -33,7 +33,9 @@ use pbs_api_types::{
     RateLimitConfig, ServerIdentity, SnapshotListItem, StorageStatus,
 };
 use pbs_client::catalog_shell::Shell;
-use pbs_client::pxar::{ErrorHandler as PxarErrorHandler, MetadataArchiveReader, PxarPrevRef};
+use pbs_client::pxar::{
+    ErrorHandler as PxarErrorHandler, MetadataArchiveReader, PxarArchiverProgressStats, PxarPrevRef,
+};
 use pbs_client::tools::{
     CHUNK_SIZE_SCHEMA, complete_archive_name, complete_auth_id, complete_backup_group,
     complete_backup_snapshot, complete_backup_source, complete_chunk_size,
@@ -50,8 +52,8 @@ use pbs_client::{
     BACKUP_SOURCE_SCHEMA, BackupDetectionMode, BackupReader, BackupRepository,
     BackupRepositoryArgs, BackupSpecificationType, BackupStats, BackupTargetArgs, BackupWriter,
     BackupWriterOptions, ChunkStream, FixedChunkStream, HttpClient, IndexType, InjectionData,
-    LogThrottle, PxarBackupStream, RemoteChunkReader, UploadOptions, delete_ticket_info,
-    parse_backup_specification, view_task_result,
+    LogThrottle, PxarBackupStream, RemoteChunkReader, UploadCounters, UploadOptions,
+    delete_ticket_info, parse_backup_specification, view_task_result,
 };
 use pbs_datastore::catalog::{BackupCatalogWriter, CatalogReader, CatalogWriter};
 use pbs_datastore::chunk_store::verify_chunk_size;
@@ -265,11 +267,13 @@ async fn backup_directory<P: AsRef<Path>>(
     catalog: Option<Arc<Mutex<Catalog>>>,
     pxar_create_options: pbs_client::pxar::PxarCreateOptions,
     upload_options: UploadOptions,
+    is_metadata_mode: bool,
 ) -> Result<(BackupStats, Option<BackupStats>), Error> {
     if let IndexType::Fixed(_) = upload_options.index_type {
         bail!("cannot backup directory with fixed chunk size!");
     }
 
+    let pxar_archiver_progress_stats = Arc::new(PxarArchiverProgressStats::default());
     let (payload_boundaries_tx, payload_boundaries_rx) = std::sync::mpsc::channel();
     let (pxar_stream, payload_stream) = PxarBackupStream::open(
         dir_path.as_ref(),
@@ -277,6 +281,7 @@ async fn backup_directory<P: AsRef<Path>>(
         pxar_create_options,
         Some(payload_boundaries_tx),
         payload_target.is_some(),
+        Some(pxar_archiver_progress_stats.clone()),
     )?;
 
     let mut chunk_stream = ChunkStream::new(pxar_stream, chunk_size, None, None);
@@ -293,7 +298,40 @@ async fn backup_directory<P: AsRef<Path>>(
 
     let stats = client.upload_stream(archive_name, stream, upload_options.clone(), None);
 
+    let progress_logger = move |counters: &UploadCounters, elapsed: &TimeSpan| {
+        let size = HumanByte::from(counters.total_stream_len());
+        let size_uploaded = HumanByte::from(counters.uploaded_len());
+
+        let total_files_processed = pxar_archiver_progress_stats.total_files_processed();
+        let total_reencoded_size = pxar_archiver_progress_stats.total_reencoded_size();
+
+        if is_metadata_mode {
+            let reuse_file_count = pxar_archiver_progress_stats.files_reused_count();
+            let reuse_payload_size = pxar_archiver_progress_stats.total_reused_payload_size();
+
+            let total_reuse_payload_size = HumanByte::from(reuse_payload_size);
+            let total_logical_file_size =
+                HumanByte::from(total_reencoded_size + reuse_payload_size);
+            log::info!(
+                "scanned {total_files_processed} files with {total_logical_file_size} \
+                       (reusing {reuse_file_count} files with {total_reuse_payload_size}) \
+                       of which processed {size} in {elapsed} and uploaded {size_uploaded}"
+            );
+        } else {
+            let total_logical_file_size = HumanByte::from(total_reencoded_size);
+            log::info!(
+                "scanned {total_files_processed} files with {total_logical_file_size} \
+                       of which processed {size} in {elapsed} and uploaded {size_uploaded}"
+            );
+        }
+    };
+
     if let Some(mut payload_stream) = payload_stream {
+        let upload_options = UploadOptions {
+            progress_logging_callback: Some(Arc::new(Box::new(progress_logger))),
+            ..upload_options
+        };
+
         let payload_target = payload_target
             .ok_or_else(|| format_err!("got payload stream, but no target archive name"))?;
 
@@ -1248,6 +1286,7 @@ async fn create_backup(
                     catalog.as_ref().cloned(),
                     pxar_options,
                     upload_options,
+                    detection_mode.is_metadata(),
                 )
                 .await?;
 
@@ -1268,6 +1307,12 @@ async fn create_backup(
             (BackupSpecificationType::IMAGE, false) => {
                 log_file("image", &filename, target.as_ref());
 
+                let progress_logger = |counters: &UploadCounters, elapsed: &TimeSpan| {
+                    let size = HumanByte::from(counters.total_stream_len());
+                    let size_uploaded = HumanByte::from(counters.uploaded_len());
+                    log::info!("processed {size} in {elapsed}, uploaded {size_uploaded}");
+                };
+
                 // 0 means fifo pipe with unknown size
                 let image_file_size = (size != 0).then_some(size);
                 let upload_options = UploadOptions {
@@ -1275,6 +1320,8 @@ async fn create_backup(
                     index_type: IndexType::Fixed(image_file_size),
                     compress: true,
                     encrypt: crypto.mode == CryptMode::Encrypt,
+                    progress_logging_callback: Some(Arc::new(Box::new(progress_logger))),
+                    ..UploadOptions::default()
                 };
 
                 let stats =
diff --git a/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs b/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs
index ff21c9bee..b7f4fd141 100644
--- a/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs
+++ b/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs
@@ -375,6 +375,7 @@ fn extract(
                         options,
                         None,
                         None,
+                        None
                     )
                     .await
                 }
diff --git a/pxar-bin/src/main.rs b/pxar-bin/src/main.rs
index 4b14507ca..953c6e756 100644
--- a/pxar-bin/src/main.rs
+++ b/pxar-bin/src/main.rs
@@ -450,6 +450,7 @@ async fn create_archive(
         options,
         None,
         None,
+        None,
     )
     .await?;
 
diff --git a/tests/catar.rs b/tests/catar.rs
index aed23f866..81ca9d8db 100644
--- a/tests/catar.rs
+++ b/tests/catar.rs
@@ -41,6 +41,7 @@ fn run_test(dir_name: &str) -> Result<(), Error> {
         options,
         None,
         None,
+        None,
     ))?;
 
     Command::new("cmp")
-- 
2.47.3





  parent reply	other threads:[~2026-07-13  8:16 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-13  8:15 [PATCH proxmox-backup 0/6] fix #7024: add more information inside backup logs Shan Shaji
2026-07-13  8:15 ` [PATCH proxmox-backup 1/6] client: pxar: rename `ReuseStats` struct to PxarArchiverProgressStats Shan Shaji
2026-07-13  8:15 ` [PATCH proxmox-backup 2/6] client: pxar: use atomic values in `PxarArchiverProgressStats` Shan Shaji
2026-07-13  8:15 ` [PATCH proxmox-backup 3/6] fix #7024: pxar: show archiver summary for legacy and data mode Shan Shaji
2026-07-13  8:15 ` [PATCH proxmox-backup 4/6] client: backup_stats: move `uploaded_len` counter to `UploadCounters` Shan Shaji
2026-07-13  8:15 ` Shan Shaji [this message]
2026-07-15 12:46   ` [PATCH proxmox-backup 5/6] fix #7024: cli: add more information inside the backup progress logs Thomas Ellmenreich
2026-07-15 14:06     ` Shan Shaji
2026-07-13  8:15 ` [PATCH proxmox-backup 6/6] fix #7024: cli: add option to enable verbose logs Shan Shaji

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260713081529.62859-6-s.shaji@proxmox.com \
    --to=s.shaji@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal