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
next prev 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.