feat(stats): populate receiver wire counters on both receive paths

The single-threaded and -m receivers never populated ReceiverStats.matched_data
or .deleted_files, so --stats always printed 0 for both even when rsync
reported nonzero.  Track the bytes reconstructed from the basis file while
applying a delta, and tally the delete-commit counts (manifest and
per-directory sessions) into the receiver stats.  The -m pipeline now carries
its own stats/would-delete fields and emits the STATUS_STATS frame before the
terminal success, so --threads finally reports the counters and renders
-n --delete  lines.

Also normalize the -n --delete would-delete enumeration's absolute basis
prefixes exactly like the real commit path (fixing an over-report) and fix the
basis_delete_relative off-by-one when the receive root is '/'.  Unit tests
cover the root mapping and the basis protection; integration tests cover
matched/deleted stats for both receivers and the --threads dry-run delete
lines.
This commit is contained in:
2026-09-17 01:08:32 +02:00
parent b02799327d
commit e32733fbf6
11 changed files with 305 additions and 15 deletions
+14 -2
View File
@@ -29,6 +29,8 @@ PipelineContextReceiver* pipeline_context_receiver_create(Config* config, Queue*
context->deferred_manifest = NULL;
context->deferred_plans = NULL;
context->delete_limit_reached = false;
memset(&context->stats, 0, sizeof(context->stats));
context->would_delete = NULL;
atomic_init(&context->cancelled, false);
int init = 0;
if (mtx_init(&context->mutex, mtx_plain) != thrd_success)
@@ -41,6 +43,9 @@ PipelineContextReceiver* pipeline_context_receiver_create(Config* config, Queue*
goto fail;
// cppcheck-suppress unreadVariable
init++;
context->would_delete = array_list_create(free);
if (!context->would_delete)
goto fail;
return context;
fail:
@@ -64,6 +69,8 @@ void pipeline_context_receiver_destroy(PipelineContextReceiver* context) {
queue_destroy(context->queue);
receiver_outcomes_destroy(&context->outcomes);
dir_time_list_free(&context->dir_times);
if (context->would_delete)
array_list_delete(context->would_delete);
mtx_destroy(&context->mutex);
cnd_destroy(&context->condition_not_full);
cnd_destroy(&context->condition_not_empty);
@@ -136,6 +143,11 @@ bool pipeline_context_receiver_enqueue_file(PipelineContextReceiver* context, Fi
static bool receiver_enqueue_file(File* file, void* context_pointer) {
PipelineContextReceiver* context = (PipelineContextReceiver*)context_pointer;
if (file && file->matched_bytes > 0) {
mtx_lock(&context->mutex);
context->stats.matched_data += file->matched_bytes;
mtx_unlock(&context->mutex);
}
return pipeline_context_receiver_enqueue_file(context, file);
}
@@ -171,8 +183,8 @@ int receive_thread(void* pipeline_context) {
false,
NULL,
receiver_pipeline_note_delete_limit,
NULL,
NULL};
&context->stats,
context->would_delete};
if (receiver_process_pending((Config*)config, file_descriptor, &sink, &context->deferred_manifest,
&context->deferred_plans) != 0) {
receiver_thread_fail(context);