From 02f5d3273a05cc29196174da42634ea76a067516 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Sun, 4 Jan 2026 00:25:28 +0100 Subject: [PATCH 1/4] prng: print benchmark progress live and show PRNG activity immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PRNG auto-selection benchmark previously ran silently and only printed results at the end. As a result, there was a noticeable delay (several seconds) between starting nwipe and any visible output, making it appear as if the program was stalled. This change moves the output into the benchmark loop itself: - Print "Analysing PRNG performance:" immediately when benchmarking starts - Show a classic rotating cursor (-\|/) while benchmarks are running - Print "Testing performance..." before each PRNG is benchmarked - Print each PRNG’s result (MB/s or failure) immediately after it finishes The existing nwipe_prng_benchmark_all() API is preserved for compatibility. A new nwipe_prng_benchmark_all_live() function provides the live output behaviour and is used for --prng=auto. This makes startup progress visible, improves UX, and aligns behaviour with the original maintainer’s expectations. --- src/nwipe.c | 35 +++++++++-- src/prng.c | 176 +++++++++++++++++++++++++++++++++++++++++++++------- src/prng.h | 13 ++++ 3 files changed, 196 insertions(+), 28 deletions(-) diff --git a/src/nwipe.c b/src/nwipe.c index fd1e14f..88f70b4 100644 --- a/src/nwipe.c +++ b/src/nwipe.c @@ -385,10 +385,17 @@ int main( int argc, char** argv ) nwipe_prng_bench_result_t results[16]; memset( results, 0, sizeof( results ) ); + /* ------------------------------------------------------------ + * --prng-benchmark-only path + * (keep output clean: no live "Testing..." lines by default) + * ------------------------------------------------------------ */ if( nwipe_options.prng_benchmark_only ) { - int n = nwipe_prng_benchmark_all( - seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ) ); + const int live_print = 0; /* set to 1 if you also want live here */ + + int n = nwipe_prng_benchmark_all_live( + seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ), live_print ); + if( n <= 0 ) { nwipe_log( NWIPE_LOG_ERROR, "PRNG benchmark failed (no results)." ); @@ -435,11 +442,31 @@ int main( int argc, char** argv ) exit( 0 ); } - /* --prng=auto path */ + /* ------------------------------------------------------------ + * --prng=auto path + * (THIS is the path where live output matters for “GUI delay”) + * ------------------------------------------------------------ */ if( nwipe_options.prng_auto ) { + /* live_print=1: prints: + * - "Analysing PRNG performance:" immediately (with spinner) + * - "Testing performance..." per PRNG + * - " -> xx.x MB/s" immediately after each PRNG + */ + const int live_print = 1; + + /* Option A (preferred): make select_fastest call the live benchmark internally. + * best = nwipe_prng_select_fastest(seconds, io_block, results, count); + * and inside select_fastest use nwipe_prng_benchmark_all_live(..., 1) + * + * Option B: benchmark here (live), then choose best locally. + * Since you already have nwipe_prng_select_fastest(), stick with Option A. + */ + const nwipe_prng_t* best = nwipe_prng_select_fastest( - seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ) ); + seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ) /* results_count */ + /* ensure select_fastest uses nwipe_prng_benchmark_all_live(..., live_print) */ + ); if( best != NULL ) { diff --git a/src/prng.c b/src/prng.c index d87175f..cc16960 100644 --- a/src/prng.c +++ b/src/prng.c @@ -723,11 +723,61 @@ static void* nwipe_prng_alloc_aligned( size_t alignment, size_t size ) return p; } +/* --- live spinner state ------------------------------------------------- */ + +typedef struct +{ + int enabled; + int is_tty; + int spin_idx; + double last_tick; + const char* analysing_msg; +} nwipe_prng_live_t; + +static void nwipe_prng_live_clear_line( nwipe_prng_live_t* live ) +{ + if( !live || !live->enabled || !live->is_tty ) + return; + + /* clear current line */ + printf( "\r\033[K" ); + fflush( stdout ); +} + +static void nwipe_prng_live_render_spinner( nwipe_prng_live_t* live, int advance ) +{ + static const char spin[] = "-\\|/"; + if( !live || !live->enabled || !live->is_tty ) + return; + + if( advance ) + live->spin_idx = ( live->spin_idx + 1 ) & 3; + + printf( "\r\033[K%s %c", live->analysing_msg, spin[live->spin_idx] ); + fflush( stdout ); +} + +static void nwipe_prng_live_tick_if_due( nwipe_prng_live_t* live, double now ) +{ + if( !live || !live->enabled || !live->is_tty ) + return; + + /* ~10 Hz */ + if( ( now - live->last_tick ) >= 0.10 ) + { + live->last_tick = now; + nwipe_prng_live_render_spinner( live, /*advance=*/1 ); + } +} + +/* --- bench one PRNG (now with optional live spinner ticks) -------------- */ + static void nwipe_prng_bench_one( const nwipe_prng_t* prng, nwipe_prng_bench_result_t* out, void* io_buf, size_t io_block, - double seconds_per_prng ) + double seconds_per_prng, + nwipe_prng_live_t* live ) { void* state = NULL; @@ -755,6 +805,13 @@ static void nwipe_prng_bench_one( const nwipe_prng_t* prng, const double t0 = nwipe_prng_monotonic_seconds(); double now = t0; + /* ensure spinner is visible right when this PRNG starts doing work */ + if( live && live->enabled && live->is_tty ) + { + live->last_tick = now; + nwipe_prng_live_render_spinner( live, /*advance=*/0 ); + } + while( ( now - t0 ) < seconds_per_prng ) { rc = prng->read( &state, io_buf, io_block ); @@ -766,6 +823,9 @@ static void nwipe_prng_bench_one( const nwipe_prng_t* prng, out->bytes += (unsigned long long) io_block; now = nwipe_prng_monotonic_seconds(); + + /* keep the classic cursor rotating while the benchmark runs */ + nwipe_prng_live_tick_if_due( live, now ); } out->seconds = now - t0; @@ -778,15 +838,18 @@ static void nwipe_prng_bench_one( const nwipe_prng_t* prng, free( state ); } -int nwipe_prng_benchmark_all( double seconds_per_prng, - size_t io_block_bytes, - nwipe_prng_bench_result_t* results, - size_t results_count ) +/* --- new: benchmark all with optional live output ----------------------- */ + +int nwipe_prng_benchmark_all_live( double seconds_per_prng, + size_t io_block_bytes, + nwipe_prng_bench_result_t* results, + size_t results_count, + int live_print ) { if( results == NULL || results_count == 0 ) return 0; - /* Anzahl PRNGs begrenzen auf results_count */ + /* limit number of PRNGs to results_count */ size_t max = sizeof( all_prngs ) / sizeof( all_prngs[0] ); if( results_count < max ) max = results_count; @@ -798,42 +861,107 @@ int nwipe_prng_benchmark_all( double seconds_per_prng, return -1; } + nwipe_prng_live_t live; + memset( &live, 0, sizeof( live ) ); + live.enabled = ( live_print ? 1 : 0 ); + live.is_tty = ( live.enabled ? ( isatty( fileno( stdout ) ) ? 1 : 0 ) : 0 ); + live.spin_idx = 0; + live.last_tick = nwipe_prng_monotonic_seconds(); + live.analysing_msg = "Analysing PRNG performance:"; + + if( live.enabled ) + { + if( live.is_tty ) + { + /* show immediately (user sees activity right away) */ + nwipe_prng_live_render_spinner( &live, /*advance=*/0 ); + } + else + { + /* non-interactive: just print the header once */ + printf( "%s\n", live.analysing_msg ); + fflush( stdout ); + } + } + for( size_t i = 0; i < max; i++ ) { nwipe_prng_bench_result_t* r = &results[i]; memset( r, 0, sizeof( *r ) ); - nwipe_prng_bench_one( all_prngs[i], r, io_buf, io_block_bytes, seconds_per_prng ); + + const nwipe_prng_t* prng = all_prngs[i]; + + if( live.enabled ) + { + if( live.is_tty ) + nwipe_prng_live_clear_line( &live ); + + /* as requested: print before starting each PRNG */ + printf( "Testing %s performance...\n", prng->label ); + fflush( stdout ); + + if( live.is_tty ) + { + /* re-show spinner line while the PRNG runs */ + live.last_tick = nwipe_prng_monotonic_seconds(); + nwipe_prng_live_render_spinner( &live, /*advance=*/0 ); + } + } + + nwipe_prng_bench_one( prng, r, io_buf, io_block_bytes, seconds_per_prng, &live ); + + if( live.enabled ) + { + if( live.is_tty ) + nwipe_prng_live_clear_line( &live ); + + /* print result immediately after each PRNG */ + if( r->rc == 0 ) + printf( "%-22s -> %8.1f MB/s\n", r->prng->label, r->mbps ); + else + printf( "%-22s -> (failed: rc=%d)\n", r->prng->label, r->rc ); + fflush( stdout ); + + if( live.is_tty ) + { + /* keep spinner visible between PRNGs */ + live.last_tick = nwipe_prng_monotonic_seconds(); + nwipe_prng_live_render_spinner( &live, /*advance=*/1 ); + } + } + } + + if( live.enabled && live.is_tty ) + { + /* remove spinner line before subsequent prints (Selected PRNG, GUI, etc.) */ + nwipe_prng_live_clear_line( &live ); } free( io_buf ); return (int) max; } +/* keep old API intact (no live output) */ +int nwipe_prng_benchmark_all( double seconds_per_prng, + size_t io_block_bytes, + nwipe_prng_bench_result_t* results, + size_t results_count ) +{ + return nwipe_prng_benchmark_all_live( seconds_per_prng, io_block_bytes, results, results_count, /*live_print=*/0 ); +} + +/* --- select fastest: now uses live benchmark so printing is not delayed -- */ + const nwipe_prng_t* nwipe_prng_select_fastest( double seconds_per_prng, size_t io_block_bytes, nwipe_prng_bench_result_t* results, size_t results_count ) { - int n = nwipe_prng_benchmark_all( seconds_per_prng, io_block_bytes, results, results_count ); + /* live_print=1 => prints per PRNG immediately (plus spinner) */ + int n = nwipe_prng_benchmark_all_live( seconds_per_prng, io_block_bytes, results, results_count, /*live_print=*/1 ); if( n <= 0 ) return NULL; - printf( "Analysing PRNG performance:\n" ); - fflush( stdout ); - - for( int i = 0; i < n; i++ ) - { - if( results[i].prng == NULL ) - continue; - - if( results[i].rc == 0 ) - printf( "%-22s -> %8.1f MB/s\n", results[i].prng->label, results[i].mbps ); - else - printf( "%-22s -> (failed: rc=%d)\n", results[i].prng->label, results[i].rc ); - - fflush( stdout ); - } - const nwipe_prng_t* best = NULL; double best_mbps = 0.0; diff --git a/src/prng.h b/src/prng.h index 0e244ef..4a0024e 100644 --- a/src/prng.h +++ b/src/prng.h @@ -54,11 +54,24 @@ typedef struct int rc; } nwipe_prng_bench_result_t; +/* Existing API (kept for compatibility: no live output) */ int nwipe_prng_benchmark_all( double seconds_per_prng, size_t io_block_bytes, nwipe_prng_bench_result_t* results, size_t results_count ); +/* New API: live output (spinner + per-PRNG immediate prints) + * live_print: + * 0 = behave like old benchmark (silent, just fills results[]) + * 1 = print "Analysing PRNG performance:" immediately, rotate cursor, + * print "Testing ..." before each PRNG, and print result right after. + */ +int nwipe_prng_benchmark_all_live( double seconds_per_prng, + size_t io_block_bytes, + nwipe_prng_bench_result_t* results, + size_t results_count, + int live_print ); + const nwipe_prng_t* nwipe_prng_select_fastest( double seconds_per_prng, size_t io_block_bytes, nwipe_prng_bench_result_t* results, From 951bb5642dea84e8eb2f5cc5c0b34030786d8a26 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Sun, 4 Jan 2026 00:32:42 +0100 Subject: [PATCH 2/4] options: make PRNG auto-selection the default, add manual/default opt-out PRNG auto-selection is now enabled by default when no --prng option is specified. On startup, nwipe benchmarks the available PRNGs and selects the fastest one for the current hardware. To disable auto-selection, a new opt-out mode is introduced: --prng=default (alias: --prng=manual) This keeps the previous behaviour by using the built-in, CPU-based PRNG selection without running benchmarks. Explicit PRNG selections (e.g. --prng=isaac, --prng=aes_ctr_prng) also disable auto-selection automatically. Benchmark timing defaults are adjusted accordingly: - auto-selection defaults to a short benchmark run - --prng-benchmark-only keeps a longer default duration This improves out-of-the-box performance while preserving full user control and backwards compatibility. --- src/options.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/options.c b/src/options.c index 24b50cd..1a3b751 100644 --- a/src/options.c +++ b/src/options.c @@ -142,7 +142,7 @@ int nwipe_options_parse( int argc, char** argv ) nwipe_options.autonuke = 0; nwipe_options.autopoweroff = 0; nwipe_options.method = &nwipe_random; - nwipe_options.prng_auto = 0; + nwipe_options.prng_auto = 1; /* by default the PRNG is selected through the benchmark selection */ nwipe_options.prng_benchmark_only = 0; nwipe_options.prng_bench_seconds = 1.0; /* default for interactive / manual */ @@ -611,12 +611,25 @@ int nwipe_options_parse( int argc, char** argv ) case 'p': /* PRNG option. */ + /* Default behaviour is auto now, but allow explicit opt-out */ if( strcmp( optarg, "auto" ) == 0 ) { nwipe_options.prng_auto = 1; /* keep current default as fallback until autoselect runs */ break; } + + /* NEW: disable auto and keep compiled-in default selection */ + if( strcmp( optarg, "default" ) == 0 || strcmp( optarg, "manual" ) == 0 ) + { + nwipe_options.prng_auto = 0; + /* keep nwipe_options.prng as chosen by CPU heuristics above */ + break; + } + + /* Any explicit PRNG selection implies auto off */ + nwipe_options.prng_auto = 0; + if( strcmp( optarg, "mersenne" ) == 0 || strcmp( optarg, "twister" ) == 0 ) { nwipe_options.prng = &nwipe_twister; @@ -634,16 +647,19 @@ int nwipe_options_parse( int argc, char** argv ) nwipe_options.prng = &nwipe_isaac64; break; } + if( strcmp( optarg, "add_lagg_fibonacci_prng" ) == 0 ) { nwipe_options.prng = &nwipe_add_lagg_fibonacci_prng; break; } + if( strcmp( optarg, "xoroshiro256_prng" ) == 0 ) { nwipe_options.prng = &nwipe_xoroshiro256_prng; break; } + if( strcmp( optarg, "aes_ctr_prng" ) == 0 ) { if( has_aes_ni() ) @@ -660,7 +676,6 @@ int nwipe_options_parse( int argc, char** argv ) break; } - /* Else we do not know this PRNG. */ fprintf( stderr, "Error: Unknown prng '%s'.\n", optarg ); exit( EINVAL ); @@ -877,10 +892,16 @@ void display_help() puts( " If set to \"noPDF\" no PDF reports are written.\n" ); puts( " -p, --prng=METHOD PRNG option " "(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng|aes_ctr_prng)\n" ); - puts( " --prng=auto" ); + puts( " --prng=auto (default)" ); puts( " Automatically benchmark all available PRNGs at startup and" ); puts( " select the fastest one for the current hardware." ); puts( "" ); + + puts( " --prng=default" ); + puts( " Disable auto-selection and use the built-in default PRNG choice" ); + puts( " (CPU-based heuristic; no benchmarking)." ); + puts( " Alias: --prng=manual" ); + puts( "" ); puts( " --prng-benchmark" ); puts( " Run a RAM-only PRNG throughput benchmark and exit." ); puts( " Prints a sorted leaderboard (MB/s). No wipe is performed." ); From 7df282386e618809dd6b335d2bba7458ac43d8b2 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Sun, 4 Jan 2026 12:59:16 +0100 Subject: [PATCH 3/4] Removed annoying Testing PRNG.. printfs during init --- src/prng.c | 79 +++++++++++++++++++++--------------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/prng.c b/src/prng.c index cc16960..090feba 100644 --- a/src/prng.c +++ b/src/prng.c @@ -731,7 +731,9 @@ typedef struct int is_tty; int spin_idx; double last_tick; - const char* analysing_msg; + + /* message currently shown with spinner (includes current PRNG label) */ + char msg[160]; } nwipe_prng_live_t; static void nwipe_prng_live_clear_line( nwipe_prng_live_t* live ) @@ -739,11 +741,21 @@ static void nwipe_prng_live_clear_line( nwipe_prng_live_t* live ) if( !live || !live->enabled || !live->is_tty ) return; - /* clear current line */ printf( "\r\033[K" ); fflush( stdout ); } +static void nwipe_prng_live_set_msg_for_prng( nwipe_prng_live_t* live, const nwipe_prng_t* prng ) +{ + if( !live || !live->enabled ) + return; + + if( prng && prng->label ) + snprintf( live->msg, sizeof( live->msg ), "Testing %s performance", prng->label ); + else + snprintf( live->msg, sizeof( live->msg ), "Testing PRNG performance" ); +} + static void nwipe_prng_live_render_spinner( nwipe_prng_live_t* live, int advance ) { static const char spin[] = "-\\|/"; @@ -753,7 +765,7 @@ static void nwipe_prng_live_render_spinner( nwipe_prng_live_t* live, int advance if( advance ) live->spin_idx = ( live->spin_idx + 1 ) & 3; - printf( "\r\033[K%s %c", live->analysing_msg, spin[live->spin_idx] ); + printf( "\r\033[K%s %c", live->msg, spin[live->spin_idx] ); fflush( stdout ); } @@ -770,7 +782,7 @@ static void nwipe_prng_live_tick_if_due( nwipe_prng_live_t* live, double now ) } } -/* --- bench one PRNG (now with optional live spinner ticks) -------------- */ +/* --- bench one PRNG (unchanged except it ticks spinner) ----------------- */ static void nwipe_prng_bench_one( const nwipe_prng_t* prng, nwipe_prng_bench_result_t* out, @@ -805,7 +817,6 @@ static void nwipe_prng_bench_one( const nwipe_prng_t* prng, const double t0 = nwipe_prng_monotonic_seconds(); double now = t0; - /* ensure spinner is visible right when this PRNG starts doing work */ if( live && live->enabled && live->is_tty ) { live->last_tick = now; @@ -824,21 +835,19 @@ static void nwipe_prng_bench_one( const nwipe_prng_t* prng, out->bytes += (unsigned long long) io_block; now = nwipe_prng_monotonic_seconds(); - /* keep the classic cursor rotating while the benchmark runs */ + /* rotate cursor while running */ nwipe_prng_live_tick_if_due( live, now ); } out->seconds = now - t0; if( out->rc == 0 && out->seconds > 0.0 ) - { out->mbps = ( (double) out->bytes / ( 1024.0 * 1024.0 ) ) / out->seconds; - } if( state ) free( state ); } -/* --- new: benchmark all with optional live output ----------------------- */ +/* --- benchmark all with live current-PRNG spinner ----------------------- */ int nwipe_prng_benchmark_all_live( double seconds_per_prng, size_t io_block_bytes, @@ -849,7 +858,6 @@ int nwipe_prng_benchmark_all_live( double seconds_per_prng, if( results == NULL || results_count == 0 ) return 0; - /* limit number of PRNGs to results_count */ size_t max = sizeof( all_prngs ) / sizeof( all_prngs[0] ); if( results_count < max ) max = results_count; @@ -867,22 +875,7 @@ int nwipe_prng_benchmark_all_live( double seconds_per_prng, live.is_tty = ( live.enabled ? ( isatty( fileno( stdout ) ) ? 1 : 0 ) : 0 ); live.spin_idx = 0; live.last_tick = nwipe_prng_monotonic_seconds(); - live.analysing_msg = "Analysing PRNG performance:"; - - if( live.enabled ) - { - if( live.is_tty ) - { - /* show immediately (user sees activity right away) */ - nwipe_prng_live_render_spinner( &live, /*advance=*/0 ); - } - else - { - /* non-interactive: just print the header once */ - printf( "%s\n", live.analysing_msg ); - fflush( stdout ); - } - } + snprintf( live.msg, sizeof( live.msg ), "Testing PRNG performance" ); for( size_t i = 0; i < max; i++ ) { @@ -893,49 +886,39 @@ int nwipe_prng_benchmark_all_live( double seconds_per_prng, if( live.enabled ) { - if( live.is_tty ) - nwipe_prng_live_clear_line( &live ); - - /* as requested: print before starting each PRNG */ - printf( "Testing %s performance...\n", prng->label ); - fflush( stdout ); - if( live.is_tty ) { - /* re-show spinner line while the PRNG runs */ + /* single status line with spinner showing current PRNG */ + nwipe_prng_live_set_msg_for_prng( &live, prng ); live.last_tick = nwipe_prng_monotonic_seconds(); nwipe_prng_live_render_spinner( &live, /*advance=*/0 ); } + else + { + /* non-tty: just print once per PRNG */ + printf( "Testing %s performance...\n", prng->label ); + fflush( stdout ); + } } nwipe_prng_bench_one( prng, r, io_buf, io_block_bytes, seconds_per_prng, &live ); + if( live.enabled && live.is_tty ) + nwipe_prng_live_clear_line( &live ); + + /* print result immediately after each PRNG */ if( live.enabled ) { - if( live.is_tty ) - nwipe_prng_live_clear_line( &live ); - - /* print result immediately after each PRNG */ if( r->rc == 0 ) printf( "%-22s -> %8.1f MB/s\n", r->prng->label, r->mbps ); else printf( "%-22s -> (failed: rc=%d)\n", r->prng->label, r->rc ); fflush( stdout ); - - if( live.is_tty ) - { - /* keep spinner visible between PRNGs */ - live.last_tick = nwipe_prng_monotonic_seconds(); - nwipe_prng_live_render_spinner( &live, /*advance=*/1 ); - } } } if( live.enabled && live.is_tty ) - { - /* remove spinner line before subsequent prints (Selected PRNG, GUI, etc.) */ nwipe_prng_live_clear_line( &live ); - } free( io_buf ); return (int) max; From e964dbab09251e85eb3526518da586efa0c505ab Mon Sep 17 00:00:00 2001 From: PartialVolume <22084881+PartialVolume@users.noreply.github.com> Date: Sun, 4 Jan 2026 13:17:29 +0000 Subject: [PATCH 4/4] Update prng.c Removed an unnecessary "Testing ....prng" printf to improve output clarity. --- src/prng.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/prng.c b/src/prng.c index 090feba..666a362 100644 --- a/src/prng.c +++ b/src/prng.c @@ -893,12 +893,6 @@ int nwipe_prng_benchmark_all_live( double seconds_per_prng, live.last_tick = nwipe_prng_monotonic_seconds(); nwipe_prng_live_render_spinner( &live, /*advance=*/0 ); } - else - { - /* non-tty: just print once per PRNG */ - printf( "Testing %s performance...\n", prng->label ); - fflush( stdout ); - } } nwipe_prng_bench_one( prng, r, io_buf, io_block_bytes, seconds_per_prng, &live );