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