LCOV - code coverage report
Current view: top level - flamenco/accdb - bench_accdb_mt.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 247 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 5 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : 
       3             : #include "fd_accdb.h"
       4             : #include "../../util/fd_util.h"
       5             : 
       6             : #include <pthread.h>
       7             : #include <sched.h>
       8             : #include <stdlib.h>
       9             : #include <string.h>
      10             : #include <unistd.h>
      11             : #include <sys/mman.h>
      12             : 
      13             : /* bench_accdb_mt: the accdb acquire/release hot path under many
      14             :    concurrent joins, the way exec tiles drive it.
      15             : 
      16             :    N worker threads, each with its own accdb join, run a transaction mix
      17             :    against one shared accdb on one fork: acquire read-only and writable
      18             :    accounts, mutate the writable ones, release with commit or revert.
      19             :    As the replay scheduler guarantees (fd_accdb.h), a writable acquire
      20             :    of an account never overlaps another acquire of it: each worker
      21             :    writes only its own partition of the accounts, and reads either its
      22             :    own partition, a shared range that is never written, or the shared
      23             :    hot read-only accounts.  A background thread runs
      24             :    fd_accdb_background.
      25             : 
      26             :    --accounts      total accounts (default 1M)
      27             :    --threads       worker thread counts to sweep, e.g. 1,4,8,15
      28             :                    (default 4); every step reuses the same accdb
      29             :    --duration      seconds per step (default 10)
      30             :    --cache-gib     cache size, fractional allowed (default 4); a
      31             :                    working set larger than the cache exercises CLOCK
      32             :                    eviction and write-back
      33             :    --cpu0          first CPU workers are pinned to (default 1; the
      34             :                    background thread runs on cpu0-1)
      35             :    --mix           "transfer" (1 RO + 2 RW accounts) or "mainnet" (the
      36             :                    account counts and failure rates of bench_accdb_txn's
      37             :                    mainnet mix); every account is BENCH_DATA_SZ bytes
      38             :    --ro-hot N      N read-only accounts shared by every thread: every
      39             :                    read-only acquire comes from them (as far as the
      40             :                    txn's read-only count allows), like a few hot
      41             :                    programs (default 0: off)
      42             :    --per-thread N  each thread writes its own N accounts (default 0:
      43             :                    the threads split --accounts); --accounts is then
      44             :                    ro-hot + max threads * N
      45             :    --data-sz2 B    every committed write alternates the account between
      46             :                    BENCH_DATA_SZ and B bytes, so each release changes
      47             :                    size class (default 0: off)
      48             :    --preset hot    transfer mix, 4 hot read-only accounts, 512 accounts
      49             :                    per thread, 3 GiB cache: everything stays cached, so
      50             :                    it measures only what the threads share
      51             : 
      52             :    Reports per step total txn/s, per-thread acquire+release ns/txn,
      53             :    throughput speedup over the first step (perfect scaling from 1
      54             :    thread reads as the thread count), acquire/release ns per account
      55             :    and reservation retries (acquire_failed). */
      56             : 
      57             : static uchar dummy_owner[ 32 ] = { 0xEE };
      58             : 
      59           0 : #define BENCH_DATA_SZ           (165UL)
      60           0 : #define BENCH_MAX_ACCTS_PER_TXN (64UL)
      61           0 : #define BENCH_MAX_THREADS       (64UL)
      62           0 : #define BENCH_MAX_LIVE_SLOTS    (64UL)
      63           0 : #define BENCH_PARTITION_CNT     (8192UL)
      64           0 : #define BENCH_PARTITION_SZ      (1UL<<30UL)
      65           0 : #define BENCH_MIN_RESERVED      (192UL)
      66             : 
      67             : /* Transaction archetype: ro_cnt read-only and rw_cnt writable
      68             :    accounts, picked with frequency weight (parts per 10000), reverted
      69             :    with probability fail (parts per 10000). */
      70             : 
      71             : struct txn_archetype {
      72             :   uint ro_cnt;
      73             :   uint rw_cnt;
      74             :   uint weight;
      75             :   uint fail;
      76             : };
      77             : 
      78             : typedef struct txn_archetype txn_archetype_t;
      79             : 
      80             : /* The account counts and failure rates of bench_accdb_txn's mainnet
      81             :    archetypes. */
      82             : 
      83             : static txn_archetype_t const MIX_MAINNET[] = {
      84             :   /* ro rw weight  fail */
      85             :   {  0,  1,   300,   25 },  /* single write */
      86             :   {  1,  2,  5930,   25 },  /* simple transfer/token op */
      87             :   {  2,  3,   330,  400 },  /* simple swap */
      88             :   {  4,  4,   370,  600 },  /* small DeFi */
      89             :   {  6,  6,   505,  700 },  /* medium DeFi / AMM */
      90             :   { 12, 12,  1354, 2700 },  /* complex DeFi */
      91             :   { 24, 24,   891, 3400 },  /* multi-hop DeFi */
      92             :   {  2, 48,   320, 1700 },  /* bulk operations */
      93             : };
      94             : 
      95             : static txn_archetype_t const MIX_TRANSFER[] = {
      96             :   /* ro rw weight  fail */
      97             :   {  1,  2, 10000,    0 },
      98             : };
      99             : 
     100           0 : #define MIX_MAINNET_CNT  (sizeof(MIX_MAINNET)/sizeof(MIX_MAINNET[0]))
     101           0 : #define MIX_TRANSFER_CNT (sizeof(MIX_TRANSFER)/sizeof(MIX_TRANSFER[0]))
     102             : #define MIX_MAX          (8UL)
     103             : 
     104             : /* Shared state, read by the workers.  Set before they start, except
     105             :    g_go and g_stop, which are only accessed with FD_VOLATILE. */
     106             : 
     107             : static txn_archetype_t const * g_mix;
     108             : static ulong                   g_mix_cnt;
     109             : static ulong                   g_accounts;
     110             : static ulong                   g_threads;
     111             : static ulong                   g_ro0;        /* accounts [g_ro0,g_accounts) are never written */
     112             : static ulong                   g_ro_hot;     /* accounts [0,g_ro_hot) are shared read-only */
     113             : static ulong                   g_per_thread;
     114             : static ulong                   g_data_sz2;
     115             : static ulong                   g_cpu0;
     116             : static fd_accdb_fork_id_t      g_fork;
     117             : static int                     g_go;
     118             : static int                     g_stop;
     119             : 
     120             : static void
     121             : make_pubkey( uchar pubkey[ static 32 ],
     122           0 :              ulong idx ) {
     123           0 :   fd_memset( pubkey, 0, 32UL );
     124           0 :   FD_STORE( ulong, pubkey,     idx                 );
     125           0 :   FD_STORE( ulong, pubkey+8UL, fd_ulong_hash( idx ) );
     126           0 : }
     127             : 
     128             : static void
     129           0 : pin_cpu( ulong cpu ) {
     130           0 :   cpu_set_t set;
     131           0 :   CPU_ZERO( &set );
     132           0 :   CPU_SET( cpu, &set );
     133           0 :   if( FD_UNLIKELY( pthread_setaffinity_np( pthread_self(), sizeof(set), &set ) ) ) FD_LOG_WARNING(( "could not pin to cpu %lu", cpu ));
     134           0 : }
     135             : 
     136             : struct __attribute__((aligned(64))) worker {
     137             :   ulong        idx;
     138             :   fd_accdb_t * accdb;
     139             : 
     140             :   ulong        txn_cnt;
     141             :   ulong        acc_cnt;
     142             :   long         acquire_ticks;
     143             :   long         release_ticks;
     144             : };
     145             : 
     146             : typedef struct worker worker_t;
     147             : 
     148             : static void *
     149           0 : worker_main( void * arg ) {
     150           0 :   worker_t * w = (worker_t *)arg;
     151           0 :   pin_cpu( g_cpu0+w->idx );
     152             : 
     153           0 :   fd_rng_t   _rng[1];
     154           0 :   fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, (uint)( 1000UL+w->idx ), 0UL ) );
     155             : 
     156             :   /* This worker writes [part0,part0+part). */
     157           0 :   ulong part  = g_per_thread ? g_per_thread : ( g_ro0-g_ro_hot )/g_threads;
     158           0 :   ulong part0 = g_ro_hot + w->idx*part;
     159             : 
     160           0 :   uint cumul[ MIX_MAX ];
     161           0 :   uint sum = 0U;
     162           0 :   for( ulong i=0UL; i<g_mix_cnt; i++ ) {
     163           0 :     sum       += g_mix[ i ].weight;
     164           0 :     cumul[ i ] = sum;
     165           0 :   }
     166             : 
     167           0 :   uchar         pubkeys [ BENCH_MAX_ACCTS_PER_TXN ][ 32 ];
     168           0 :   uchar const * pks     [ BENCH_MAX_ACCTS_PER_TXN ];
     169           0 :   ulong         ids     [ BENCH_MAX_ACCTS_PER_TXN ];
     170           0 :   int           writable[ BENCH_MAX_ACCTS_PER_TXN ];
     171           0 :   fd_acc_t      accs    [ BENCH_MAX_ACCTS_PER_TXN ];
     172           0 :   for( ulong i=0UL; i<BENCH_MAX_ACCTS_PER_TXN; i++ ) pks[ i ] = pubkeys[ i ];
     173             : 
     174           0 :   while( !FD_VOLATILE_CONST( g_go ) ) FD_SPIN_PAUSE();
     175             : 
     176           0 :   while( !FD_VOLATILE_CONST( g_stop ) ) {
     177           0 :     uint  r = fd_rng_uint( rng )%sum;
     178           0 :     ulong a = 0UL;
     179           0 :     while( r>=cumul[ a ] ) a++;
     180           0 :     txn_archetype_t const * arch = &g_mix[ a ];
     181           0 :     ulong rw_cnt = (ulong)arch->rw_cnt;
     182           0 :     ulong cnt    = rw_cnt + (ulong)arch->ro_cnt;
     183             : 
     184             :     /* Distinct accounts within a txn: writable ones from our
     185             :        partition, read-only ones from the hot set first, then the
     186             :        never-written range or our partition. */
     187           0 :     for( ulong i=0UL; i<cnt; i++ ) {
     188           0 :       ulong id;
     189           0 :       for(;;) {
     190           0 :         if(      i<rw_cnt                  ) id = part0 + fd_rng_ulong( rng )%part;
     191           0 :         else if( i-rw_cnt<g_ro_hot         ) id = fd_rng_ulong( rng )%g_ro_hot;
     192           0 :         else if( g_ro0<g_accounts &&
     193           0 :                  ( fd_rng_uint( rng )&1U ) ) id = g_ro0 + fd_rng_ulong( rng )%( g_accounts-g_ro0 );
     194           0 :         else                                 id = part0 + fd_rng_ulong( rng )%part;
     195           0 :         int dup = 0;
     196           0 :         for( ulong j=0UL; j<i; j++ ) dup |= ids[ j ]==id;
     197           0 :         if( FD_LIKELY( !dup ) ) break;
     198           0 :       }
     199           0 :       ids[ i ] = id;
     200           0 :       make_pubkey( pubkeys[ i ], id );
     201           0 :       writable[ i ] = i<rw_cnt;
     202           0 :     }
     203           0 :     memset( accs, 0, cnt*sizeof(fd_acc_t) );
     204             : 
     205           0 :     long tacq_start = fd_tickcount();
     206           0 :     fd_accdb_acquire( w->accdb, g_fork, cnt, pks, writable, accs );
     207           0 :     long tacq = fd_tickcount()-tacq_start;
     208             : 
     209           0 :     int commit = ( fd_rng_uint( rng )%10000U )>=arch->fail;
     210           0 :     for( ulong i=0UL; i<rw_cnt; i++ ) {
     211           0 :       accs[ i ].commit = commit;
     212           0 :       if( !commit ) continue;
     213           0 :       if( FD_LIKELY( accs[ i ].data && accs[ i ].data_len ) ) accs[ i ].data[ 0 ] ^= 1;
     214           0 :       if( g_data_sz2 ) accs[ i ].data_len = accs[ i ].data_len==g_data_sz2 ? BENCH_DATA_SZ : g_data_sz2;
     215           0 :     }
     216             : 
     217           0 :     long trel_start = fd_tickcount();
     218           0 :     fd_accdb_release( w->accdb, cnt, accs );
     219           0 :     long trel = fd_tickcount()-trel_start;
     220             : 
     221           0 :     w->txn_cnt++;
     222           0 :     w->acc_cnt += cnt;
     223           0 :     w->acquire_ticks += tacq;
     224           0 :     w->release_ticks += trel;
     225           0 :   }
     226             : 
     227           0 :   fd_rng_delete( fd_rng_leave( rng ) );
     228           0 :   return NULL;
     229           0 : }
     230             : 
     231             : static void *
     232           0 : background_main( void * arg ) {
     233           0 :   fd_accdb_t * accdb = (fd_accdb_t *)arg;
     234           0 :   pin_cpu( g_cpu0 ? g_cpu0-1UL : 0UL );
     235           0 :   while( !FD_VOLATILE_CONST( g_stop ) ) {
     236           0 :     int charge_busy = 0;
     237           0 :     fd_accdb_background( accdb, &charge_busy );
     238           0 :     if( !charge_busy ) FD_SPIN_PAUSE();
     239           0 :   }
     240           0 :   return NULL;
     241           0 : }
     242             : 
     243             : int
     244             : main( int     argc,
     245           0 :       char ** argv ) {
     246           0 :   fd_boot( &argc, &argv );
     247             : 
     248           0 :   char const * preset = fd_env_strip_cmdline_cstr( &argc, &argv, "--preset", NULL, "" );
     249           0 :   int hot = !strcmp( preset, "hot" );
     250           0 :   if( FD_UNLIKELY( preset[0] && !hot ) ) FD_LOG_ERR(( "unknown --preset %s", preset ));
     251             : 
     252           0 :   g_accounts               = fd_env_strip_cmdline_ulong ( &argc, &argv, "--accounts",   NULL, 1000000UL           );
     253           0 :   char const * threads_str = fd_env_strip_cmdline_cstr  ( &argc, &argv, "--threads",    NULL, "4"                 );
     254           0 :   ulong        duration_s  = fd_env_strip_cmdline_ulong ( &argc, &argv, "--duration",   NULL, 10UL                );
     255           0 :   double       cache_gib   = fd_env_strip_cmdline_double( &argc, &argv, "--cache-gib",  NULL, hot ? 3.0   : 4.0   );
     256           0 :   g_cpu0                   = fd_env_strip_cmdline_ulong ( &argc, &argv, "--cpu0",       NULL, 1UL                 );
     257           0 :   char const * mix         = fd_env_strip_cmdline_cstr  ( &argc, &argv, "--mix",        NULL, "transfer"          );
     258           0 :   g_ro_hot                 = fd_env_strip_cmdline_ulong ( &argc, &argv, "--ro-hot",     NULL, hot ? 4UL   : 0UL   );
     259           0 :   g_per_thread             = fd_env_strip_cmdline_ulong ( &argc, &argv, "--per-thread", NULL, hot ? 512UL : 0UL   );
     260           0 :   g_data_sz2               = fd_env_strip_cmdline_ulong ( &argc, &argv, "--data-sz2",   NULL, 0UL                 );
     261             : 
     262           0 :   if(      !strcmp( mix, "mainnet"  ) ) { g_mix = MIX_MAINNET;  g_mix_cnt = MIX_MAINNET_CNT;  }
     263           0 :   else if( !strcmp( mix, "transfer" ) ) { g_mix = MIX_TRANSFER; g_mix_cnt = MIX_TRANSFER_CNT; }
     264           0 :   else FD_LOG_ERR(( "unknown --mix %s", mix ));
     265           0 :   FD_TEST( g_mix_cnt<=MIX_MAX );
     266           0 :   FD_TEST( g_data_sz2<=(10UL<<20UL) );
     267             : 
     268           0 :   ulong sweep[ BENCH_MAX_THREADS ];
     269           0 :   ulong sweep_cnt   = 0UL;
     270           0 :   ulong max_threads = 0UL;
     271           0 :   for( char const * p=threads_str; *p; ) {
     272           0 :     char * end;
     273           0 :     ulong  t = strtoul( p, &end, 10 );
     274           0 :     FD_TEST( end!=p && t && t<=BENCH_MAX_THREADS && sweep_cnt<BENCH_MAX_THREADS );
     275           0 :     sweep[ sweep_cnt++ ] = t;
     276           0 :     max_threads = fd_ulong_max( max_threads, t );
     277           0 :     p = *end==',' ? end+1 : end;
     278           0 :   }
     279           0 :   if( FD_UNLIKELY( !sweep_cnt   ) ) FD_LOG_ERR(( "--threads needs at least one thread count" ));
     280           0 :   if( FD_UNLIKELY( !duration_s ) ) FD_LOG_ERR(( "--duration must be positive" ));
     281             : 
     282             :   /* Each txn picks distinct accounts: its writable ones and the
     283             :      read-only ones beyond the hot set may all come from the worker's
     284             :      partition, so the partition must hold that many for every
     285             :      archetype (else the duplicate-rejection loop never ends). */
     286           0 :   ulong max_part_cnt = 0UL;
     287           0 :   for( ulong i=0UL; i<g_mix_cnt; i++ ) {
     288           0 :     ulong ro_cold = (ulong)g_mix[ i ].ro_cnt - fd_ulong_min( (ulong)g_mix[ i ].ro_cnt, g_ro_hot );
     289           0 :     max_part_cnt = fd_ulong_max( max_part_cnt, (ulong)g_mix[ i ].rw_cnt + ro_cold );
     290           0 :   }
     291           0 :   if( g_per_thread ) {
     292             :     /* Hot read-only accounts, then one partition per thread; no
     293             :        never-written range. */
     294           0 :     if( FD_UNLIKELY( g_per_thread<max_part_cnt ) ) FD_LOG_ERR(( "--per-thread %lu < %lu accounts some txn needs", g_per_thread, max_part_cnt ));
     295           0 :     g_accounts = g_ro_hot + max_threads*g_per_thread;
     296           0 :     g_ro0      = g_accounts;
     297           0 :   } else {
     298           0 :     g_ro0 = g_accounts - g_accounts/8UL;
     299           0 :     if( FD_UNLIKELY( g_ro0<=g_ro_hot || ( g_ro0-g_ro_hot )/max_threads<max_part_cnt ) )
     300           0 :       FD_LOG_ERR(( "--accounts %lu too small: partitions need %lu accounts each", g_accounts, max_part_cnt ));
     301           0 :   }
     302             : 
     303           0 :   int fd = memfd_create( "accdb_mt", 0 );
     304           0 :   if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "memfd_create failed" ));
     305             : 
     306           0 :   ulong cache_fp     = (ulong)( cache_gib*(double)(1UL<<30UL) );
     307           0 :   ulong joiner_cnt   = max_threads+2UL;
     308           0 :   ulong max_accounts = 2UL*g_accounts;
     309           0 :   ulong writes       = g_accounts+4096UL;
     310           0 :   ulong shmem_fp     = fd_accdb_shmem_footprint( max_accounts, BENCH_MAX_LIVE_SLOTS, writes, BENCH_PARTITION_CNT,
     311           0 :                                                  cache_fp, BENCH_MIN_RESERVED, joiner_cnt, 0UL );
     312           0 :   FD_TEST( shmem_fp );
     313           0 :   void * shmem_mem = mmap( NULL, shmem_fp, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0 );
     314           0 :   FD_TEST( shmem_mem!=MAP_FAILED );
     315           0 :   fd_accdb_shmem_t * shmem = fd_accdb_shmem_join(
     316           0 :       fd_accdb_shmem_new( shmem_mem, max_accounts, BENCH_MAX_LIVE_SLOTS, writes, BENCH_PARTITION_CNT,
     317           0 :                           BENCH_PARTITION_SZ, cache_fp, BENCH_MIN_RESERVED, 0, 42UL, joiner_cnt, 0UL ) );
     318           0 :   FD_TEST( shmem );
     319             : 
     320             :   /* One join per worker, plus the background join (compaction). */
     321           0 :   fd_accdb_t * joins[ BENCH_MAX_THREADS+1UL ];
     322           0 :   for( ulong i=0UL; i<=max_threads; i++ ) {
     323           0 :     int    compaction = i==max_threads;
     324           0 :     void * mem        = aligned_alloc( fd_accdb_align(), fd_accdb_footprint( BENCH_MAX_LIVE_SLOTS, compaction ) );
     325           0 :     FD_TEST( mem );
     326           0 :     joins[ i ] = fd_accdb_join( fd_accdb_new( mem, shmem, fd, 0UL, NULL, NULL, 0UL, compaction ) );
     327           0 :     FD_TEST( joins[ i ] );
     328           0 :   }
     329           0 :   fd_accdb_t * bg = joins[ max_threads ];
     330             : 
     331             :   /* Populate on a fork, root it (hot accounts are normally rooted), and
     332             :      run on a child. */
     333           0 :   fd_accdb_fork_id_t root = fd_accdb_attach_child( joins[ 0 ], (fd_accdb_fork_id_t){ .val = USHORT_MAX } );
     334           0 :   g_fork = fd_accdb_attach_child( joins[ 0 ], root );
     335           0 :   FD_LOG_NOTICE(( "populating %lu accounts ...", g_accounts ));
     336           0 :   for( ulong i=0UL; i<g_accounts; i++ ) {
     337           0 :     uchar pubkey[ 32 ];
     338           0 :     make_pubkey( pubkey, i );
     339           0 :     uchar const * pks[1] = { pubkey };
     340           0 :     int           wr [1] = { 1 };
     341           0 :     fd_acc_t      acc[1];
     342           0 :     memset( acc, 0, sizeof(acc) );
     343           0 :     fd_accdb_acquire( joins[ 0 ], g_fork, 1UL, pks, wr, acc );
     344           0 :     acc[0].lamports = i+1UL;
     345           0 :     acc[0].data_len = BENCH_DATA_SZ;
     346           0 :     memcpy( acc[0].owner, dummy_owner, 32UL );
     347           0 :     memset( acc[0].data, (uchar)i, BENCH_DATA_SZ );
     348           0 :     acc[0].commit = 1;
     349           0 :     fd_accdb_release( joins[ 0 ], 1UL, acc );
     350           0 :   }
     351           0 :   fd_accdb_advance_root( joins[ 0 ], g_fork );
     352           0 :   for( ulong i=0UL; i<64UL; i++ ) {
     353           0 :     int charge_busy = 0;
     354           0 :     fd_accdb_background( bg, &charge_busy );
     355           0 :   }
     356           0 :   g_fork = fd_accdb_attach_child( joins[ 0 ], g_fork );
     357             : 
     358             :   /* Ticks per ns, to convert the workers' tick counts. */
     359           0 :   long   cal_ns0     = fd_log_wallclock();
     360           0 :   long   cal_ticks0  = fd_tickcount();
     361           0 :   while( fd_log_wallclock()-cal_ns0<20000000L ) FD_SPIN_PAUSE();
     362           0 :   double tick_per_ns = (double)( fd_tickcount()-cal_ticks0 )/(double)( fd_log_wallclock()-cal_ns0 );
     363             : 
     364           0 :   worker_t * workers = aligned_alloc( alignof(worker_t), BENCH_MAX_THREADS*sizeof(worker_t) );
     365           0 :   FD_TEST( workers );
     366           0 :   pthread_t threads[ BENCH_MAX_THREADS ];
     367           0 :   double    tps0 = 0.0;
     368             : 
     369           0 :   for( ulong s=0UL; s<sweep_cnt; s++ ) {
     370           0 :     g_threads = sweep[ s ];
     371           0 :     FD_VOLATILE( g_go   ) = 0;
     372           0 :     FD_VOLATILE( g_stop ) = 0;
     373             : 
     374           0 :     ulong failed0 = 0UL;
     375           0 :     for( ulong i=0UL; i<g_threads; i++ ) failed0 += fd_accdb_metrics( joins[ i ] )->acquire_failed;
     376             : 
     377           0 :     pthread_t bg_thread;
     378           0 :     FD_TEST( !pthread_create( &bg_thread, NULL, background_main, bg ) );
     379           0 :     memset( workers, 0, g_threads*sizeof(worker_t) );
     380           0 :     for( ulong i=0UL; i<g_threads; i++ ) {
     381           0 :       workers[ i ].idx   = i;
     382           0 :       workers[ i ].accdb = joins[ i ];
     383           0 :       FD_TEST( !pthread_create( &threads[ i ], NULL, worker_main, &workers[ i ] ) );
     384           0 :     }
     385             : 
     386           0 :     long start = fd_log_wallclock();
     387           0 :     FD_VOLATILE( g_go ) = 1;
     388           0 :     while( fd_log_wallclock()-start<(long)duration_s*1000000000L ) usleep( 10000 );
     389           0 :     FD_VOLATILE( g_stop ) = 1;
     390           0 :     long elapsed = fd_log_wallclock()-start;
     391           0 :     for( ulong i=0UL; i<g_threads; i++ ) FD_TEST( !pthread_join( threads[ i ], NULL ) );
     392           0 :     FD_TEST( !pthread_join( bg_thread, NULL ) );
     393             : 
     394           0 :     ulong  txn_cnt       = 0UL;
     395           0 :     ulong  acc_cnt       = 0UL;
     396           0 :     ulong  failed        = 0UL;
     397           0 :     double acquire_ticks = 0.0;
     398           0 :     double release_ticks = 0.0;
     399           0 :     for( ulong i=0UL; i<g_threads; i++ ) {
     400           0 :       txn_cnt       += workers[ i ].txn_cnt;
     401           0 :       acc_cnt       += workers[ i ].acc_cnt;
     402           0 :       acquire_ticks += (double)workers[ i ].acquire_ticks;
     403           0 :       release_ticks += (double)workers[ i ].release_ticks;
     404           0 :       failed        += fd_accdb_metrics( joins[ i ] )->acquire_failed;
     405           0 :     }
     406           0 :     double secs   = (double)elapsed/1e9;
     407           0 :     double ns_txn = ( acquire_ticks+release_ticks )/tick_per_ns/(double)txn_cnt; /* per thread */
     408           0 :     double tps    = (double)txn_cnt/secs;
     409           0 :     if( !s ) tps0 = tps;
     410           0 :     FD_LOG_NOTICE(( "RESULT mix=%s threads=%lu accounts=%lu cache_gib=%.2f ro_hot=%lu per_thread=%lu data_sz2=%lu: "
     411           0 :                     "%.0f txn/s, per thread %.0f txn/s, %.0f ns/txn, scaling %.2fx, acquire %.0f ns/acc, release %.0f ns/acc, reserve retries %lu",
     412           0 :                     mix, g_threads, g_accounts, cache_gib, g_ro_hot, g_per_thread, g_data_sz2,
     413           0 :                     tps, tps/(double)g_threads, ns_txn, tps/tps0,
     414           0 :                     acquire_ticks/tick_per_ns/(double)acc_cnt, release_ticks/tick_per_ns/(double)acc_cnt, failed-failed0 ));
     415           0 :   }
     416             : 
     417           0 :   free( workers );
     418           0 :   for( ulong i=0UL; i<=max_threads; i++ ) free( joins[ i ] );
     419           0 :   munmap( shmem_mem, shmem_fp );
     420           0 :   close( fd );
     421           0 :   fd_halt();
     422           0 :   return 0;
     423           0 : }

Generated by: LCOV version 1.14