Line data Source code
1 : #include "../dev.h"
2 : #include "../../../shared/commands/configure/configure.h" /* CONFIGURE_CMD_INIT */
3 : #include "../../../shared/commands/run/run.h" /* fdctl_check_configure */
4 : #include "../../../../disco/net/fd_net_tile.h"
5 : #include "../../../../disco/metrics/fd_metrics.h"
6 : #include "../../../../disco/topo/fd_topob.h"
7 : #include "../../../../disco/topo/fd_cpu_topo.h"
8 : #include "../../../../util/net/fd_ip4.h"
9 :
10 : #include <stdio.h> /* printf */
11 : #include <unistd.h> /* isatty */
12 : #include <sys/ioctl.h>
13 : #include <poll.h>
14 :
15 : extern fd_topo_obj_callbacks_t * CALLBACKS[];
16 :
17 : fd_topo_run_tile_t
18 : fdctl_tile_run( fd_topo_tile_t const * tile );
19 :
20 : static void
21 0 : pktgen_topo( config_t * config ) {
22 0 : char const * affinity = config->development.pktgen.affinity;
23 0 : int is_auto_affinity = !strcmp( affinity, "auto" );
24 :
25 0 : ushort parsed_tile_to_cpu[ FD_TILE_MAX ];
26 0 : for( ulong i=0UL; i<FD_TILE_MAX; i++ ) parsed_tile_to_cpu[ i ] = USHORT_MAX;
27 :
28 0 : fd_topo_cpus_t cpus[1];
29 0 : fd_topo_cpus_init( cpus );
30 :
31 0 : ulong affinity_tile_cnt = 0UL;
32 0 : if( FD_LIKELY( !is_auto_affinity ) ) affinity_tile_cnt = fd_topob_parse_affinity_cstr( affinity, parsed_tile_to_cpu, 0 );
33 :
34 0 : ulong tile_to_cpu[ FD_TILE_MAX ] = {0};
35 0 : for( ulong i=0UL; i<affinity_tile_cnt; i++ ) {
36 0 : if( FD_UNLIKELY( parsed_tile_to_cpu[ i ]!=USHORT_MAX && parsed_tile_to_cpu[ i ]>=cpus->cpu_cnt ) )
37 0 : FD_LOG_ERR(( "The CPU affinity string in the configuration file under [development.pktgen.affinity] specifies a CPU index of %hu, but the system "
38 0 : "only has %lu CPUs. You should either change the CPU allocations in the affinity string, or increase the number of CPUs "
39 0 : "in the system.",
40 0 : parsed_tile_to_cpu[ i ], cpus->cpu_cnt ));
41 0 : tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] );
42 0 : }
43 0 : if( FD_LIKELY( !is_auto_affinity ) ) {
44 0 : if( FD_UNLIKELY( affinity_tile_cnt!=4UL ) )
45 0 : FD_LOG_ERR(( "Invalid [development.pktgen.affinity]: must include exactly three CPUs" ));
46 0 : }
47 :
48 : /* Reset topology from scratch */
49 0 : fd_topo_t * topo = &config->topo;
50 0 : fd_topob_new( &config->topo, config->name );
51 0 : topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
52 :
53 0 : fd_topob_wksp( topo, "metric" );
54 0 : fd_topob_wksp( topo, "metric_in" );
55 0 : fd_topos_net_tiles( topo, config->layout.net_tile_count, &config->net, config->tiles.netlink.max_routes, config->tiles.netlink.max_peer_routes, config->tiles.netlink.max_neighbors, 0, tile_to_cpu );
56 0 : fd_topob_tile( topo, "metric", "metric", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 );
57 :
58 0 : fd_topob_wksp( topo, "pktgen" );
59 0 : fd_topo_tile_t * pktgen_tile = fd_topob_tile( topo, "pktgen", "pktgen", "pktgen", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 );
60 0 : if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->development.pktgen.fake_dst_ip, &pktgen_tile->pktgen.fake_dst_ip ) ) ) {
61 0 : FD_LOG_ERR(( "Invalid [development.pktgen.fake_dst_ip]" ));
62 0 : }
63 0 : fd_topob_link( topo, "pktgen_out", "pktgen", 32768UL, FD_NET_MTU, 1UL );
64 0 : fd_topob_tile_out( topo, "pktgen", 0UL, "pktgen_out", 0UL );
65 0 : fd_topob_tile_in( topo, "net", 0UL, "metric_in", "pktgen_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
66 :
67 : /* Create dummy RX link */
68 0 : fd_topos_net_rx_link( topo, "net_quic", 0UL, config->net.ingress_buffer_size );
69 0 : fd_topob_tile_in( topo, "pktgen", 0UL, "metric_in", "net_quic", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
70 :
71 0 : fd_topos_net_tile_finish( topo, 0UL );
72 0 : if( FD_UNLIKELY( is_auto_affinity ) ) fd_topob_auto_layout( topo, 0 );
73 0 : topo->agave_affinity_cnt = 0;
74 0 : fd_topob_finish( topo, CALLBACKS );
75 0 : fd_topo_print_log( /* stdout */ 1, topo );
76 0 : }
77 :
78 : void
79 : pktgen_cmd_args( int * pargc,
80 : char *** pargv,
81 0 : args_t * args ) {
82 : /* FIXME add config options here */
83 0 : (void)pargc; (void)pargv; (void)args;
84 0 : }
85 :
86 : /* Hacky: Since the pktgen runs in the same process, use globals to
87 : share state */
88 : extern uint fd_pktgen_active;
89 :
90 : /* render_status prints statistics at the top of the screen.
91 : Should be called at a low rate (~500ms). */
92 :
93 : static void
94 0 : render_status( ulong volatile const * net_metrics ) {
95 0 : fputs( "\0337" /* save cursor position */
96 0 : "\033[H" /* move cursor to (0,0) */
97 0 : "\033[2K\n", /* create an empty line to avoid spamming look back buffer */
98 0 : stdout );
99 0 : printf( "\033[2K" "[Firedancer pktgen] mode=%s\n",
100 0 : FD_VOLATILE_CONST( fd_pktgen_active ) ? "send+recv" : "recv" );
101 :
102 : /* Render packet per second rates */
103 0 : static long ts_last = -1L;
104 0 : static ulong cum_idle_last = 0UL;
105 0 : static ulong cum_tick_last = 0UL;
106 0 : static ulong rx_ok_last = 0UL;
107 0 : static ulong rx_byte_last = 0UL;
108 0 : static ulong rx_drop_last = 0UL;
109 0 : static ulong tx_ok_last = 0UL;
110 0 : static ulong tx_byte_last = 0UL;
111 :
112 0 : static double busy_r = 0.0;
113 0 : static double rx_ok_pps = 0.0;
114 0 : static double rx_bps = 0.0;
115 0 : static double rx_drop_pps = 0.0;
116 0 : static double tx_ok_pps = 0.0;
117 0 : static double tx_bps = 0.0;
118 :
119 0 : if( FD_UNLIKELY( ts_last==-1 ) ) ts_last = fd_log_wallclock();
120 0 : long now = fd_log_wallclock();
121 0 : long dt = now-ts_last;
122 0 : if( dt>(long)10e6 ) {
123 0 : ulong cum_idle_now = net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ];
124 0 : ulong cum_tick_now = cum_idle_now;
125 0 : /* */ cum_tick_now += net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_HOUSEKEEPING ) ];
126 0 : /* */ cum_tick_now += net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_HOUSEKEEPING ) ];
127 0 : /* */ cum_tick_now += net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_HOUSEKEEPING ) ];
128 0 : /* */ cum_tick_now += net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_PREFRAG ) ];
129 0 : /* */ cum_tick_now += net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_PREFRAG ) ];
130 0 : /* */ cum_tick_now += net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
131 0 : /* */ cum_tick_now += net_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_POSTFRAG ) ];
132 0 : ulong rx_ok_now = net_metrics[ MIDX( COUNTER, NET, PKT_RX ) ];
133 0 : ulong rx_byte_now = net_metrics[ MIDX( COUNTER, NET, PKT_RX_BYTES ) ];
134 0 : ulong rx_drop_now = net_metrics[ MIDX( COUNTER, NET, PKT_RX_FILL_RING_FULL ) ];
135 0 : /* */ rx_drop_now += net_metrics[ MIDX( COUNTER, NET, PKT_RX_BACKPRESSURE ) ];
136 0 : /* */ rx_drop_now += net_metrics[ MIDX( COUNTER, NET, XDP_RX_OTHER_DROPPED ) ];
137 0 : /* */ rx_drop_now += net_metrics[ MIDX( COUNTER, NET, XDP_RX_INVALID_DESCRIPTOR ) ];
138 0 : /* */ rx_drop_now += net_metrics[ MIDX( COUNTER, NET, XDP_RX_RING_FULL ) ];
139 0 : ulong tx_ok_now = net_metrics[ MIDX( COUNTER, NET, PKT_TX_COMPLETED ) ];
140 0 : ulong tx_byte_now = net_metrics[ MIDX( COUNTER, NET, PKT_TX_BYTES ) ];
141 :
142 0 : ulong cum_idle_delta = cum_idle_now-cum_idle_last;
143 0 : ulong cum_tick_delta = cum_tick_now-cum_tick_last;
144 0 : ulong rx_ok_delta = rx_ok_now -rx_ok_last;
145 0 : ulong rx_byte_delta = rx_byte_now -rx_byte_last;
146 0 : ulong rx_drop_delta = rx_drop_now -rx_drop_last;
147 0 : ulong tx_ok_delta = tx_ok_now -tx_ok_last;
148 0 : ulong tx_byte_delta = tx_byte_now -tx_byte_last;
149 :
150 0 : busy_r = 1.0 - ( (double)cum_idle_delta / (double)cum_tick_delta );
151 0 : rx_ok_pps = 1e9*( (double)rx_ok_delta /(double)dt );
152 0 : rx_bps = 8e9*( (double)rx_byte_delta/(double)dt );
153 0 : rx_drop_pps = 1e9*( (double)rx_drop_delta/(double)dt );
154 0 : tx_ok_pps = 1e9*( (double)tx_ok_delta /(double)dt );
155 0 : tx_bps = 8e9*( (double)tx_byte_delta/(double)dt );
156 :
157 0 : ts_last = now;
158 0 : cum_idle_last = cum_idle_now;
159 0 : cum_tick_last = cum_tick_now;
160 0 : rx_ok_last = rx_ok_now;
161 0 : rx_byte_last = rx_byte_now;
162 0 : rx_drop_last = rx_drop_now;
163 0 : tx_ok_last = tx_ok_now;
164 0 : tx_byte_last = tx_byte_now;
165 0 : }
166 :
167 0 : ulong rx_idle = net_metrics[ MIDX( GAUGE, NET, RX_BUFFER_IDLE ) ];
168 0 : ulong rx_busy = net_metrics[ MIDX( GAUGE, NET, RX_BUFFER_BUSY ) ];
169 0 : ulong tx_idle = net_metrics[ MIDX( GAUGE, NET, TX_BUFFER_IDLE ) ];
170 0 : ulong tx_busy = net_metrics[ MIDX( GAUGE, NET, TX_BUFFER_BUSY ) ];
171 0 : printf( "\033[2K" " Net busy: %.2f%%\n"
172 0 : "\033[2K" " RX ok: %10.3e pps %10.3e bps\n"
173 0 : "\033[2K" " RX drop: %10.3e pps\n"
174 0 : "\033[2K" " TX ok: %10.3e pps %10.3e bps\n"
175 0 : "\033[2K" " RX bufs: %6lu idle %6lu busy\n"
176 0 : "\033[2K" " TX bufs: %6lu idle %6lu busy\n",
177 0 : 100.*busy_r,
178 0 : rx_ok_pps, rx_bps,
179 0 : rx_drop_pps,
180 0 : tx_ok_pps, tx_bps,
181 0 : rx_idle, rx_busy,
182 0 : tx_idle, tx_busy );
183 :
184 0 : fputs( "\0338", stdout ); /* restore cursor position */
185 0 : fflush( stdout );
186 0 : }
187 :
188 : /* FIXME fixup screen on window size changes */
189 :
190 : void
191 : pktgen_cmd_fn( args_t * args FD_PARAM_UNUSED,
192 0 : config_t * config ) {
193 0 : pktgen_topo( config );
194 0 : fd_topo_t * topo = &config->topo;
195 0 : fd_topo_tile_t * net_tile = &topo->tiles[ fd_topo_find_tile( topo, "net", 0UL ) ];
196 0 : fd_topo_tile_t * metric_tile = &topo->tiles[ fd_topo_find_tile( topo, "metric", 0UL ) ];
197 :
198 0 : ushort const listen_port = 9000;
199 0 : net_tile->net.legacy_transaction_listen_port = listen_port;
200 :
201 0 : if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->tiles.metric.prometheus_listen_address, &metric_tile->metric.prometheus_listen_addr ) ) )
202 0 : FD_LOG_ERR(( "failed to parse prometheus listen address `%s`", config->tiles.metric.prometheus_listen_address ));
203 0 : metric_tile->metric.prometheus_listen_port = config->tiles.metric.prometheus_listen_port;
204 :
205 0 : configure_stage( &fd_cfg_stage_sysctl, CONFIGURE_CMD_INIT, config );
206 0 : configure_stage( &fd_cfg_stage_hugetlbfs, CONFIGURE_CMD_INIT, config );
207 0 : configure_stage( &fd_cfg_stage_bonding, CONFIGURE_CMD_INIT, config );
208 0 : configure_stage( &fd_cfg_stage_ethtool_channels, CONFIGURE_CMD_INIT, config );
209 0 : configure_stage( &fd_cfg_stage_ethtool_offloads, CONFIGURE_CMD_INIT, config );
210 0 : configure_stage( &fd_cfg_stage_sysfs_poll, CONFIGURE_CMD_INIT, config );
211 :
212 0 : fdctl_check_configure( config );
213 : /* FIXME this allocates lots of memory unnecessarily */
214 0 : initialize_workspaces( config );
215 0 : initialize_stacks( config );
216 0 : if( 0==strcmp( config->net.provider, "xdp" ) ) {
217 0 : fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed );
218 0 : }
219 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
220 :
221 : /* FIXME allow running sandboxed/multiprocess */
222 0 : fd_topo_run_single_process( topo, 2, config->uid, config->gid, fdctl_tile_run );
223 :
224 0 : ulong volatile const * net_metrics = fd_metrics_tile( net_tile->metrics );
225 :
226 : /* Don't attempt to render TTY */
227 0 : if( !isatty( STDOUT_FILENO ) ) {
228 0 : puts( "stdout is not a tty, not taking commands" );
229 0 : FD_VOLATILE( fd_pktgen_active ) = 1;
230 0 : for(;;) pause();
231 0 : return;
232 0 : }
233 :
234 : /* Clear screen */
235 0 : struct winsize w;
236 0 : if( FD_UNLIKELY( 0!=ioctl( STDOUT_FILENO, TIOCGWINSZ, &w ) ) ) {
237 0 : FD_LOG_WARNING(( "ioctl(STDOUT_FILENO,TIOCGWINSZ) failed" ));
238 0 : } else {
239 0 : for( ulong i=0UL; i<w.ws_row; i++ ) putc( '\n', stdout );
240 0 : }
241 :
242 : /* Simple REPL loop */
243 0 : puts( "Running fddev pktgen" );
244 0 : printf( "XDP socket listening on port %u\n", (uint)listen_port );
245 0 : puts( "Available commands: start, stop, quit" );
246 0 : puts( "" );
247 0 : char input[ 256 ] = {0};
248 0 : for(;;) {
249 0 : render_status( net_metrics );
250 0 : fputs( "pktgen> ", stdout );
251 0 : fflush( stdout );
252 :
253 0 : for(;;) {
254 0 : struct pollfd fds[1] = {{ .fd=STDIN_FILENO, .events=POLLIN }};
255 0 : int poll_res = poll( fds, 1, 500 );
256 0 : if( poll_res==0 ) {
257 0 : render_status( net_metrics );
258 0 : continue;
259 0 : } else if( poll_res>0 ) {
260 0 : break;
261 0 : } else {
262 0 : FD_LOG_ERR(( "poll(STDIN_FILENO) failed" ));
263 0 : break;
264 0 : }
265 0 : }
266 :
267 0 : if( fgets( input, sizeof(input), stdin )==NULL ) {
268 0 : putc( '\n', stdout );
269 0 : break;
270 0 : }
271 0 : input[ strcspn( input, "\n" ) ] = '\0';
272 0 : input[ sizeof(input)-1 ] = '\0';
273 :
274 0 : if( !input[0] ) {
275 : /* No command */
276 0 : } else if( !strcmp( input, "exit" ) || !strcmp( input, "quit" ) ) {
277 0 : break;
278 0 : } else if( !strcmp( input, "start" ) ) {
279 0 : FD_VOLATILE( fd_pktgen_active ) = 1U;
280 0 : } else if( !strcmp( input, "stop" ) ) {
281 0 : FD_VOLATILE( fd_pktgen_active ) = 0U;
282 0 : } else {
283 0 : fputs( "Unknown command\n", stdout );
284 0 : }
285 0 : }
286 0 : puts( "Exiting" );
287 0 : }
288 :
289 : action_t fd_action_pktgen = {
290 : .name = "pktgen",
291 : .args = pktgen_cmd_args,
292 : .fn = pktgen_cmd_fn,
293 : .perm = dev_cmd_perm,
294 : .description = "Flood interface with invalid Ethernet frames"
295 : };
|