Line data Source code
1 : #define _GNU_SOURCE
2 : #include "bench.h"
3 : #include "../../../shared/commands/configure/configure.h"
4 : #include "../../../shared/commands/run/run.h"
5 :
6 : #include "../../../shared/commands/watch/watch.h"
7 : #include "../../../platform/fd_sys_util.h"
8 : #include "../../../../disco/topo/fd_topob.h"
9 : #include "../../../../disco/topo/fd_cpu_topo.h"
10 : #include "../../../../disco/net/fd_net_tile.h"
11 :
12 : #include <errno.h>
13 : #include <unistd.h>
14 : #include <sched.h>
15 : #include <fcntl.h>
16 : #include <pthread.h>
17 : #include <signal.h>
18 : #include <linux/capability.h>
19 : #include <linux/futex.h>
20 : #include <sys/syscall.h>
21 : #include <sys/wait.h>
22 : #include <sys/socket.h>
23 : #include <arpa/inet.h>
24 :
25 : extern fd_topo_obj_callbacks_t * CALLBACKS[];
26 :
27 : fd_topo_run_tile_t
28 : fdctl_tile_run( fd_topo_tile_t const * tile );
29 :
30 : void
31 : update_config_for_dev( config_t * config );
32 :
33 : int
34 0 : bench_transaction_mode( char const * name ) {
35 0 : if( !strcmp( name, "" ) ) return BENCHG_TRANSACTION_MODE_NOOP;
36 0 : if( !strcmp( name, "noop" ) ) return BENCHG_TRANSACTION_MODE_NOOP;
37 0 : if( !strcmp( name, "sol-transfer" ) ) return BENCHG_TRANSACTION_MODE_SOL_TRANSFER;
38 0 : if( !strcmp( name, "ptoken-transfer" ) ) return BENCHG_TRANSACTION_MODE_PTOKEN_TRANSFER;
39 0 : return -1;
40 0 : }
41 :
42 : void
43 : bench_cmd_args( int * pargc,
44 : char *** pargv,
45 0 : args_t * args ) {
46 0 : args->load.no_quic = fd_env_strip_cmdline_contains( pargc, pargv, "--no-quic" );
47 0 : args->load.no_watch = fd_env_strip_cmdline_contains( pargc, pargv, "--no-watch" );
48 0 : args->load.duration = fd_env_strip_cmdline_ulong( pargc, pargv, "--duration", NULL, 0UL );
49 0 : }
50 :
51 : void
52 : add_bench_topo( fd_topo_t * topo,
53 : char const * affinity,
54 : ulong benchg_tile_cnt,
55 : ulong benchs_tile_cnt,
56 : ulong accounts_cnt,
57 : int transaction_mode,
58 : float contending_fraction,
59 : float cu_price_spread,
60 : ulong conn_cnt,
61 : ushort send_to_port,
62 : uint send_to_ip_addr,
63 : ushort rpc_port,
64 0 : uint rpc_ip_addr ) {
65 :
66 0 : fd_topob_wksp( topo, "bench" );
67 0 : fd_topob_link( topo, "bencho_out", "bench", 128UL, 64UL, 1UL );
68 0 : for( ulong i=0UL; i<benchg_tile_cnt; i++ ) fd_topob_link( topo, "benchg_s", "bench", 65536UL, FD_TXN_MTU, 1UL );
69 :
70 0 : int is_bench_auto_affinity = !strcmp( affinity, "auto" );
71 :
72 0 : ushort parsed_tile_to_cpu[ FD_TILE_MAX ];
73 0 : for( ulong i=0UL; i<FD_TILE_MAX; i++ ) parsed_tile_to_cpu[ i ] = USHORT_MAX;
74 :
75 0 : fd_topo_cpus_t cpus[1];
76 0 : fd_topo_cpus_init( cpus );
77 :
78 0 : ulong affinity_tile_cnt = 0UL;
79 0 : if( FD_LIKELY( !is_bench_auto_affinity ) ) affinity_tile_cnt = fd_topob_parse_affinity_cstr( affinity, parsed_tile_to_cpu, 0, 1 );
80 :
81 0 : ulong tile_to_cpu[ FD_TILE_MAX ] = {0};
82 0 : for( ulong i=0UL; i<affinity_tile_cnt; i++ ) {
83 0 : ushort cpu_idx = (ushort)( parsed_tile_to_cpu[ i ] & ~FD_TOPOB_CPU_SHARED );
84 0 : if( FD_UNLIKELY( parsed_tile_to_cpu[ i ]!=USHORT_MAX && cpu_idx>=cpus->cpu_cnt ) )
85 0 : FD_LOG_ERR(( "The CPU affinity string in the configuration file under [development.bench.affinity] specifies a CPU index of %hu, but the system "
86 0 : "only has %lu CPUs. You should either change the CPU allocations in the affinity string, or increase the number of CPUs "
87 0 : "in the system.",
88 0 : cpu_idx, cpus->cpu_cnt ));
89 0 : tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] );
90 0 : }
91 0 : if( FD_LIKELY( !is_bench_auto_affinity ) ) {
92 0 : if( FD_UNLIKELY( affinity_tile_cnt<benchg_tile_cnt+1UL+benchs_tile_cnt ) )
93 0 : FD_LOG_ERR(( "The benchmark topology you are using has %lu bench tiles, but the CPU affinity specified "
94 0 : "in the [development.bench.affinity] only provides for %lu cores. ",
95 0 : benchg_tile_cnt+1UL+benchs_tile_cnt, affinity_tile_cnt ));
96 0 : else if( FD_UNLIKELY( affinity_tile_cnt>benchg_tile_cnt+1UL+benchs_tile_cnt ) )
97 0 : FD_LOG_WARNING(( "The benchmark topology you are using has %lu bench tiles, but the CPU affinity specified "
98 0 : "in the [development.bench.affinity] provides for %lu cores. The extra cores will be unused.",
99 0 : benchg_tile_cnt+1UL+benchs_tile_cnt, affinity_tile_cnt ));
100 0 : }
101 0 : fd_topo_tile_t * bencho = fd_topob_tile( topo, "bencho", "bench", "bench", tile_to_cpu[ 0 ], 0, 0, 0 );
102 0 : bencho->bencho.rpc_port = rpc_port;
103 0 : bencho->bencho.rpc_ip_addr = rpc_ip_addr;
104 0 : for( ulong i=0UL; i<benchg_tile_cnt; i++ ) {
105 0 : fd_topo_tile_t * benchg = fd_topob_tile( topo, "benchg", "bench", "bench", tile_to_cpu[ i+1UL ], 0, 0, 0 );
106 0 : benchg->benchg.accounts_cnt = accounts_cnt;
107 0 : benchg->benchg.mode = transaction_mode;
108 0 : benchg->benchg.contending_fraction = contending_fraction;
109 0 : benchg->benchg.cu_price_spread = cu_price_spread;
110 0 : }
111 0 : for( ulong i=0UL; i<benchs_tile_cnt; i++ ) {
112 0 : fd_topo_tile_t * benchs = fd_topob_tile( topo, "benchs", "bench", "bench", tile_to_cpu[ benchg_tile_cnt+1UL+i ], 0, 0, 0 );
113 0 : benchs->benchs.send_to_ip_addr = send_to_ip_addr;
114 0 : benchs->benchs.send_to_port = send_to_port;
115 0 : benchs->benchs.conn_cnt = conn_cnt;
116 0 : }
117 :
118 0 : fd_topob_tile_out( topo, "bencho", 0UL, "bencho_out", 0UL );
119 0 : if( FD_LIKELY( fd_topo_find_link( topo, "replay_slot", 0UL )!=ULONG_MAX ) ) {
120 0 : fd_topob_tile_in( topo, "bencho", 0UL, "metric_in", "replay_slot", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /* fseq in a wksp replay maps */
121 0 : }
122 0 : for( ulong i=0UL; i<benchg_tile_cnt; i++ ) {
123 0 : fd_topob_tile_in( topo, "benchg", i, "bench", "bencho_out", 0, 1, 1 );
124 0 : fd_topob_tile_out( topo, "benchg", i, "benchg_s", i );
125 0 : }
126 0 : for( ulong i=0UL; i<benchg_tile_cnt; i++ ) {
127 0 : for( ulong j=0UL; j<benchs_tile_cnt; j++ ) {
128 0 : fd_topob_tile_in( topo, "benchs", j, "bench", "benchg_s", i, 1, 1 );
129 0 : }
130 0 : }
131 :
132 : /* This will blow away previous auto topology layouts and recompute an auto topology. */
133 0 : if( FD_UNLIKELY( is_bench_auto_affinity ) ) fd_topob_auto_layout( topo );
134 0 : fd_topob_sleep_finish( topo );
135 0 : fd_topob_finish( topo, CALLBACKS );
136 0 : }
137 :
138 : void
139 : fd_topo_initialize( config_t * config );
140 :
141 : void
142 0 : bench_topo( config_t * config ) {
143 0 : config->tiles.rpc.delay_startup = 0;
144 :
145 0 : fd_topo_initialize( config );
146 :
147 0 : ushort rpc_port;
148 0 : uint rpc_ip_addr;
149 0 : if( FD_UNLIKELY( !config->tiles.rpc.enabled ) ) FD_LOG_ERR(( "RPC tile must be enabled to run bench" ));
150 0 : rpc_port = config->tiles.rpc.rpc_listen_port;
151 0 : if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->tiles.rpc.rpc_listen_address, &rpc_ip_addr ) ) )
152 0 : FD_LOG_ERR(( "failed to parse rpc listen address `%s`", config->tiles.rpc.rpc_listen_address ));
153 :
154 0 : int is_auto_affinity = !strcmp( config->layout.affinity, "auto" );
155 0 : int is_bench_auto_affinity = !strcmp( config->development.bench.affinity, "auto" );
156 :
157 0 : if( FD_UNLIKELY( is_auto_affinity != is_bench_auto_affinity ) ) {
158 0 : FD_LOG_ERR(( "The CPU affinity string in the configuration file under [layout.affinity] and [development.bench.affinity] must all be set to 'auto' or all be set to a specific CPU affinity string." ));
159 0 : }
160 :
161 0 : int transaction_mode = bench_transaction_mode( config->development.bench.transaction_mode );
162 0 : if( FD_UNLIKELY( transaction_mode<0 ) )
163 0 : FD_LOG_ERR(( "unknown [development.bench.transaction_mode] `%s`", config->development.bench.transaction_mode ));
164 :
165 0 : add_bench_topo( &config->topo,
166 0 : config->development.bench.affinity,
167 0 : config->development.bench.benchg_tile_count,
168 0 : config->development.bench.benchs_tile_count,
169 0 : config->development.genesis.fund_initial_accounts,
170 0 : transaction_mode, 0.0f, 0.0f,
171 0 : config->layout.quic_tile_count,
172 0 : config->tiles.quic.quic_transaction_listen_port,
173 0 : config->net.ip_addr,
174 0 : rpc_port,
175 0 : rpc_ip_addr );
176 0 : }
177 :
178 : static pid_t bench_pid;
179 :
180 : static void
181 0 : bench_signal( int sig ) {
182 0 : kill( bench_pid, sig );
183 0 : }
184 :
185 : void
186 : bench_cmd_fn( args_t * args,
187 0 : config_t * config ) {
188 :
189 0 : if( args->load.no_quic ) {
190 0 : ushort port = config->tiles.quic.regular_transaction_listen_port;
191 0 : ulong benchs_tile_cnt = fd_topo_tile_name_cnt( &config->topo, "benchs" );
192 0 : for( ulong i=0UL; i<benchs_tile_cnt; i++ ) {
193 0 : fd_topo_tile_t * benchs = &config->topo.tiles[ fd_topo_find_tile( &config->topo, "benchs", i ) ];
194 0 : benchs->benchs.no_quic = 1;
195 0 : benchs->benchs.send_to_port = port;
196 0 : }
197 0 : }
198 :
199 0 : config->topo.tiles[ fd_topo_find_tile( &config->topo, "bencho", 0UL ) ].bencho.duration_s = args->load.duration;
200 :
201 0 : args_t configure_args = {
202 0 : .configure.command = CONFIGURE_CMD_INIT,
203 0 : };
204 :
205 0 : for( ulong i=0UL; STAGES[ i ]; i++ )
206 0 : configure_args.configure.stages[ i ] = STAGES[ i ];
207 0 : configure_cmd_fn( &configure_args, config );
208 :
209 : /* Everything past configure runs in a child: the tiles join the
210 : cpuset cgroup and drop privileges, so only a root parent outside
211 : it can dissolve the partition once they exit (or fail to boot) */
212 0 : bench_pid = fork();
213 0 : if( FD_UNLIKELY( bench_pid<0 ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
214 0 : if( FD_LIKELY( bench_pid ) ) {
215 0 : struct sigaction sa = { .sa_handler = bench_signal };
216 0 : if( FD_UNLIKELY( sigaction( SIGINT, &sa, NULL ) || sigaction( SIGTERM, &sa, NULL ) ) )
217 0 : FD_LOG_ERR(( "sigaction() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
218 :
219 0 : int wstatus;
220 0 : while( FD_UNLIKELY( -1==waitpid( bench_pid, &wstatus, 0 ) ) ) {
221 0 : if( FD_UNLIKELY( errno!=EINTR ) ) FD_LOG_ERR(( "waitpid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
222 0 : }
223 :
224 0 : configure_args = (args_t){ .configure.command = CONFIGURE_CMD_FINI, .configure.stages = { &fd_cfg_stage_cpuset } };
225 0 : configure_cmd_fn( &configure_args, config );
226 0 : fd_sys_util_exit_group( WIFSIGNALED( wstatus ) ? 128+WTERMSIG( wstatus ) : WEXITSTATUS( wstatus ) );
227 0 : }
228 :
229 0 : update_config_for_dev( config );
230 :
231 0 : run_firedancer_init( config, 1, 1 );
232 :
233 0 : if( 0==strcmp( config->net.provider, "xdp" ) ) {
234 0 : fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed );
235 0 : }
236 :
237 0 : initialize_accdb_fd( config );
238 0 : initialize_epoch_credits_fd( config );
239 0 : initialize_cost_tracker_fd( config );
240 0 : initialize_vote_stakes_fd( config );
241 0 : initialize_stake_delegations_fd( config );
242 0 : initialize_store_fds( config );
243 0 : initialize_snapshot_fds( config );
244 :
245 0 : fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
246 0 : if( 0==strcmp( config->net.provider, "mlx5" ) ) {
247 0 : fd_topo_install_mlx5( &config->topo, NULL );
248 0 : }
249 :
250 0 : if( !args->load.no_watch ) {
251 : /* watch incompatible with sandbox */
252 0 : config->development.sandbox = 0;
253 0 : config->development.no_clone = 1;
254 :
255 0 : int pipefd[2];
256 0 : if( FD_UNLIKELY( pipe2( pipefd, O_NONBLOCK ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
257 :
258 0 : args_t watch_args;
259 0 : watch_args.watch.drain_output_fd = pipefd[0];
260 0 : watch_args.watch.full = 1;
261 0 : if( FD_UNLIKELY( -1==dup2( pipefd[ 1 ], STDERR_FILENO ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
262 :
263 : /* FIXME allow running sandboxed/multiprocess */
264 0 : fd_topo_run_single_process( &config->topo, config->uid, config->gid, fdctl_tile_run );
265 0 : watch_cmd_fn( &watch_args, config );
266 0 : } else {
267 : /* FIXME allow running sandboxed/multiprocess */
268 0 : fd_topo_run_single_process( &config->topo, config->uid, config->gid, fdctl_tile_run );
269 0 : }
270 0 : }
|