Line data Source code
1 : /* fd_accdb_ctl.c is a command-line debugging tool for interacting with
2 : a Firedancer account database. */
3 :
4 : #include "../../vinyl/fd_vinyl.h"
5 : #include "../../flamenco/fd_flamenco_base.h"
6 : #include "../../ballet/base58/fd_base58.h"
7 : #include "../../util/cstr/fd_cstr.h"
8 : #include "../../util/pod/fd_pod.h"
9 : #include <ctype.h>
10 : #include <stddef.h> /* offsetof */
11 : #include <stdio.h>
12 :
13 : /* req_info contains various request metadata R/W mapped into the vinyl
14 : tile. */
15 :
16 : struct req_info {
17 : fd_vinyl_key_t key[1];
18 : ulong val_gaddr[1];
19 : schar err[1];
20 : fd_vinyl_comp_t comp[1];
21 : };
22 :
23 : typedef struct req_info req_info_t;
24 :
25 : /* The client class contains local handles to client-related vinyl
26 : objects. */
27 :
28 : struct client {
29 : fd_vinyl_rq_t * rq;
30 : fd_vinyl_cq_t * cq;
31 : ulong req_id;
32 : ulong link_id;
33 :
34 : fd_vinyl_meta_t * meta;
35 :
36 : req_info_t * req_info;
37 : ulong req_info_gaddr;
38 : fd_wksp_t * val_wksp;
39 : fd_wksp_t * client_wksp;
40 :
41 : /* Vinyl client status */
42 : ulong quota_rem;
43 : ulong cq_seq;
44 : };
45 :
46 : typedef struct client client_t;
47 :
48 : static char const bin2hex[ 16 ] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
49 :
50 : static void
51 : hexdump( uchar const * data,
52 0 : uint sz ) {
53 0 : ulong sz_align = fd_ulong_align_dn( sz, 16UL );
54 0 : uint i;
55 0 : for( i=0U; i<sz_align; i+=16U ) {
56 0 : char line[ 80 ];
57 0 : char * p = fd_cstr_init( line );
58 0 : p = fd_cstr_append_uint_as_hex( p, '0', i, 7UL );
59 0 : p = fd_cstr_append_text( p, ": ", 3UL );
60 0 : for( ulong j=0UL; j<16UL; j++ ) {
61 0 : p = fd_cstr_append_char( p, bin2hex[ data[ i+j ]>>4 ] );
62 0 : p = fd_cstr_append_char( p, bin2hex[ data[ i+j ]&15 ] );
63 0 : p = fd_cstr_append_char( p, ' ' );
64 0 : }
65 0 : p = fd_cstr_append_char( p, ' ' );
66 0 : for( ulong j=0UL; j<16UL; j++ ) {
67 0 : int c = data[ i+j ];
68 0 : p = fd_cstr_append_char( p, fd_char_if( fd_isalnum( c ) | fd_ispunct( c ) | (c==' '), (char)c, '.' ) );
69 0 : }
70 0 : p = fd_cstr_append_char( p, '\n' );
71 0 : ulong len = (ulong)( p-line );
72 0 : fd_cstr_fini( p );
73 0 : fwrite( line, 1UL, len, stdout );
74 0 : }
75 0 : if( sz ) {
76 0 : char line[ 80 ];
77 0 : char * p = fd_cstr_init( line );
78 0 : p = fd_cstr_append_uint_as_hex( p, '0', i, 7UL );
79 0 : p = fd_cstr_append_text( p, ": ", 3UL );
80 0 : for( ; i<sz; i++ ) {
81 0 : p = fd_cstr_append_char( p, bin2hex[ data[ i ]>>4 ] );
82 0 : p = fd_cstr_append_char( p, bin2hex[ data[ i ]&15 ] );
83 0 : p = fd_cstr_append_char( p, ' ' );
84 0 : }
85 0 : p = fd_cstr_append_char( p, '\n' );
86 0 : ulong len = (ulong)( p-line );
87 0 : fd_cstr_fini( p );
88 0 : fwrite( line, 1UL, len, stdout );
89 0 : }
90 0 : fflush( stdout );
91 0 : }
92 :
93 : static void
94 : client_query( client_t * client,
95 : char ** arg,
96 0 : ulong arg_cnt ) {
97 0 : req_info_t * req_info = client->req_info;
98 0 : if( FD_UNLIKELY( arg_cnt!=1UL ) ) {
99 0 : puts( "ERR(query): invalid query command, usage is \"query <account address>\"" );
100 0 : return;
101 0 : }
102 0 : char const * acc_addr_b58 = arg[0];
103 0 : fd_vinyl_key_t * acc_key = req_info->key;
104 0 : if( FD_UNLIKELY( !fd_base58_decode_32( acc_addr_b58, acc_key->uc ) ) ) {
105 0 : puts( "ERR(query): invalid account address" );
106 0 : return;
107 0 : }
108 :
109 : /* Send an acquire request */
110 :
111 0 : req_info->comp->seq = 0UL;
112 0 : fd_vinyl_rq_send(
113 0 : client->rq,
114 0 : client->req_id++,
115 0 : client->link_id,
116 0 : FD_VINYL_REQ_TYPE_ACQUIRE, /* type */
117 0 : 0UL, /* flags */
118 0 : 1UL,
119 0 : FD_VINYL_VAL_MAX, /* val_max */
120 0 : /* key_gaddr */ client->req_info_gaddr + offsetof( req_info_t, key ),
121 0 : /* val_gaddr_gaddr */ client->req_info_gaddr + offsetof( req_info_t, val_gaddr ),
122 0 : /* err_gaddr */ client->req_info_gaddr + offsetof( req_info_t, err ),
123 0 : /* comp_gaddr */ client->req_info_gaddr + offsetof( req_info_t, comp )
124 0 : );
125 :
126 : /* Poll direct completion for acquire (not via CQ) */
127 :
128 0 : fd_vinyl_comp_t * comp = req_info->comp;
129 0 : while( FD_VOLATILE_CONST( comp->seq )!=1UL ) FD_SPIN_PAUSE();
130 0 : int acquire_err = req_info->err[0];
131 0 : if( acquire_err==FD_VINYL_SUCCESS ) {
132 0 : fd_account_meta_t const * val = fd_wksp_laddr_fast( client->val_wksp, req_info->val_gaddr[0] );
133 0 : void const * data = (void const *)( val+1 );
134 :
135 0 : FD_BASE58_ENCODE_32_BYTES( val->owner, owner_b58 );
136 0 : printf(
137 0 : "\n"
138 0 : "Public Key: %s\n"
139 0 : "Balance: %lu.%lu SOL\n"
140 0 : "Owner: %s\n"
141 0 : "Executable: %s\n"
142 0 : "Length: %u (0x%x) bytes\n",
143 0 : acc_addr_b58,
144 0 : val->lamports / 1000000000UL,
145 0 : val->lamports % 1000000000UL,
146 0 : owner_b58,
147 0 : val->executable ? "true" : "false",
148 0 : val->dlen,
149 0 : val->dlen
150 0 : );
151 0 : hexdump( data, val->dlen );
152 :
153 : /* Send a release request */
154 :
155 0 : req_info->comp->seq = 0UL;
156 0 : fd_vinyl_rq_send(
157 0 : client->rq,
158 0 : client->req_id++,
159 0 : client->link_id,
160 0 : FD_VINYL_REQ_TYPE_RELEASE, /* type */
161 0 : 0UL, /* flags */
162 0 : 1UL,
163 0 : FD_VINYL_VAL_MAX, /* val_max */
164 0 : 0UL,
165 0 : /* val_gaddr_gaddr */ client->req_info_gaddr + offsetof( req_info_t, val_gaddr ),
166 0 : /* err_gaddr */ client->req_info_gaddr + offsetof( req_info_t, err ),
167 0 : /* comp_gaddr */ client->req_info_gaddr + offsetof( req_info_t, comp )
168 0 : );
169 :
170 : /* Poll direct completion for release (not via CQ) */
171 :
172 0 : while( FD_VOLATILE_CONST( comp->seq )!=1UL ) FD_SPIN_PAUSE();
173 0 : FD_TEST( req_info->err[0]==FD_VINYL_SUCCESS );
174 :
175 0 : puts( "" );
176 0 : } else if( acquire_err==FD_VINYL_ERR_KEY ) {
177 0 : printf(
178 0 : "\n"
179 0 : "Public Key: %s\n"
180 0 : "Account does not exist\n"
181 0 : "\n",
182 0 : acc_addr_b58
183 0 : );
184 0 : } else {
185 0 : FD_LOG_ERR(( "Vinyl acquire request failed (err %i-%s)", acquire_err, fd_vinyl_strerror( acquire_err ) ));
186 0 : }
187 0 : }
188 :
189 : typedef struct batch_req batch_req_t;
190 : struct batch_req {
191 : batch_req_t * prev;
192 : batch_req_t * next;
193 :
194 : ulong key_off;
195 : ulong err_off;
196 : ulong val_gaddr_off;
197 :
198 : ulong req_id;
199 : };
200 :
201 : static ulong
202 0 : batch_req_align( void ) {
203 0 : return fd_ulong_max( alignof(batch_req_t), alignof(fd_vinyl_key_t) );
204 0 : }
205 :
206 : static ulong
207 0 : batch_req_footprint( ulong depth ) {
208 0 : ulong l = FD_LAYOUT_INIT;
209 0 : l = FD_LAYOUT_APPEND( l, alignof(batch_req_t), sizeof(batch_req_t) );
210 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_vinyl_key_t), depth*sizeof(fd_vinyl_key_t) );
211 0 : l = FD_LAYOUT_APPEND( l, alignof(schar), depth*sizeof(schar) );
212 0 : l = FD_LAYOUT_APPEND( l, alignof(ulong), depth*sizeof(ulong) );
213 0 : return FD_LAYOUT_FINI( l, batch_req_align() );
214 0 : }
215 :
216 : static batch_req_t *
217 : batch_req_new( void * mem,
218 0 : ulong depth ) {
219 0 : FD_SCRATCH_ALLOC_INIT( l, mem );
220 0 : batch_req_t * req = FD_SCRATCH_ALLOC_APPEND( l, alignof(batch_req_t), sizeof(batch_req_t) );
221 0 : fd_vinyl_key_t * key = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_vinyl_key_t), depth*sizeof(fd_vinyl_key_t) );
222 0 : schar * err = FD_SCRATCH_ALLOC_APPEND( l, alignof(schar), depth*sizeof(schar) );
223 0 : ulong * val_gaddr = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), depth*sizeof(ulong) );
224 0 : FD_SCRATCH_ALLOC_FINI( l, batch_req_align() );
225 :
226 0 : *req = (batch_req_t) {
227 0 : .prev = NULL,
228 0 : .next = NULL,
229 :
230 0 : .key_off = (ulong)key - (ulong)mem,
231 0 : .err_off = (ulong)err - (ulong)mem,
232 0 : .val_gaddr_off = (ulong)val_gaddr - (ulong)mem
233 0 : };
234 0 : return req;
235 0 : }
236 :
237 : static inline fd_vinyl_key_t *
238 0 : batch_req_key( batch_req_t * req ) {
239 0 : return (fd_vinyl_key_t *)( (ulong)req + req->key_off );
240 0 : }
241 :
242 : static inline schar *
243 0 : batch_req_err( batch_req_t * req ) {
244 0 : return (schar *)( (ulong)req + req->err_off );
245 0 : }
246 :
247 : static inline ulong *
248 0 : batch_req_val_gaddr( batch_req_t * req ) {
249 0 : return (ulong *)( (ulong)req + req->val_gaddr_off );
250 0 : }
251 :
252 : struct bench_query_rand {
253 : batch_req_t * req_free; /* free entries */
254 : batch_req_t * req_wait_lo; /* list of entries awaiting completion */
255 : batch_req_t * req_wait_hi;
256 : ulong batch_depth;
257 :
258 : ulong iter_rem;
259 : fd_vinyl_key_t * sample;
260 : ulong sample_idx;
261 : ulong sample_max;
262 :
263 : ulong found_cnt;
264 : ulong miss_cnt;
265 : };
266 : typedef struct bench_query_rand bench_query_rand_t;
267 :
268 : /* bqr_free_push adds a wait queue entry to the free stack. */
269 :
270 : static void
271 : bqr_free_push( bench_query_rand_t * bqr,
272 0 : batch_req_t * req ) {
273 0 : req->prev = NULL;
274 0 : req->next = bqr->req_free;
275 0 : if( bqr->req_free ) bqr->req_free->prev = req;
276 0 : bqr->req_free = req;
277 0 : }
278 :
279 : /* bqr_free_pop removes a wait queue entry from the free stack (alloc). */
280 :
281 : static batch_req_t *
282 0 : bqr_free_pop( bench_query_rand_t * bqr ) {
283 0 : batch_req_t * req = bqr->req_free;
284 0 : bqr->req_free = req->next;
285 0 : if( bqr->req_free ) bqr->req_free->prev = NULL;
286 0 : req->prev = req->next = NULL;
287 0 : return req;
288 0 : }
289 :
290 : /* bqr_wait_push adds a new wait queue entry. */
291 :
292 : static void
293 : bqr_wait_push( bench_query_rand_t * bqr,
294 0 : batch_req_t * req ) {
295 0 : req->prev = bqr->req_wait_hi;
296 0 : req->next = NULL;
297 0 : if( bqr->req_wait_hi ) bqr->req_wait_hi->next = req;
298 0 : bqr->req_wait_hi = req;
299 0 : if( !bqr->req_wait_lo ) bqr->req_wait_lo = req;
300 0 : }
301 :
302 : /* bqr_wait_pop removes the oldest wait queue entry. */
303 :
304 : static batch_req_t *
305 0 : bqr_wait_pop( bench_query_rand_t * bqr ) {
306 0 : batch_req_t * req = bqr->req_wait_lo;
307 0 : bqr->req_wait_lo = req->next;
308 0 : req->prev = req->next = NULL;
309 0 : if( bqr->req_wait_lo ) bqr->req_wait_lo->prev = NULL;
310 0 : else bqr->req_wait_hi = NULL;
311 0 : return req;
312 0 : }
313 :
314 : /* bqr_req_release sends a batch RELEASE request for a batch of values.
315 : Completions arriving for RELEASE will replenish quota. */
316 :
317 : static void
318 : bqr_req_release( client_t * client,
319 : bench_query_rand_t * bqr,
320 : batch_req_t * req,
321 0 : uint cnt ) {
322 0 : FD_CRIT( !req->prev && !req->next, "attempt to release a request that is already free or still pending" );
323 :
324 0 : schar * err = batch_req_err( req );
325 0 : for( uint i=0U; i<cnt; i++ ) err[ i ] = FD_VINYL_SUCCESS;
326 :
327 0 : ulong req_id = fd_ulong_set_bit( client->req_id++, 63 );
328 0 : ulong link_id = client->link_id;
329 0 : int type = FD_VINYL_REQ_TYPE_RELEASE;
330 0 : ulong flags = 0UL;
331 0 : ulong batch_cnt = (ulong)cnt;
332 0 : ulong val_gaddr_gaddr = fd_wksp_gaddr_fast( client->client_wksp, batch_req_val_gaddr( req ) );
333 0 : ulong err_gaddr = fd_wksp_gaddr_fast( client->client_wksp, err );
334 0 : fd_vinyl_rq_send( client->rq, req_id, link_id, type, flags, batch_cnt, 0UL, 0UL, val_gaddr_gaddr, err_gaddr, 0UL );
335 :
336 0 : req->req_id = req_id;
337 0 : bqr_wait_push( bqr, req );
338 0 : }
339 :
340 : /* bqr_handle_cq handles an ACQUIRE or RELEASE completion. */
341 :
342 : static void
343 : bqr_handle_cq( client_t * client,
344 : bench_query_rand_t * bqr,
345 0 : fd_vinyl_comp_t * comp ) {
346 0 : FD_CRIT( bqr->req_wait_lo, "received completion even though no request is pending" );
347 0 : batch_req_t * req = bqr_wait_pop( bqr );
348 0 : FD_CRIT( req->req_id==comp->req_id, "received completion for unexpected req_id" );
349 0 : FD_CRIT( comp->batch_cnt<=bqr->batch_depth, "corrupt comp->batch_cnt" );
350 :
351 : /* The high bit of the request ID indicates whether this was an
352 : ACQUIRE or RELEASE request. */
353 0 : int const is_release = fd_ulong_extract_bit( comp->req_id, 63 );
354 :
355 0 : fd_vinyl_key_t * key = batch_req_key( req );
356 0 : ulong * val_gaddr = batch_req_val_gaddr( req );
357 0 : schar * err = batch_req_err( req );
358 :
359 0 : if( !is_release ) {
360 :
361 0 : uint j=0U;
362 0 : for( uint i=0U; i<comp->batch_cnt; i++ ) {
363 0 : int e = err[ i ];
364 0 : if( FD_UNLIKELY( e!=FD_VINYL_SUCCESS && e!=FD_VINYL_ERR_KEY ) ) {
365 0 : FD_LOG_CRIT(( "Unexpected vinyl error %i-%s", e, fd_vinyl_strerror( e ) ));
366 0 : }
367 0 : if( e==FD_VINYL_SUCCESS ) {
368 0 : bqr->found_cnt++;
369 0 : key [ j ] = key[ i ];
370 0 : val_gaddr[ j ] = val_gaddr[ i ];
371 0 : j++;
372 0 : } else {
373 0 : bqr->miss_cnt++;
374 0 : client->quota_rem++;
375 0 : }
376 0 : }
377 :
378 0 : if( j ) bqr_req_release( client, bqr, req, j );
379 0 : else bqr_free_push( bqr, req );
380 :
381 0 : } else {
382 :
383 0 : schar * err = batch_req_err( req );
384 0 : uint cnt = comp->batch_cnt;
385 0 : for( uint i=0U; i<cnt; i++ ) {
386 0 : int e = err[ i ];
387 0 : if( FD_UNLIKELY( e ) ) {
388 0 : FD_LOG_CRIT(( "Unexpected vinyl error for req %u %i-%s", i, e, fd_vinyl_strerror( e ) ));
389 0 : }
390 0 : }
391 0 : client->quota_rem += comp->batch_cnt;
392 0 : bqr_free_push( bqr, req );
393 :
394 0 : }
395 :
396 0 : }
397 :
398 : /* bqr_drain_cq drains all completion queue entries. */
399 :
400 : static void
401 : bqr_drain_cq( client_t * client,
402 0 : bench_query_rand_t * bqr ) {
403 0 : for(;;) {
404 0 : fd_vinyl_comp_t comp[1];
405 0 : long cq_err = fd_vinyl_cq_recv( client->cq, client->cq_seq, comp );
406 0 : if( FD_UNLIKELY( cq_err<0 ) ) {
407 0 : FD_LOG_CRIT(( "Vinyl completion queue overrun detected" ));
408 0 : }
409 0 : if( cq_err>0 ) break;
410 0 : bqr_handle_cq( client, bqr, comp );
411 0 : client->cq_seq++;
412 0 : }
413 0 : }
414 :
415 : /* bqr_req_acquire sends a batch of ACQUIRE requests. */
416 :
417 : static void
418 : bqr_req_acquire( client_t * client,
419 0 : bench_query_rand_t * bqr ) {
420 0 : FD_CRIT( bqr->req_free, "attempt to acquire a request when none are free" );
421 0 : batch_req_t * req = bqr_free_pop( bqr );
422 0 : ulong cnt = bqr->batch_depth;
423 0 : if( FD_UNLIKELY( cnt>bqr->iter_rem ) ) cnt = bqr->iter_rem;
424 :
425 : /* Prepare request descriptor */
426 0 : fd_vinyl_key_t * key = batch_req_key ( req );
427 0 : schar * err = batch_req_err ( req );
428 0 : ulong * val_gaddr = batch_req_val_gaddr( req );
429 0 : for( ulong i=0UL; i<cnt; i++ ) {
430 0 : ulong idx = bqr->sample_idx;
431 0 : key [ i ] = bqr->sample[ idx ];
432 0 : err [ i ] = 0;
433 0 : val_gaddr[ i ] = 0UL;
434 0 : bqr->sample_idx++;
435 0 : if( bqr->sample_idx>=bqr->sample_max ) bqr->sample_idx = 0UL;
436 0 : }
437 :
438 : /* Send request */
439 0 : ulong req_id = fd_ulong_clear_bit( client->req_id++, 63 );
440 0 : ulong link_id = client->link_id;
441 0 : int type = FD_VINYL_REQ_TYPE_ACQUIRE;
442 0 : ulong flags = 0UL;
443 0 : ulong key_gaddr = fd_wksp_gaddr_fast( client->client_wksp, batch_req_key ( req ) );
444 0 : ulong val_gaddr_gaddr = fd_wksp_gaddr_fast( client->client_wksp, batch_req_val_gaddr( req ) );
445 0 : ulong err_gaddr = fd_wksp_gaddr_fast( client->client_wksp, batch_req_err ( req ) );
446 0 : fd_vinyl_rq_send( client->rq, req_id, link_id, type, flags, cnt, 0UL, key_gaddr, val_gaddr_gaddr, err_gaddr, 0UL );
447 :
448 : /* Update quotas */
449 0 : bqr->iter_rem -= cnt;
450 0 : client->quota_rem -= cnt;
451 :
452 0 : req->req_id = req_id;
453 0 : bqr_wait_push( bqr, req );
454 0 : }
455 :
456 : /* bench_query_rand_poll sends as many random read requests to vinyl as
457 : possible. Returns 1 if there is more work to do, 0 if the benchmark
458 : is done. */
459 :
460 : static int
461 : bench_query_rand_poll( client_t * client,
462 0 : bench_query_rand_t * bqr ) {
463 0 : if( bqr->req_wait_lo ) {
464 0 : bqr_drain_cq( client, bqr );
465 0 : }
466 0 : while( bqr->req_free && bqr->iter_rem ) {
467 0 : bqr_req_acquire( client, bqr );
468 0 : }
469 0 : return (!!bqr->req_wait_lo) | (!!bqr->iter_rem);
470 0 : }
471 :
472 : /* client_bench_query_rand runs a random read benchmark against vinyl.
473 : Assumes that RQ and CQ are clean and quota_rem==quota_max. */
474 :
475 : static void
476 : client_bench_query_rand( client_t * client,
477 : int * pargc,
478 0 : char *** pargv ) {
479 :
480 : /* Prepare a random query benchmark
481 :
482 : 1. Randomly sample keys into an array (--keys)
483 : 2. Inject random keys at a configurable rate (--miss) to exercise
484 : index query misses
485 : 3. Loop through the sampled keys array until (--iter) queries have
486 : been submitted, while doing batches of (--batch) keys at a time
487 :
488 : The benchmark loop is pipelined/asynchronous. The client will
489 : submit request batches until it is blocked by quota, RQ, or CQ. */
490 :
491 0 : ulong batch_depth = fd_env_strip_cmdline_ulong( pargc, pargv, "--batch", NULL, 1UL );
492 0 : ulong key_cnt = fd_env_strip_cmdline_ulong( pargc, pargv, "--keys", NULL, 262144UL );
493 0 : ulong const iter_cnt = fd_env_strip_cmdline_ulong( pargc, pargv, "--iter", NULL, 1048576UL );
494 0 : ulong const seed = fd_env_strip_cmdline_ulong( pargc, pargv, "--seed", NULL, (ulong)fd_tickcount() );
495 0 : float const miss_r = fd_env_strip_cmdline_float( pargc, pargv, "--miss", NULL, 0.1f );
496 :
497 0 : batch_depth = fd_ulong_max( batch_depth, 1UL );
498 0 : key_cnt = fd_ulong_min( key_cnt, UINT_MAX );
499 :
500 0 : fd_rng_t _rng[1]; fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, (uint)fd_ulong_hash( seed ), 0UL ) );
501 :
502 0 : fd_vinyl_meta_t * meta = client->meta;
503 0 : ulong const ele_max = fd_vinyl_meta_ele_max ( meta );
504 0 : ulong const probe_max = fd_vinyl_meta_probe_max( meta );
505 :
506 : /* Allocate a huge page backed scratch memory region to back keys */
507 :
508 0 : ulong sample_fp = fd_ulong_align_up( key_cnt*sizeof(fd_vinyl_key_t), FD_SHMEM_HUGE_PAGE_SZ );
509 0 : ulong sample_page_sz = FD_SHMEM_NORMAL_PAGE_SZ;
510 0 : ulong sample_page_cnt = sample_fp>>FD_SHMEM_NORMAL_LG_PAGE_SZ;
511 0 : fd_vinyl_key_t * sample = fd_shmem_acquire( sample_page_sz, sample_page_cnt, fd_log_cpu_id() );
512 0 : ulong sample_cnt = 0UL;
513 0 : if( FD_UNLIKELY( !sample ) ) {
514 0 : FD_LOG_WARNING(( "Cannot acquire scratch memory to hold %lu vinyl keys (out of memory). Aborting benchmark", key_cnt ));
515 0 : return;
516 0 : }
517 :
518 : /* Determine pipeline depth */
519 :
520 0 : ulong const rq_ele_depth = fd_vinyl_rq_req_cnt ( client->rq )*batch_depth;
521 0 : ulong const cq_ele_depth = fd_vinyl_cq_comp_cnt( client->cq )*batch_depth;
522 0 : ulong const quota_max = fd_ulong_min( client->quota_rem, fd_ulong_min( rq_ele_depth, cq_ele_depth ) );
523 0 : ulong const batch_max = ( quota_max + batch_depth - 1UL ) / batch_depth;
524 :
525 : /* Allocate request queue entries */
526 :
527 0 : ulong req_footprint = batch_req_footprint( batch_depth );
528 0 : ulong req_batch_footprint = batch_max*req_footprint;
529 0 : ulong req_laddr = (ulong)fd_wksp_alloc_laddr( client->client_wksp, batch_req_align(), req_batch_footprint, 1UL );
530 0 : if( FD_UNLIKELY( !req_laddr ) ) {
531 0 : FD_LOG_WARNING(( "Vinyl client wksp is too small to hold requests. Aborting benchmark" ));
532 0 : fd_shmem_release( sample, sample_page_sz, sample_page_cnt );
533 0 : return;
534 0 : }
535 0 : for( ulong batch_idx=0UL,
536 0 : batch_cur=req_laddr;
537 0 : batch_idx<quota_max;
538 0 : batch_idx++,
539 0 : batch_cur+=req_footprint ) {
540 0 : batch_req_t * req = batch_req_new( (void *)batch_cur, batch_depth );
541 0 : req->prev = batch_idx>0UL ? (batch_req_t *)( batch_cur - req_footprint ) : NULL;
542 0 : req->next = batch_idx+1UL<batch_max ? (batch_req_t *)( batch_cur + req_footprint ) : NULL;
543 0 : }
544 0 : batch_req_t * req_free = (batch_req_t *)req_laddr; /* free list holding all batch_req */
545 :
546 : /* Sample keys */
547 :
548 0 : long dt = -fd_log_wallclock();
549 0 : uint const miss_u = (uint)fd_ulong_min( (ulong)( (float)UINT_MAX * miss_r ), UINT_MAX );
550 0 : for( ulong i=0UL; i<key_cnt; i++ ) {
551 :
552 0 : if( fd_rng_uint( rng )<miss_u ) { /* rand key */
553 0 : for( uint j=0U; j<32U; j+=4U ) FD_STORE( uint, sample[ i ].uc+j, fd_rng_uint( rng ) );
554 0 : continue;
555 0 : }
556 :
557 : /* sample a key, linear probe until one found */
558 0 : ulong meta_idx = fd_rng_ulong_roll( rng, ele_max );
559 0 : ulong probe_rem;
560 0 : for( probe_rem=probe_max; probe_rem; probe_rem-- ) {
561 0 : fd_vinyl_meta_ele_t const * ele = meta->ele + meta_idx;
562 0 : if( FD_LIKELY( fd_vinyl_meta_ele_in_use( ele ) ) ) {
563 0 : sample[ i ] = ele->phdr.key;
564 0 : sample_cnt++;
565 0 : break;
566 0 : }
567 0 : meta_idx = (meta_idx+1UL) % ele_max;
568 0 : }
569 0 : if( !probe_rem ) { /* no key found (low hashmap utilization) ... */
570 0 : for( uint j=0U; j<32U; j+=4U ) FD_STORE( uint, sample[ i ].uc+j, fd_rng_uint( rng ) );
571 0 : }
572 :
573 0 : }
574 0 : dt += fd_log_wallclock();
575 :
576 0 : # if FD_HAS_DOUBLE
577 0 : FD_LOG_NOTICE(( "Sampled %lu keys in %gs (miss ratio %g)",
578 0 : key_cnt, (double)dt/1e9, (double)( key_cnt-sample_cnt )/(double)key_cnt ));
579 : # else
580 : FD_LOG_NOTICE(( "Sampled %lu keys in %ldns (%lu missed)",
581 : key_cnt, dt, key_cnt-sample_cnt ));
582 : # endif
583 :
584 : /* Run benchmark */
585 :
586 0 : bench_query_rand_t bqr = {
587 0 : .req_free = req_free,
588 0 : .req_wait_lo = NULL,
589 0 : .req_wait_hi = NULL,
590 0 : .batch_depth = batch_depth,
591 0 : .iter_rem = iter_cnt,
592 0 : .sample = sample,
593 0 : .sample_idx = 0UL,
594 0 : .sample_max = key_cnt
595 0 : };
596 0 : dt = -fd_log_wallclock();
597 0 : while( bench_query_rand_poll( client, &bqr ) );
598 0 : dt += fd_log_wallclock();
599 :
600 0 : # if FD_HAS_DOUBLE
601 0 : FD_LOG_NOTICE(( "Completed %lu queries (%lu found, %lu missed) in %gs (%g q/s)",
602 0 : iter_cnt, bqr.found_cnt, bqr.miss_cnt,
603 0 : (double)dt/1e9,
604 0 : (double)iter_cnt / ( (double)dt/1e9 ) ));
605 : # else
606 : FD_LOG_NOTICE(( "Completed %lu queries (%lu found, %lu missed) in %ldns",
607 : iter_cnt, bqr.found_cnt, bqr.miss_cnt, dt ));
608 : # endif
609 :
610 : /* Clean up */
611 :
612 0 : fd_rng_delete( fd_rng_leave( rng ) );
613 :
614 0 : fd_wksp_free_laddr( (void *)req_laddr );
615 :
616 0 : fd_shmem_release( sample, sample_page_sz, sample_page_cnt );
617 0 : }
618 :
619 : static int
620 : client_cmd( client_t * client,
621 : char ** tok,
622 0 : ulong tok_cnt ) {
623 0 : if( FD_UNLIKELY( !tok_cnt ) ) return 1;
624 0 : char const * cmd = tok[0];
625 0 : if( !strcmp( cmd, "query" ) ) {
626 0 : client_query( client, tok+1, tok_cnt-1 );
627 0 : } else if( !strcmp( cmd, "bench-query-rand" ) ) {
628 0 : int argc = (int)tok_cnt;
629 0 : client_bench_query_rand( client, &argc, &tok );
630 0 : } else if( !strcmp( cmd, "quit" ) || !strcmp( cmd, "exit" ) ) {
631 0 : return 0;
632 0 : } else {
633 0 : printf( "ERR: unknown command `%s`\n", cmd );
634 0 : }
635 0 : return 1;
636 0 : }
637 :
638 : static void
639 0 : repl( client_t * client ) {
640 0 : char line[ 4096 ] = {0};
641 0 : # define TOK_MAX 16
642 0 : char * tokens[ 16 ] = {0};
643 0 : for(;;) {
644 0 : fputs( "accdb> ", stdout );
645 0 : fflush( stdout );
646 :
647 : /* Read command */
648 0 : if( fgets( line, sizeof(line), stdin )==NULL ) {
649 0 : putc( '\n', stdout );
650 0 : break;
651 0 : }
652 0 : line[ strcspn( line, "\n" ) ] = '\0';
653 0 : line[ sizeof(line)-1 ] = '\0';
654 :
655 : /* Interpret command */
656 0 : ulong tok_cnt = fd_cstr_tokenize( tokens, TOK_MAX, line, ' ' );
657 0 : if( !client_cmd( client, tokens, tok_cnt ) ) break;
658 0 : }
659 0 : # undef TOK_MAX
660 0 : }
661 :
662 : int
663 : main( int argc,
664 : char ** argv ) {
665 : fd_boot( &argc, &argv );
666 :
667 : char const * cfg_gaddr = fd_env_strip_cmdline_cstr ( &argc, &argv, "--cfg", NULL, NULL );
668 : char const * wksp_name = fd_env_strip_cmdline_cstr ( &argc, &argv, "--wksp", NULL, NULL );
669 : ulong const burst_max = fd_env_strip_cmdline_ulong( &argc, &argv, "--burst-max", NULL, 1UL );
670 : ulong const quota_max = fd_env_strip_cmdline_ulong( &argc, &argv, "--quota-max", NULL, 2UL );
671 : if( FD_UNLIKELY( !cfg_gaddr ) ) FD_LOG_ERR(( "Missing required argument --cfg" ));
672 : if( FD_UNLIKELY( !wksp_name ) ) FD_LOG_ERR(( "Missing required argument --wksp" ));
673 :
674 : argc--; argv++;
675 :
676 : /* Join server shared memory structures */
677 :
678 : uchar * pod = fd_pod_join( fd_wksp_map( cfg_gaddr ) );
679 : if( FD_UNLIKELY( !pod ) ) FD_LOG_ERR(( "Invalid --cfg pod" ));
680 :
681 : void * _cnc = fd_wksp_pod_map( pod, "cnc" );
682 : void * _meta = fd_wksp_pod_map( pod, "meta" );
683 : void * _ele = fd_wksp_pod_map( pod, "ele" );
684 : void * _obj = fd_wksp_pod_map( pod, "obj" );
685 :
686 : fd_cnc_t * cnc = fd_cnc_join( _cnc ); FD_TEST( cnc );
687 : fd_vinyl_meta_t meta[1];
688 : FD_TEST( fd_vinyl_meta_join( meta, _meta, _ele ) );
689 :
690 : ulong vinyl_status = fd_cnc_signal_query( cnc );
691 : if( FD_UNLIKELY( vinyl_status!=FD_CNC_SIGNAL_RUN ) ) {
692 : char status_cstr[ FD_CNC_SIGNAL_CSTR_BUF_MAX ];
693 : FD_LOG_ERR(( "Vinyl tile not running (status %lu-%s)", vinyl_status, fd_cnc_signal_cstr( vinyl_status, status_cstr ) ));
694 : }
695 :
696 : /* Allocate client structures */
697 :
698 : fd_wksp_t * wksp = fd_wksp_attach( wksp_name );
699 : FD_TEST( wksp );
700 :
701 : ulong const rq_max = 32UL;
702 : ulong const cq_max = 32UL;
703 : void * _rq = fd_wksp_alloc_laddr( wksp, fd_vinyl_rq_align(), fd_vinyl_rq_footprint( rq_max ), 1UL );
704 : void * _cq = fd_wksp_alloc_laddr( wksp, fd_vinyl_cq_align(), fd_vinyl_cq_footprint( cq_max ), 1UL );
705 : fd_vinyl_rq_t * rq = fd_vinyl_rq_join( fd_vinyl_rq_new( _rq, rq_max ) );
706 : fd_vinyl_cq_t * cq = fd_vinyl_cq_join( fd_vinyl_cq_new( _cq, cq_max ) );
707 : if( FD_UNLIKELY( !rq || !cq ) ) {
708 : FD_LOG_WARNING(( "Failed to allocate request/completion queues" ));
709 : goto dealloc2;
710 : }
711 :
712 : ulong req_info_gaddr = fd_wksp_alloc( wksp, alignof(req_info_t), sizeof(req_info_t), 1UL );
713 : if( FD_UNLIKELY( !req_info_gaddr ) ) {
714 : FD_LOG_WARNING(( "Failed to pre-allocate request metadata" ));
715 : goto dealloc1;
716 : }
717 : req_info_t * req_info = fd_wksp_laddr_fast( wksp, req_info_gaddr );
718 :
719 : /* Run client */
720 :
721 : ulong const link_id = 0UL;
722 : int join_err = fd_vinyl_client_join( cnc, rq, cq, wksp, link_id, burst_max, quota_max );
723 : if( FD_UNLIKELY( join_err ) ) FD_LOG_ERR(( "Failed to join vinyl client to server (err %i-%s)", join_err, fd_vinyl_strerror( join_err ) ));
724 :
725 : FD_LOG_NOTICE(( "Attached client" ));
726 :
727 : client_t client = {
728 : .rq = rq,
729 : .cq = cq,
730 : .req_id = 0UL,
731 : .link_id = link_id,
732 :
733 : .meta = meta,
734 :
735 : .req_info = req_info,
736 : .req_info_gaddr = req_info_gaddr,
737 : .val_wksp = fd_wksp_containing( _obj ),
738 : .client_wksp = wksp,
739 :
740 : .quota_rem = quota_max,
741 : .cq_seq = fd_vinyl_cq_seq( cq )
742 : };
743 :
744 : if( argc>0 ) {
745 : client_cmd( &client, argv, (ulong)argc );
746 : } else {
747 : repl( &client );
748 : }
749 :
750 : FD_LOG_NOTICE(( "Detaching client" ));
751 :
752 : int leave_err = fd_vinyl_client_leave( cnc, link_id );
753 : if( FD_UNLIKELY( leave_err ) ) FD_LOG_ERR(( "Failed to leave vinyl client from server (err %i-%s)", leave_err, fd_vinyl_strerror( leave_err ) ));
754 :
755 : dealloc1:
756 : fd_wksp_free( wksp, req_info_gaddr );
757 :
758 : dealloc2:
759 : fd_wksp_free_laddr( fd_vinyl_rq_delete( fd_vinyl_rq_leave( rq ) ) );
760 : fd_wksp_free_laddr( fd_vinyl_cq_delete( fd_vinyl_cq_leave( cq ) ) );
761 :
762 : fd_wksp_unmap( fd_cnc_leave( cnc ) );
763 : fd_vinyl_meta_leave( meta );
764 : fd_wksp_unmap( _meta );
765 : fd_wksp_unmap( _ele );
766 : fd_wksp_unmap( _obj );
767 : fd_wksp_detach( wksp );
768 :
769 : fd_halt();
770 : return 0;
771 : }
|