Skip to content
50 changes: 30 additions & 20 deletions lib/compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,34 +173,44 @@ char *do_big_num(int64 num, int human_flag, const char *fract)
static unsigned int n;
char *s;
int len, negated;
uint64_t abs_num;

if (human_flag && !number_separator)
(void)get_number_separator();

n = (n + 1) % (sizeof bufs / sizeof bufs[0]);
abs_num = num < 0 ? 0 - (uint64_t)num : (uint64_t)num;

if (human_flag > 1) {
int mult = human_flag == 2 ? 1000 : 1024;
if (num >= mult || num <= -mult) {
double dnum = (double)num / mult;
char units;
if (num < 0)
dnum = -dnum;
if (dnum < mult)
units = 'K';
else if ((dnum /= mult) < mult)
units = 'M';
else if ((dnum /= mult) < mult)
units = 'G';
else if ((dnum /= mult) < mult)
units = 'T';
else {
dnum /= mult;
units = 'P';
unsigned int mult = human_flag == 2 ? 1000 : 1024;

char units[] = "\0KMGTPE";
char *unit = units;
uint64_t powi = 1;

for (;;) {
if (abs_num / mult < powi)
break;

if (unit[1] == '\0')
break;

powi *= mult;
++unit;
}
unit[1] = '\0';

if (powi > 1) {
unsigned int powj = 1, precision = 2;

for (; precision > 0; precision--) {
powj *= 10;
if (abs_num / powi < powj)
break;
}
if (num < 0)
dnum = -dnum;
snprintf(bufs[n], sizeof bufs[0], "%.2f%c", dnum, units);

snprintf(bufs[n], sizeof bufs[0], "%.*f%s%s", precision,
(double) num / powi, unit, *unit && mult == 1024 ? "i" : "");
return bufs[n];
}
}
Expand Down
21 changes: 5 additions & 16 deletions progress.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ static unsigned long msdiff(struct timeval *t1, struct timeval *t2)
static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_last)
{
char rembuf[64], eol[128];
const char *units;
unsigned long diff;
double rate, remain;
int pct;
Expand All @@ -93,26 +92,16 @@ static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_l
/* Compute stats based on the starting info. */
if (!ph_start.time.tv_sec || !(diff = msdiff(&ph_start.time, now)))
diff = 1;
rate = (double) (ofs - ph_start.ofs) * 1000.0 / diff / 1024.0;
rate = (double) (ofs - ph_start.ofs) * 1000.0 / diff;
/* Switch to total time taken for our last update. */
remain = (double) diff / 1000.0;
} else {
strlcpy(eol, " ", sizeof eol);
/* Compute stats based on recent progress. */
if (!(diff = msdiff(&ph_list[oldest_hpos].time, now)))
diff = 1;
rate = (double) (ofs - ph_list[oldest_hpos].ofs) * 1000.0 / diff / 1024.0;
remain = rate ? (double) (size - ofs) / rate / 1000.0 : 0.0;
}

if (rate > 1024*1024) {
rate /= 1024.0 * 1024.0;
units = "GB/s";
} else if (rate > 1024) {
rate /= 1024.0;
units = "MB/s";
} else {
units = "kB/s";
rate = (double) (ofs - ph_list[oldest_hpos].ofs) * 1000.0 / diff;
remain = rate ? (double) (size - ofs) / rate : 0.0;
}

if (remain < 0 || remain > 9999.0 * 3600.0)
Expand All @@ -126,8 +115,8 @@ static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_l

output_needs_newline = 0;
pct = ofs == size ? 100 : (int) (100.0 * ofs / size);
rprintf(FCLIENT, "\r%15s %3d%% %7.2f%s %s%s",
human_num(ofs), pct, rate, units, rembuf, eol);
rprintf(FCLIENT, "\r%15sB %3d%% %7sB/s %s%s",
human_num(ofs), pct, human_num((int64)rate), rembuf, eol);
if (!is_last && !quiet) {
output_needs_newline = 1;
rflush(FCLIENT);
Expand Down
22 changes: 13 additions & 9 deletions rsync.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -3352,9 +3352,13 @@ expand it.
digits) by specifying the `--no-human-readable` (`--no-h`) option.

The unit letters that are appended in levels 2 and 3 are: `K` (kilo), `M`
(mega), `G` (giga), `T` (tera), or `P` (peta). For example, a 1234567-byte
file would output as 1.23M in level-2 (assuming that a period is your local
decimal point).
(mega), `G` (giga), `T` (tera), `P` (peta) or `E` (exa). For example, a
1234567-byte file would output as 1.23M in level-2 (assuming that a
period is your local decimal point). Dynamic precision is applied, so the
three most-significant digits are shown (for example: 3.45M -> 46.7M ->
523M -> 1.24G -> ...).
Additionally an `i` is appended in level-3 to indicate the binary base.
The same file would output as 1.17Mi in level-3.

Backward compatibility note: versions of rsync prior to 3.1.0 do not
support human-readable level 1, and they default to level 0. Thus,
Expand Down Expand Up @@ -3522,10 +3526,10 @@ expand it.
While rsync is transferring a regular file, it updates a progress line that
looks like this:

> 782448 63% 110.64kB/s 0:00:04
> 782,448B 63% 113,295B/s 0:00:04

In this example, the receiver has reconstructed 782448 bytes or 63% of the
sender's file, which is being reconstructed at a rate of 110.64 kilobytes
In this example, the receiver has reconstructed 782,448 bytes or 63% of the
sender's file, which is being reconstructed at a rate of 113,295 bytes
per second, and the transfer will finish in 4 seconds if the current rate
is maintained until the end.

Expand All @@ -3539,11 +3543,11 @@ expand it.
When the file transfer finishes, rsync replaces the progress line with a
summary line that looks like this:

> 1,238,099 100% 146.38kB/s 0:00:08 (xfr#5, to-chk=169/396)
> 1,238,099B 100% 149,893B/s 0:00:08 (xfr#5, to-chk=169/396)

In this example, the file was 1,238,099 bytes long in total, the average
rate of transfer for the whole file was 146.38 kilobytes per second over
the 8 seconds that it took to complete, it was the 5th transfer of a
rate of transfer for the whole file was 149,893 bytes per second over the
8 seconds that it took to complete, it was the 5th transfer of a
regular file during the current rsync session, and there are 169 more files
for the receiver to check (to see if they are up-to-date or not) remaining
out of the 396 total files in the file-list.
Expand Down
27 changes: 25 additions & 2 deletions testsuite/output-options_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ def out(*args, want_rc=0, env=None, text=True):
# --- --progress shows a percentage ------------------------------------------
rmtree(TODIR)
p = out('-a', '--progress', f'{src}/', f'{TODIR}/')
if '100%' not in p.stdout:
test_fail(f"--progress did not show a percentage:\n{p.stdout}")
progress_re = r'[\d.,]+B\s+100%\s+[\d.,]+B/s\s+\d+:\d\d:\d\d'
if not re.search(progress_re, p.stdout):
test_fail(f"--progress did not show the expected final shape:\n{p.stdout}")

# --- -h / --human-readable formats byte counts with a unit suffix -----------
# Without -h, --stats prints grouped digits ("50,000 bytes"); with -h it uses a
Expand All @@ -114,6 +115,28 @@ def out(*args, want_rc=0, env=None, text=True):
if re.search(suffix_re, plain):
test_fail(f"--stats without -h unexpectedly used a unit suffix:\n{plain}")

rmtree(TODIR)
human_binary = out('-a', '-hh', '--stats', f'{src}/', f'{TODIR}/').stdout
if 'Total file size: 48.8Ki bytes' not in human_binary:
test_fail(f"-hh did not use dynamic binary-unit precision:\n{human_binary}")

rmtree(src)
rmtree(TODIR)
makepath(src)
make_data_file(src / 'threshold', 1024)
threshold = out('-a', '-hh', '--stats', f'{src}/', f'{TODIR}/').stdout
if 'Total file size: 1.00Ki bytes' not in threshold:
test_fail(f"-hh did not show an exact-threshold binary unit:\n{threshold}")

rmtree(src)
rmtree(TODIR)
makepath(src)
make_data_file(src / 'below-threshold', 1000)
below_threshold = out('-a', '-hh', '--stats', f'{src}/', f'{TODIR}/').stdout
if 'Total file size: 1,000 bytes' not in below_threshold:
test_fail("-hh should preserve raw byte formatting below the binary-unit "
f"threshold:\n{below_threshold}")

# --- -8 / --8-bit-output leaves high-bit filename bytes unescaped ------------
# rsync escapes non-printable name bytes as \#NNN; -8 prints 8-bit bytes raw.
# This needs a filename containing a high-bit byte and a C locale (where such a
Expand Down
Loading