diff --git a/lib/compat.c b/lib/compat.c index 71738afe2..cc50ab1a7 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -184,23 +184,33 @@ char *do_big_num(int64 num, int human_flag, const char *fract) if (human_flag > 1) { unsigned int mult = human_flag == 2 ? 1000 : 1024; - if (abs_num >= mult) { - const char* units = " KMGTPE"; - uint64_t powi = 1; + char units[] = "\0KMGTPE"; + char *unit = units; + uint64_t powi = 1; - for (;;) { - if (abs_num / mult < powi) - break; + for (;;) { + if (abs_num / mult < powi) + break; - if (units[1] == '\0') - break; + if (unit[1] == '\0') + break; + + powi *= mult; + ++unit; + } + unit[1] = '\0'; + + if (powi > 1) { + unsigned int powj = 1, precision = 2; - powi *= mult; - ++units; + for (; precision > 0; precision--) { + powj *= 10; + if (abs_num / powi < powj) + break; } - snprintf(bufs[n], sizeof bufs[0], "%.2f%c", - (double) num / powi, *units); + snprintf(bufs[n], sizeof bufs[0], "%.*f%s%s", precision, + (double) num / powi, unit, *unit && mult == 1024 ? "i" : ""); return bufs[n]; } } diff --git a/progress.c b/progress.c index 7a241a962..cb1af376a 100644 --- a/progress.c +++ b/progress.c @@ -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; @@ -93,7 +92,7 @@ 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 { @@ -101,18 +100,8 @@ static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_l /* 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) @@ -127,8 +116,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); rput_progress(); - rprintf(FCLIENT, "%15s %3d%% %7.2f%s %s%s", - human_num(ofs), pct, rate, units, rembuf, eol); + rprintf(FCLIENT, "%15sB %3d%% %7sB/s %s%s", human_num(ofs), pct, + rate >= INT64_MAX ? ">MAX " : human_num((int64)rate), rembuf, eol); if (!is_last && !quiet) { output_needs_newline = 1; rflush(FCLIENT); diff --git a/rsync.1.md b/rsync.1.md index bab9257ce..4fb2ff149 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -3616,7 +3616,11 @@ sign) if you want the local shell to expand it. The unit letters that are appended in levels 2 and 3 are: `K` (kilo), `M` (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). + 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 (September 2013) do not support human-readable level 1, and they default to level 0. Thus, @@ -3784,10 +3788,10 @@ sign) if you want the local shell to 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. @@ -3801,11 +3805,11 @@ sign) if you want the local shell to 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. diff --git a/testsuite/output-options_test.py b/testsuite/output-options_test.py index 325d828a1..be68297be 100644 --- a/testsuite/output-options_test.py +++ b/testsuite/output-options_test.py @@ -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}") # --quiet must suppress the progress carriage return as well as its text. rmtree(TODIR) @@ -121,6 +122,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