From 1aaae91e8809adbdda71ab727d1f53eac852fa78 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Tue, 16 Dec 2025 12:30:39 +0100 Subject: [PATCH] gui: add interactive PRNG benchmark mode (RAM-only throughput) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new interactive PRNG benchmark mode to the ncurses GUI, opened with the 'o' key from the main device selection screen. The benchmark runs all available PRNG engines in a RAM-only loop, measuring sustained throughput in MB/s by generating keystream data into an aligned memory buffer. Results are collected and displayed as a sorted leaderboard, making relative PRNG performance immediately visible. Key features: - New modal “PRNG Benchmark” GUI view (o to open, ESC to return) - RAM-only throughput test (~1s per PRNG by default) - Aligned multi-megabyte buffer to reduce syscall and cache artefacts - Deterministic in-memory seeding for reproducible results - Sorted leaderboard showing MB/s per PRNG This mode is intended for diagnostics, comparison, and performance validation of PRNG implementations and does not affect any wipe logic or security properties. --- src/gui.c | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/gui.h | 1 + 2 files changed, 290 insertions(+), 3 deletions(-) diff --git a/src/gui.c b/src/gui.c index 525d3ca..79d863e 100644 --- a/src/gui.c +++ b/src/gui.c @@ -149,9 +149,9 @@ const char* stats_title = " Statistics "; /* Footer labels. */ const char* main_window_footer = - "S=Start m=Method p=PRNG v=Verify t=path r=Rounds b=Blanking Space=Select c=Config CTRL+C=Quit"; -const char* shredos_main_window_footer = - "S=Start m=Method p=PRNG v=Verify t=path r=Rounds b=Blanking Space=Select f=Font size c=Config CTRL+C=Quit"; + "S=Start m=Method p=PRNG v=Verify t=path o=Benchmark r=Rounds b=Blanking Space=Select c=Config CTRL+C=Quit"; +const char* shredos_main_window_footer = "S=Start m=Method p=PRNG v=Verify t=path o=Benchmark r=Rounds b=Blanking " + "Space=Select f=Font size c=Config CTRL+C=Quit"; char** p_main_window_footer; const char* main_window_footer_warning_lower_case_s = " WARNING: To start the wipe press SHIFT+S (uppercase S) "; @@ -168,6 +168,7 @@ const char* main_window_footer_warning_no_drive_selected = * of the footer message when the terminal is resized, a quirk in ncurses? - DO NOT REMOVE THE \" */ const char* selection_footer = "J=Down K=Up Space=Select Backspace=Cancel Ctrl+C=Quit"; const char* selection_footer_config = "J=Down K=Up Return=Select ESC|Backspace=back Ctrl+C=Quit"; +const char* selection_footer_benchmark = "ESC|Backspace=Back ENTER=Run Ctrl+C=Quit"; const char* selection_footer_preview_prior_to_drive_selection = "A=Accept & display drives J=Down K=Up Space=Select Backspace=Cancel Ctrl+C=Quit"; const char* selection_footer_add_customer = "S=Save J=Down K=Up Space=Select Backspace=Cancel Ctrl+C=Quit"; @@ -207,6 +208,116 @@ static void nwipe_gui_draw_acs_prefix( WINDOW* w, int y, int x ) mvwaddch( w, y, x + 3, ' ' ); } +typedef struct +{ + const nwipe_prng_t* prng; + double mbps; + double seconds; + unsigned long long bytes; + int rc; +} nwipe_prng_bench_result_t; + +static double nwipe_gui_monotonic_seconds( void ) +{ + struct timespec ts; + clock_gettime( CLOCK_MONOTONIC, &ts ); + return (double) ts.tv_sec + (double) ts.tv_nsec / 1000000000.0; +} + +/* Simple deterministic seed for benchmarking (no external deps). */ +static void nwipe_gui_make_bench_seed( unsigned char* seed, size_t len ) +{ + unsigned long t = (unsigned long) time( NULL ); + unsigned long x = ( t ^ 0xA5A5A5A5UL ) + (unsigned long) (uintptr_t) seed; + + for( size_t i = 0; i < len; i++ ) + { + x = x * 1664525UL + 1013904223UL; + seed[i] = (unsigned char) ( ( x >> 16 ) & 0xFF ); + } +} + +static void* nwipe_gui_alloc_aligned( size_t alignment, size_t size ) +{ + void* p = NULL; + if( posix_memalign( &p, alignment, size ) != 0 ) + return NULL; + return p; +} + +static int nwipe_gui_cmp_bench_desc( const void* a, const void* b ) +{ + const nwipe_prng_bench_result_t* A = (const nwipe_prng_bench_result_t*) a; + const nwipe_prng_bench_result_t* B = (const nwipe_prng_bench_result_t*) b; + + if( A->rc != 0 && B->rc == 0 ) + return 1; + if( A->rc == 0 && B->rc != 0 ) + return -1; + + if( A->mbps < B->mbps ) + return 1; + if( A->mbps > B->mbps ) + return -1; + return 0; +} + +/* Benchmark one PRNG by generating keystream into a RAM buffer for ~target_seconds. */ +static void nwipe_gui_bench_one_prng( const nwipe_prng_t* prng, + nwipe_prng_bench_result_t* out, + void* io_buf, + size_t io_block, + double target_seconds ) +{ + void* state = NULL; + + unsigned char seedbuf[4096]; + nwipe_entropy_t seed; + seed.s = (u8*) seedbuf; + seed.length = sizeof( seedbuf ); + nwipe_gui_make_bench_seed( seedbuf, sizeof( seedbuf ) ); + + out->prng = prng; + out->mbps = 0.0; + out->seconds = 0.0; + out->bytes = 0; + out->rc = 0; + + int rc = prng->init( &state, &seed ); + if( rc != 0 ) + { + out->rc = rc; + if( state ) + free( state ); + return; + } + + const double t0 = nwipe_gui_monotonic_seconds(); + double now = t0; + + while( ( now - t0 ) < target_seconds ) + { + rc = prng->read( &state, io_buf, io_block ); + if( rc != 0 ) + { + out->rc = rc; + break; + } + + out->bytes += (unsigned long long) io_block; + now = nwipe_gui_monotonic_seconds(); + } + + 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 ); +} + static void nwipe_gui_trim_to_devices( const char* in, const char** out_start ) { const char* p = strstr( in ? in : "", "/devices/" ); @@ -648,6 +759,176 @@ void nwipe_gui_amend_footer_window( const char* footer_text ) } /* nwipe_gui_amend_footer_window */ +void nwipe_gui_benchmark_prng( void ) +{ + extern nwipe_prng_t nwipe_twister; + extern nwipe_prng_t nwipe_isaac; + extern nwipe_prng_t nwipe_isaac64; + extern nwipe_prng_t nwipe_add_lagg_fibonacci_prng; + extern nwipe_prng_t nwipe_xoroshiro256_prng; + extern nwipe_prng_t nwipe_aes_ctr_prng; + + extern int terminate_signal; + + const nwipe_prng_t* prngs[] = { + &nwipe_twister, + &nwipe_isaac, + &nwipe_isaac64, + &nwipe_add_lagg_fibonacci_prng, + &nwipe_xoroshiro256_prng, + &nwipe_aes_ctr_prng, + }; + + const int prng_count = (int) ( sizeof( prngs ) / sizeof( prngs[0] ) ); + + nwipe_prng_bench_result_t results[8]; + memset( results, 0, sizeof( results ) ); + + /* Settings: keep it quick and comparable */ + const double target_seconds = 1.0; /* ~1s per PRNG */ + const size_t io_block = 4 * 1024 * 1024; /* 4 MiB block into RAM */ + const size_t alignment = 4096; + + void* io_buf = nwipe_gui_alloc_aligned( alignment, io_block ); + if( !io_buf ) + { + /* Minimal error view */ + werase( main_window ); + box( main_window, 0, 0 ); + nwipe_gui_title( main_window, " PRNG Benchmark " ); + // wattron( main_window, A_DIM ); + mvwprintw( main_window, 3, 2, "Unable to allocate benchmark buffer (%zu bytes).", io_block ); + mvwprintw( main_window, 5, 2, "Press ESC to return." ); + // wattroff( main_window, A_DIM ); + wrefresh( main_window ); + + int k; + do + { + timeout( 250 ); + k = getch(); + timeout( -1 ); + } while( k != 27 && terminate_signal != 1 ); + return; + } + + /* Footer */ + werase( footer_window ); + nwipe_gui_title( footer_window, selection_footer_benchmark ); + wrefresh( footer_window ); + + int keystroke = 0; + int have_results = 0; + + do + { + nwipe_gui_create_all_windows_on_terminal_resize( 0, selection_footer_benchmark ); + + int wlines, wcols; + getmaxyx( main_window, wlines, wcols ); + + werase( main_window ); + box( main_window, 0, 0 ); + nwipe_gui_title( main_window, " PRNG Benchmark " ); + + // wattron( main_window, A_DIM ); + + mvwprintw( main_window, 2, 2, "RAM-only PRNG throughput test (~%.1fs each).", target_seconds ); + mvwprintw( main_window, 3, 2, "ENTER = run benchmark ESC = back" ); + + int yy = 5; + + if( !have_results ) + { + mvwprintw( main_window, yy++, 2, "No results yet." ); + } + else + { + mvwprintw( main_window, yy++, 2, "Leaderboard (MB/s):" ); + yy++; + + for( int i = 0; i < prng_count && yy < ( wlines - 2 ); i++ ) + { + if( results[i].rc == 0 ) + { + mvwprintw( main_window, + yy++, + 2, + "%2d) %-40.40s %10.1f MB/s", + i + 1, + results[i].prng->label, + results[i].mbps ); + } + else + { + mvwprintw( main_window, + yy++, + 2, + "%2d) %-40.40s (failed: rc=%d)", + i + 1, + results[i].prng->label, + results[i].rc ); + } + } + } + + // wattroff( main_window, A_DIM ); + wrefresh( main_window ); + + timeout( 250 ); + keystroke = getch(); + timeout( -1 ); + + if( keystroke == 27 || keystroke == KEY_BACKSPACE || keystroke == KEY_BREAK ) + { + break; + } + + if( keystroke == 10 || keystroke == KEY_ENTER ) /* ENTER */ + { + /* Run benchmark */ + have_results = 0; + + for( int i = 0; i < prng_count; i++ ) + { + /* Update progress screen */ + werase( main_window ); + box( main_window, 0, 0 ); + nwipe_gui_title( main_window, " PRNG Benchmark " ); + + // wattron( main_window, A_DIM ); + mvwprintw( main_window, 2, 2, "Running %d/%d:", i + 1, prng_count ); + mvwprintw( main_window, 3, 2, "%s", prngs[i]->label ); + mvwprintw( + main_window, 5, 2, "Generating into RAM buffer (%zu MiB blocks)...", io_block / ( 1024 * 1024 ) ); + mvwprintw( main_window, 7, 2, "ESC to abort this benchmark." ); + // wattroff( main_window, A_DIM ); + + wrefresh( main_window ); + + /* Actually benchmark one PRNG */ + nwipe_gui_bench_one_prng( prngs[i], &results[i], io_buf, io_block, target_seconds ); + + /* Allow user abort quickly after each PRNG */ + timeout( 0 ); + int k = getch(); + timeout( -1 ); + if( k == 27 || terminate_signal == 1 ) + { + break; + } + } + + /* Sort results into a leaderboard */ + qsort( results, prng_count, sizeof( results[0] ), nwipe_gui_cmp_bench_desc ); + have_results = 1; + } + + } while( terminate_signal != 1 ); + + free( io_buf ); +} + void nwipe_gui_create_options_window() { /* Create the options window. */ @@ -1339,6 +1620,11 @@ void nwipe_gui_select( int count, nwipe_context_t** c ) /* Run the option dialog. */ nwipe_gui_verify(); break; + case 'o': + validkeyhit = 1; + nwipe_gui_benchmark_prng(); + break; + case 't': validkeyhit = 1; diff --git a/src/gui.h b/src/gui.h index 2285c4a..5a12391 100644 --- a/src/gui.h +++ b/src/gui.h @@ -70,6 +70,7 @@ int nwipe_gui_yes_no_footer( void ); // Change footer to yes no void nwipe_gui_user_defined_tag( void ); // Edit user defined text to be added to pdf report void nwipe_gui_pdf_certificate_edit_user_defined_tag( const char* ); void nwipe_gui_view_device( int count, nwipe_context_t** c, int focus ); +void nwipe_gui_benchmark_prng( void ); /** nwipe_gui_preview_org_customer( int ) * Display a editable preview of organisation, customer and date/time