vcl: refactor test client to support workers
[vpp.git] / src / vcl / vcl_test_client.c
1 /*
2  * Copyright (c) 2017-2018 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <unistd.h>
17 #include <errno.h>
18 #include <stdlib.h>
19 #include <ctype.h>
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <stdio.h>
23 #include <time.h>
24 #include <arpa/inet.h>
25 #include <vcl/vcl_test.h>
26 #include <pthread.h>
27
28 typedef struct
29 {
30   sock_test_socket_t *sessions;
31   uint32_t n_sessions;
32   uint32_t wrk_index;
33   fd_set wr_fdset;
34   fd_set rd_fdset;
35   int max_fd_index;
36   pthread_t thread_handle;
37   sock_test_cfg_t cfg;
38 } vcl_test_client_worker_t;
39
40 typedef struct
41 {
42   vcl_test_client_worker_t *workers;
43   vppcom_endpt_t server_endpt;
44   uint32_t cfg_seq_num;
45   sock_test_socket_t ctrl_socket;
46   sock_test_socket_t *sessions;
47   uint8_t dump_cfg;
48   sock_test_t post_test;
49   uint32_t proto;
50   uint32_t n_workers;
51   volatile int active_workers;
52   struct sockaddr_storage server_addr;
53 } vcl_test_client_main_t;
54
55 static __thread int __wrk_index = 0;
56
57 vcl_test_client_main_t vcl_client_main;
58
59 #define vtc_min(a, b) (a < b ? a : b)
60 #define vtc_max(a, b) (a > b ? a : b)
61
62 static int
63 vtc_cfg_sync (sock_test_socket_t * sock)
64 {
65   vcl_test_client_main_t *vcm = &vcl_client_main;
66   sock_test_cfg_t *rx_cfg = (sock_test_cfg_t *) sock->rxbuf;
67   int rx_bytes, tx_bytes;
68
69   vt_atomic_add (&sock->cfg.seq_num, 1);
70   if (sock->cfg.verbose)
71     {
72       vtinf ("(fd %d): Sending config to server.", sock->fd);
73       sock_test_cfg_dump (&sock->cfg, 1 /* is_client */ );
74     }
75   tx_bytes = vcl_test_write (sock->fd, (uint8_t *) & sock->cfg,
76                              sizeof (sock->cfg), NULL, sock->cfg.verbose);
77   if (tx_bytes < 0)
78     {
79       vtwrn ("(fd %d): write test cfg failed (%d)!", sock->fd, tx_bytes);
80       return tx_bytes;
81     }
82
83   rx_bytes = vcl_test_read (sock->fd, (uint8_t *) sock->rxbuf,
84                             sizeof (sock_test_cfg_t), NULL);
85   if (rx_bytes < 0)
86     return rx_bytes;
87
88   if (rx_cfg->magic != SOCK_TEST_CFG_CTRL_MAGIC)
89     {
90       vtwrn ("(fd %d): Bad server reply cfg -- aborting!", sock->fd);
91       return -1;
92     }
93   if ((rx_bytes != sizeof (sock_test_cfg_t))
94       || !sock_test_cfg_verify (rx_cfg, &sock->cfg))
95     {
96       vtwrn ("(fd %d): Invalid config received from server!", sock->fd);
97       if (rx_bytes != sizeof (sock_test_cfg_t))
98         {
99           vtinf ("\tRx bytes %d != cfg size %lu", rx_bytes,
100                  sizeof (sock_test_cfg_t));
101         }
102       else
103         {
104           sock_test_cfg_dump (rx_cfg, 1 /* is_client */ );
105           vtinf ("(fd %d): Valid config sent to server.", sock->fd);
106           sock_test_cfg_dump (&sock->cfg, 1 /* is_client */ );
107         }
108       return -1;
109     }
110   if (sock->cfg.verbose)
111     {
112       vtinf ("(fd %d): Got config back from server.", sock->fd);
113       sock_test_cfg_dump (rx_cfg, 1 /* is_client */ );
114     }
115
116   return 0;
117 }
118
119 static int
120 vtc_connect_test_sessions (vcl_test_client_worker_t * wrk)
121 {
122   vcl_test_client_main_t *vcm = &vcl_client_main;
123   sock_test_socket_t *tsock;
124   uint32_t n_test_sessions;
125   int i, rv;
126
127   n_test_sessions = wrk->cfg.num_test_sockets;
128   if (n_test_sessions < 1)
129     {
130       errno = EINVAL;
131       return -1;
132     }
133
134   if (wrk->n_sessions >= n_test_sessions)
135     goto done;
136
137   if (wrk->n_sessions)
138     wrk->sessions = realloc (wrk->sessions,
139                              n_test_sessions * sizeof (sock_test_socket_t));
140   else
141     wrk->sessions = calloc (n_test_sessions, sizeof (sock_test_socket_t));
142
143   if (!wrk->sessions)
144     {
145       vterr ("failed to alloc sessions", -errno);
146       return errno;
147     }
148
149   for (i = 0; i < n_test_sessions; i++)
150     {
151       tsock = &wrk->sessions[i];
152       tsock->fd = vppcom_session_create (vcm->proto, 1 /* is_nonblocking */ );
153       if (tsock->fd < 0)
154         {
155           vterr ("vppcom_session_create()", tsock->fd);
156           return tsock->fd;
157         }
158
159       rv = vppcom_session_connect (tsock->fd, &vcm->server_endpt);
160       if (rv < 0)
161         {
162           vterr ("vppcom_session_connect()", rv);
163           return rv;
164         }
165       vtinf ("(fd %d): Test socket %d connected.", tsock->fd, i);
166     }
167   wrk->n_sessions = n_test_sessions;
168
169 done:
170   vtinf ("All test sessions (%d) connected!", n_test_sessions);
171   return 0;
172 }
173
174 static int
175 vtc_worker_test_setup (vcl_test_client_worker_t * wrk)
176 {
177   vcl_test_client_main_t *vcm = &vcl_client_main;
178   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
179   sock_test_cfg_t *cfg = &wrk->cfg;
180   sock_test_socket_t *tsock;
181   uint32_t sidx;
182   int i, j;
183
184   FD_ZERO (&wrk->wr_fdset);
185   FD_ZERO (&wrk->rd_fdset);
186
187   for (i = 0; i < cfg->num_test_sockets; i++)
188     {
189       tsock = &wrk->sessions[i];
190       tsock->cfg = wrk->cfg;
191       sock_test_socket_buf_alloc (tsock);
192
193       switch (cfg->test)
194         {
195         case SOCK_TEST_TYPE_ECHO:
196           memcpy (tsock->txbuf, ctrl->txbuf, cfg->total_bytes);
197           break;
198         case SOCK_TEST_TYPE_UNI:
199         case SOCK_TEST_TYPE_BI:
200           for (j = 0; j < tsock->txbuf_size; j++)
201             tsock->txbuf[j] = j & 0xff;
202           break;
203         }
204
205       FD_SET (vppcom_session_index (tsock->fd), &wrk->wr_fdset);
206       FD_SET (vppcom_session_index (tsock->fd), &wrk->rd_fdset);
207       sidx = vppcom_session_index (tsock->fd);
208       wrk->max_fd_index = vtc_max (sidx, wrk->max_fd_index);
209     }
210   wrk->max_fd_index += 1;
211
212   return 0;
213 }
214
215 static int
216 vtc_worker_init (vcl_test_client_worker_t * wrk)
217 {
218   vcl_test_client_main_t *vcm = &vcl_client_main;
219   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
220   sock_test_cfg_t *cfg = &wrk->cfg;
221   sock_test_socket_t *tsock;
222   uint32_t i, n;
223   int rv, nbytes;
224
225   __wrk_index = wrk->wrk_index;
226
227   vtinf ("Initializing worker ...");
228
229   if (wrk->wrk_index)
230     {
231       if (vppcom_worker_register ())
232         {
233           vtwrn ("failed to register worker");
234           return -1;
235         }
236
237       vt_atomic_add (&vcm->active_workers, 1);
238     }
239   rv = vtc_connect_test_sessions (wrk);
240   if (rv)
241     {
242       vterr ("vtc_connect_test_sockets ()", rv);
243       return rv;
244     }
245
246   if (vtc_worker_test_setup (wrk))
247     return -1;
248
249   vtinf ("Sending config to server on all sessions ...");
250
251   for (n = 0; n < cfg->num_test_sockets; n++)
252     {
253       tsock = &wrk->sessions[n];
254       if (vtc_cfg_sync (tsock))
255         return -1;
256       memset (&tsock->stats, 0, sizeof (tsock->stats));
257     }
258
259   return 0;
260 }
261
262 static void *
263 vtc_worker_loop (void *arg)
264 {
265   vcl_test_client_main_t *vcm = &vcl_client_main;
266   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
267   vcl_test_client_worker_t *wrk = arg;
268   fd_set _wfdset, *wfdset = &_wfdset;
269   fd_set _rfdset, *rfdset = &_rfdset;
270   sock_test_socket_t *tsock;
271   uint32_t n_active_sessions;
272   int i, rv, check_rx = 0;
273
274   rv = vtc_worker_init (wrk);
275   if (rv)
276     {
277       vterr ("vtc_worker_init()", rv);
278       return 0;
279     }
280
281   vtinf ("Starting test ...");
282
283   if (wrk->wrk_index == 0)
284     clock_gettime (CLOCK_REALTIME, &ctrl->stats.start);
285
286   check_rx = wrk->cfg.test != SOCK_TEST_TYPE_UNI;
287   n_active_sessions = wrk->cfg.num_test_sockets;
288   while (n_active_sessions)
289     {
290       _wfdset = wrk->wr_fdset;
291       _rfdset = wrk->rd_fdset;
292
293       rv = vppcom_select (wrk->max_fd_index, (uint64_t *) rfdset,
294                           (uint64_t *) wfdset, NULL, 0);
295       if (rv < 0)
296         {
297           vterr ("vppcom_select()", rv);
298           goto exit;
299         }
300       else if (rv == 0)
301         continue;
302
303       for (i = 0; i < wrk->cfg.num_test_sockets; i++)
304         {
305           tsock = &wrk->sessions[i];
306           if (!((tsock->stats.stop.tv_sec == 0) &&
307                 (tsock->stats.stop.tv_nsec == 0)))
308             continue;
309
310           if (FD_ISSET (vppcom_session_index (tsock->fd), rfdset)
311               && tsock->stats.rx_bytes < tsock->cfg.total_bytes)
312             {
313               (void) vcl_test_read (tsock->fd, (uint8_t *) tsock->rxbuf,
314                                     tsock->rxbuf_size, &tsock->stats);
315             }
316
317           if (FD_ISSET (vppcom_session_index (tsock->fd), wfdset)
318               && tsock->stats.tx_bytes < tsock->cfg.total_bytes)
319             {
320               rv = vcl_test_write (tsock->fd, (uint8_t *) tsock->txbuf,
321                                    tsock->cfg.txbuf_size, &tsock->stats,
322                                    tsock->cfg.verbose);
323               if (rv < 0)
324                 {
325                   vtwrn ("vppcom_test_write (%d) failed -- aborting test",
326                          tsock->fd);
327                   goto exit;
328                 }
329             }
330
331           if ((!check_rx && tsock->stats.tx_bytes >= tsock->cfg.total_bytes)
332               || (check_rx
333                   && tsock->stats.rx_bytes >= tsock->cfg.total_bytes))
334             {
335               clock_gettime (CLOCK_REALTIME, &tsock->stats.stop);
336               n_active_sessions--;
337             }
338         }
339     }
340 exit:
341   if (wrk->wrk_index)
342     vt_atomic_add (&vcm->active_workers, -1);
343   return 0;
344 }
345
346 static void
347 vtc_accumulate_stats (vcl_test_client_worker_t * wrk,
348                       sock_test_socket_t * ctrl)
349 {
350   sock_test_socket_t *tsock;
351   static char buf[64];
352   int i, show_rx = 0;
353
354   if (ctrl->cfg.test == SOCK_TEST_TYPE_BI
355       || ctrl->cfg.test == SOCK_TEST_TYPE_ECHO)
356     show_rx = 1;
357
358   for (i = 0; i < wrk->cfg.num_test_sockets; i++)
359     {
360       tsock = &wrk->sessions[i];
361       tsock->stats.start = ctrl->stats.start;
362
363       if (ctrl->cfg.verbose > 1)
364         {
365           sprintf (buf, "CLIENT (fd %d) RESULTS", tsock->fd);
366           sock_test_stats_dump (buf, &tsock->stats, show_rx, 1 /* show tx */ ,
367                                 ctrl->cfg.verbose);
368         }
369
370       sock_test_stats_accumulate (&ctrl->stats, &tsock->stats);
371     }
372 }
373
374 static void
375 vtc_print_stats (sock_test_socket_t * ctrl)
376 {
377   int is_echo = ctrl->cfg.test == SOCK_TEST_TYPE_ECHO;
378   int show_rx = 0;
379   char buf[64];
380
381   if (ctrl->cfg.test == SOCK_TEST_TYPE_BI
382       || ctrl->cfg.test == SOCK_TEST_TYPE_ECHO)
383     show_rx = 1;
384
385   sock_test_stats_dump ("CLIENT RESULTS", &ctrl->stats,
386                         show_rx, 1 /* show tx */ ,
387                         ctrl->cfg.verbose);
388   sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
389
390   if (ctrl->cfg.verbose)
391     {
392       vtinf ("  ctrl socket info\n"
393              SOCK_TEST_SEPARATOR_STRING
394              "          fd:  %d (0x%08x)\n"
395              "       rxbuf:  %p\n"
396              "  rxbuf size:  %u (0x%08x)\n"
397              "       txbuf:  %p\n"
398              "  txbuf size:  %u (0x%08x)\n"
399              SOCK_TEST_SEPARATOR_STRING,
400              ctrl->fd, (uint32_t) ctrl->fd,
401              ctrl->rxbuf, ctrl->rxbuf_size, ctrl->rxbuf_size,
402              ctrl->txbuf, ctrl->txbuf_size, ctrl->txbuf_size);
403     }
404
405   if (is_echo)
406     sprintf (buf, "Echo");
407   else
408     sprintf (buf, "%s-directional Stream",
409              ctrl->cfg.test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni");
410
411   vtinf ("(fd %d): %s Test Complete!\n"
412          SOCK_TEST_BANNER_STRING, ctrl->fd, buf);
413 }
414
415 static void
416 vtc_echo_client (vcl_test_client_main_t * vcm)
417 {
418   vcl_test_client_worker_t *wrk;
419   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
420   sock_test_cfg_t *cfg = &ctrl->cfg;
421
422   cfg->total_bytes = strlen (ctrl->txbuf) + 1;
423   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
424
425   /* Echo works with only one worker */
426   wrk = vcm->workers;
427   wrk->wrk_index = 0;
428   wrk->cfg = *cfg;
429
430   vtc_worker_loop (wrk);
431
432   clock_gettime (CLOCK_REALTIME, &ctrl->stats.stop);
433   vtc_accumulate_stats (wrk, ctrl);
434   vtc_print_stats (ctrl);
435 }
436
437 static void
438 vtc_stream_client (vcl_test_client_main_t * vcm)
439 {
440   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
441   sock_test_cfg_t *cfg = &ctrl->cfg;
442   vcl_test_client_worker_t *wrk;
443   sock_test_socket_t *tsock;
444   int tx_bytes, rv;
445   uint32_t i, n, sidx, n_conn, n_conn_per_wrk;
446
447   vtinf (SOCK_TEST_BANNER_STRING "%s-directional Stream Test!\n",
448          ctrl->cfg.test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni");
449
450   cfg->total_bytes = cfg->num_writes * cfg->txbuf_size;
451   cfg->ctrl_handle = ~0;
452   if (vtc_cfg_sync (ctrl))
453     {
454       vtwrn ("test cfg sync failed -- aborting!");
455       return;
456     }
457   cfg->ctrl_handle = ((sock_test_cfg_t *) ctrl->rxbuf)->ctrl_handle;
458   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
459
460   n_conn = cfg->num_test_sockets;
461   n_conn_per_wrk = n_conn / vcm->n_workers;
462   for (i = 0; i < vcm->n_workers; i++)
463     {
464       wrk = &vcm->workers[i];
465       wrk->wrk_index = i;
466       wrk->cfg = ctrl->cfg;
467       wrk->cfg.num_test_sockets = vtc_min (n_conn_per_wrk, n_conn);
468       n_conn -= wrk->cfg.num_test_sockets;
469     }
470
471   for (i = 1; i < vcm->n_workers; i++)
472     {
473       wrk = &vcm->workers[i];
474       pthread_create (&wrk->thread_handle, NULL, vtc_worker_loop,
475                       (void *) wrk);
476     }
477   vtc_worker_loop (&vcm->workers[0]);
478
479   while (vcm->active_workers > 0)
480     ;
481
482   clock_gettime (CLOCK_REALTIME, &ctrl->stats.stop);
483
484   vtinf ("(fd %d): Sending config on ctrl socket for stats...\n", ctrl->fd);
485   if (vtc_cfg_sync (ctrl))
486     {
487       vtwrn ("test cfg sync failed -- aborting!");
488       return;
489     }
490
491   for (i = 0; i < vcm->n_workers; i++)
492     vtc_accumulate_stats (&vcm->workers[i], ctrl);
493   vtc_print_stats (ctrl);
494
495   ctrl->cfg.test = SOCK_TEST_TYPE_ECHO;
496   ctrl->cfg.total_bytes = 0;
497   if (vtc_cfg_sync (ctrl))
498     vtwrn ("post-test cfg sync failed!");
499 }
500
501 static void
502 vtc_client_exit (void)
503 {
504   vcl_test_client_main_t *vcm = &vcl_client_main;
505   vcl_test_client_worker_t *wrk = &vcm->workers[0];
506   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
507   sock_test_socket_t *tsock;
508   int i, verbose;
509
510   verbose = ctrl->cfg.verbose;
511   for (i = 0; i < wrk->cfg.num_test_sockets; i++)
512     {
513       tsock = &wrk->sessions[i];
514       tsock->cfg.test = SOCK_TEST_TYPE_EXIT;
515
516       if (verbose)
517         {
518           vtinf ("(fd %d): Sending exit cfg to server...", tsock->fd);
519           sock_test_cfg_dump (&tsock->cfg, 1 /* is_client */ );
520         }
521       (void) vcl_test_write (tsock->fd, (uint8_t *) & tsock->cfg,
522                              sizeof (tsock->cfg), &tsock->stats, verbose);
523     }
524
525   ctrl->cfg.test = SOCK_TEST_TYPE_EXIT;
526   if (verbose)
527     {
528       vtinf ("(fd %d): Sending exit cfg to server...", ctrl->fd);
529       sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
530     }
531   (void) vcl_test_write (ctrl->fd, (uint8_t *) & ctrl->cfg,
532                          sizeof (ctrl->cfg), &ctrl->stats, verbose);
533   vtinf ("So long and thanks for all the fish!\n\n");
534   sleep (1);
535 }
536
537 static void
538 dump_help (void)
539 {
540 #define INDENT "\n  "
541
542   printf ("CLIENT: Test configuration commands:"
543           INDENT SOCK_TEST_TOKEN_HELP
544           "\t\t\tDisplay help."
545           INDENT SOCK_TEST_TOKEN_EXIT
546           "\t\t\tExit test client & server."
547           INDENT SOCK_TEST_TOKEN_SHOW_CFG
548           "\t\t\tShow the current test cfg."
549           INDENT SOCK_TEST_TOKEN_RUN_UNI
550           "\t\t\tRun the Uni-directional test."
551           INDENT SOCK_TEST_TOKEN_RUN_BI
552           "\t\t\tRun the Bi-directional test."
553           INDENT SOCK_TEST_TOKEN_VERBOSE
554           "\t\t\tToggle verbose setting."
555           INDENT SOCK_TEST_TOKEN_RXBUF_SIZE
556           "<rxbuf size>\tRx buffer size (bytes)."
557           INDENT SOCK_TEST_TOKEN_TXBUF_SIZE
558           "<txbuf size>\tTx buffer size (bytes)."
559           INDENT SOCK_TEST_TOKEN_NUM_WRITES
560           "<# of writes>\tNumber of txbuf writes to server." "\n");
561 }
562
563 static void
564 cfg_txbuf_size_set (void)
565 {
566   vcl_test_client_main_t *vcm = &vcl_client_main;
567   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
568   char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_TXBUF_SIZE);
569   uint64_t txbuf_size = strtoull ((const char *) p, NULL, 10);
570
571   if (txbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN)
572     {
573       ctrl->cfg.txbuf_size = txbuf_size;
574       ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
575       sock_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ ,
576                            (uint8_t **) & ctrl->txbuf, &ctrl->txbuf_size);
577       sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
578     }
579   else
580     fprintf (stderr, "CLIENT: ERROR: Invalid txbuf size (%lu) < "
581              "minimum buf size (%u)!\n",
582              txbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN);
583 }
584
585 static void
586 cfg_num_writes_set (void)
587 {
588   vcl_test_client_main_t *vcm = &vcl_client_main;
589   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
590   char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_NUM_WRITES);
591   uint32_t num_writes = strtoul ((const char *) p, NULL, 10);
592
593   if (num_writes > 0)
594     {
595       ctrl->cfg.num_writes = num_writes;
596       ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
597       sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
598     }
599   else
600     {
601       fprintf (stderr, "CLIENT: ERROR: invalid num writes: %u\n", num_writes);
602     }
603 }
604
605 static void
606 cfg_num_test_sockets_set (void)
607 {
608   vcl_test_client_main_t *vcm = &vcl_client_main;
609   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
610   char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_NUM_TEST_SCKTS);
611   uint32_t num_test_sockets = strtoul ((const char *) p, NULL, 10);
612
613   if ((num_test_sockets > 0) &&
614       (num_test_sockets <= SOCK_TEST_CFG_MAX_TEST_SCKTS))
615     {
616       ctrl->cfg.num_test_sockets = num_test_sockets;
617       sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
618     }
619   else
620     {
621       vtwrn ("invalid num test sockets: %u, (%d max)",
622              num_test_sockets, SOCK_TEST_CFG_MAX_TEST_SCKTS);
623     }
624 }
625
626 static void
627 cfg_rxbuf_size_set (void)
628 {
629   vcl_test_client_main_t *vcm = &vcl_client_main;
630   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
631   char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_RXBUF_SIZE);
632   uint64_t rxbuf_size = strtoull ((const char *) p, NULL, 10);
633
634   if (rxbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN)
635     {
636       ctrl->cfg.rxbuf_size = rxbuf_size;
637       sock_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ ,
638                            (uint8_t **) & ctrl->rxbuf, &ctrl->rxbuf_size);
639       sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
640     }
641   else
642     fprintf (stderr, "CLIENT: ERROR: Invalid rxbuf size (%lu) < "
643              "minimum buf size (%u)!\n",
644              rxbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN);
645 }
646
647 static void
648 cfg_verbose_toggle (void)
649 {
650   vcl_test_client_main_t *vcm = &vcl_client_main;
651   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
652
653   ctrl->cfg.verbose = ctrl->cfg.verbose ? 0 : 1;
654   sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
655
656 }
657
658 static sock_test_t
659 parse_input ()
660 {
661   vcl_test_client_main_t *vcm = &vcl_client_main;
662   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
663   sock_test_t rv = SOCK_TEST_TYPE_NONE;
664
665   if (!strncmp (SOCK_TEST_TOKEN_EXIT, ctrl->txbuf,
666                 strlen (SOCK_TEST_TOKEN_EXIT)))
667     rv = SOCK_TEST_TYPE_EXIT;
668
669   else if (!strncmp (SOCK_TEST_TOKEN_HELP, ctrl->txbuf,
670                      strlen (SOCK_TEST_TOKEN_HELP)))
671     dump_help ();
672
673   else if (!strncmp (SOCK_TEST_TOKEN_SHOW_CFG, ctrl->txbuf,
674                      strlen (SOCK_TEST_TOKEN_SHOW_CFG)))
675     vcm->dump_cfg = 1;
676
677   else if (!strncmp (SOCK_TEST_TOKEN_VERBOSE, ctrl->txbuf,
678                      strlen (SOCK_TEST_TOKEN_VERBOSE)))
679     cfg_verbose_toggle ();
680
681   else if (!strncmp (SOCK_TEST_TOKEN_TXBUF_SIZE, ctrl->txbuf,
682                      strlen (SOCK_TEST_TOKEN_TXBUF_SIZE)))
683     cfg_txbuf_size_set ();
684
685   else if (!strncmp (SOCK_TEST_TOKEN_NUM_TEST_SCKTS, ctrl->txbuf,
686                      strlen (SOCK_TEST_TOKEN_NUM_TEST_SCKTS)))
687     cfg_num_test_sockets_set ();
688
689   else if (!strncmp (SOCK_TEST_TOKEN_NUM_WRITES, ctrl->txbuf,
690                      strlen (SOCK_TEST_TOKEN_NUM_WRITES)))
691     cfg_num_writes_set ();
692
693   else if (!strncmp (SOCK_TEST_TOKEN_RXBUF_SIZE, ctrl->txbuf,
694                      strlen (SOCK_TEST_TOKEN_RXBUF_SIZE)))
695     cfg_rxbuf_size_set ();
696
697   else if (!strncmp (SOCK_TEST_TOKEN_RUN_UNI, ctrl->txbuf,
698                      strlen (SOCK_TEST_TOKEN_RUN_UNI)))
699     rv = ctrl->cfg.test = SOCK_TEST_TYPE_UNI;
700
701   else if (!strncmp (SOCK_TEST_TOKEN_RUN_BI, ctrl->txbuf,
702                      strlen (SOCK_TEST_TOKEN_RUN_BI)))
703     rv = ctrl->cfg.test = SOCK_TEST_TYPE_BI;
704
705   else
706     rv = SOCK_TEST_TYPE_ECHO;
707
708   return rv;
709 }
710
711 void
712 print_usage_and_exit (void)
713 {
714   fprintf (stderr,
715            "sock_test_client [OPTIONS] <ipaddr> <port>\n"
716            "  OPTIONS\n"
717            "  -h               Print this message and exit.\n"
718            "  -6               Use IPv6\n"
719            "  -u               Use UDP transport layer\n"
720            "  -c               Print test config before test.\n"
721            "  -w <dir>         Write test results to <dir>.\n"
722            "  -X               Exit after running test.\n"
723            "  -E               Run Echo test.\n"
724            "  -N <num-writes>  Test Cfg: number of writes.\n"
725            "  -R <rxbuf-size>  Test Cfg: rx buffer size.\n"
726            "  -T <txbuf-size>  Test Cfg: tx buffer size.\n"
727            "  -U               Run Uni-directional test.\n"
728            "  -B               Run Bi-directional test.\n"
729            "  -V               Verbose mode.\n");
730   exit (1);
731 }
732
733 static void
734 vtc_process_opts (vcl_test_client_main_t * vcm, int argc, char **argv)
735 {
736   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
737   int c, v;
738
739   opterr = 0;
740   while ((c = getopt (argc, argv, "chn:w:XE:I:N:R:T:UBV6D")) != -1)
741     switch (c)
742       {
743       case 'c':
744         vcm->dump_cfg = 1;
745         break;
746
747       case 's':
748         if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sockets) != 1)
749           if (sscanf (optarg, "%u", &ctrl->cfg.num_test_sockets) != 1)
750             {
751               vtwrn ("Invalid value for option -%c!", c);
752               print_usage_and_exit ();
753             }
754         if (!ctrl->cfg.num_test_sockets ||
755             (ctrl->cfg.num_test_sockets > FD_SETSIZE))
756           {
757             vtwrn ("Invalid number of sockets (%d) specified for option -%c!"
758                    "\n       Valid range is 1 - %d",
759                    ctrl->cfg.num_test_sockets, c, FD_SETSIZE);
760             print_usage_and_exit ();
761           }
762         break;
763
764       case 'w':
765         if (sscanf (optarg, "%d", &v) != 1)
766           {
767             vtwrn ("Invalid value for option -%c!", c);
768             print_usage_and_exit ();
769           }
770         if (v > 1)
771           vcm->n_workers = v;
772         break;
773
774       case 'X':
775         vcm->post_test = SOCK_TEST_TYPE_EXIT;
776         break;
777
778       case 'E':
779         if (strlen (optarg) > ctrl->txbuf_size)
780           {
781             vtwrn ("Option -%c value larger than txbuf size (%d)!",
782                    optopt, ctrl->txbuf_size);
783             print_usage_and_exit ();
784           }
785         strcpy (ctrl->txbuf, optarg);
786         ctrl->cfg.test = SOCK_TEST_TYPE_ECHO;
787         break;
788
789       case 'I':
790         if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sockets) != 1)
791           if (sscanf (optarg, "%d", &ctrl->cfg.num_test_sockets) != 1)
792             {
793               vtwrn ("Invalid value for option -%c!", c);
794               print_usage_and_exit ();
795             }
796         if (ctrl->cfg.num_test_sockets > SOCK_TEST_CFG_MAX_TEST_SCKTS)
797           {
798             vtwrn ("value greater than max number test sockets (%d)!",
799                    SOCK_TEST_CFG_MAX_TEST_SCKTS);
800             print_usage_and_exit ();
801           }
802         break;
803
804       case 'N':
805         if (sscanf (optarg, "0x%lx", &ctrl->cfg.num_writes) != 1)
806           if (sscanf (optarg, "%ld", &ctrl->cfg.num_writes) != 1)
807             {
808               vtwrn ("Invalid value for option -%c!", c);
809               print_usage_and_exit ();
810             }
811         ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
812         break;
813
814       case 'R':
815         if (sscanf (optarg, "0x%lx", &ctrl->cfg.rxbuf_size) != 1)
816           if (sscanf (optarg, "%ld", &ctrl->cfg.rxbuf_size) != 1)
817             {
818               vtwrn ("Invalid value for option -%c!", c);
819               print_usage_and_exit ();
820             }
821         if (ctrl->cfg.rxbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN)
822           {
823             ctrl->rxbuf_size = ctrl->cfg.rxbuf_size;
824             sock_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ ,
825                                  (uint8_t **) & ctrl->rxbuf,
826                                  &ctrl->rxbuf_size);
827           }
828         else
829           {
830             vtwrn ("rxbuf size (%lu) less than minumum (%u)",
831                    ctrl->cfg.rxbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN);
832             print_usage_and_exit ();
833           }
834
835         break;
836
837       case 'T':
838         if (sscanf (optarg, "0x%lx", &ctrl->cfg.txbuf_size) != 1)
839           if (sscanf (optarg, "%ld", &ctrl->cfg.txbuf_size) != 1)
840             {
841               vtwrn ("Invalid value for option -%c!", c);
842               print_usage_and_exit ();
843             }
844         if (ctrl->cfg.txbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN)
845           {
846             ctrl->txbuf_size = ctrl->cfg.txbuf_size;
847             sock_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ ,
848                                  (uint8_t **) & ctrl->txbuf,
849                                  &ctrl->txbuf_size);
850             ctrl->cfg.total_bytes =
851               ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
852           }
853         else
854           {
855             vtwrn ("txbuf size (%lu) less than minumum (%u)!",
856                    ctrl->cfg.txbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN);
857             print_usage_and_exit ();
858           }
859         break;
860
861       case 'U':
862         ctrl->cfg.test = SOCK_TEST_TYPE_UNI;
863         break;
864
865       case 'B':
866         ctrl->cfg.test = SOCK_TEST_TYPE_BI;
867         break;
868
869       case 'V':
870         ctrl->cfg.verbose = 1;
871         break;
872
873       case '6':
874         ctrl->cfg.address_ip6 = 1;
875         break;
876
877       case 'D':
878         ctrl->cfg.transport_udp = 1;
879         break;
880
881       case '?':
882         switch (optopt)
883           {
884           case 'E':
885           case 'I':
886           case 'N':
887           case 'R':
888           case 'T':
889           case 'w':
890             vtwrn ("Option -%c requires an argument.", optopt);
891             break;
892
893           default:
894             if (isprint (optopt))
895               vtwrn ("Unknown option `-%c'.", optopt);
896             else
897               vtwrn ("Unknown option character `\\x%x'.", optopt);
898           }
899         /* fall thru */
900       case 'h':
901       default:
902         print_usage_and_exit ();
903       }
904
905   if (argc < (optind + 2))
906     {
907       vtwrn ("Insufficient number of arguments!");
908       print_usage_and_exit ();
909     }
910   vcm->proto = ctrl->cfg.transport_udp ? VPPCOM_PROTO_UDP : VPPCOM_PROTO_TCP;
911
912   memset (&vcm->server_addr, 0, sizeof (vcm->server_addr));
913   if (ctrl->cfg.address_ip6)
914     {
915       struct sockaddr_in6 *sddr6 = (struct sockaddr_in6 *) &vcm->server_addr;
916       sddr6->sin6_family = AF_INET6;
917       inet_pton (AF_INET6, argv[optind++], &(sddr6->sin6_addr));
918       sddr6->sin6_port = htons (atoi (argv[optind]));
919
920       vcm->server_endpt.is_ip4 = 0;
921       vcm->server_endpt.ip = (uint8_t *) & sddr6->sin6_addr;
922       vcm->server_endpt.port = (uint16_t) sddr6->sin6_port;
923     }
924   else
925     {
926       struct sockaddr_in *saddr4 = (struct sockaddr_in *) &vcm->server_addr;
927       saddr4->sin_family = AF_INET;
928       inet_pton (AF_INET, argv[optind++], &(saddr4->sin_addr));
929       saddr4->sin_port = htons (atoi (argv[optind]));
930
931       vcm->server_endpt.is_ip4 = 1;
932       vcm->server_endpt.ip = (uint8_t *) & saddr4->sin_addr;
933       vcm->server_endpt.port = (uint16_t) saddr4->sin_port;
934     }
935 }
936
937 static void
938 vtc_read_user_input (sock_test_socket_t * ctrl)
939 {
940   printf ("\nType some characters and hit <return>\n"
941           "('" SOCK_TEST_TOKEN_HELP "' for help): ");
942
943   if (fgets (ctrl->txbuf, ctrl->txbuf_size, stdin) != NULL)
944     {
945       if (strlen (ctrl->txbuf) == 1)
946         {
947           printf ("\nNothing to send!  Please try again...\n");
948           return;
949         }
950       ctrl->txbuf[strlen (ctrl->txbuf) - 1] = 0;        // chomp the newline.
951
952       /* Parse input for keywords */
953       ctrl->cfg.test = parse_input ();
954     }
955 }
956
957 int
958 main (int argc, char **argv)
959 {
960   vcl_test_client_main_t *vcm = &vcl_client_main;
961   sock_test_socket_t *ctrl = &vcm->ctrl_socket;
962   int rv, errno_val;
963
964   vcm->n_workers = 1;
965   sock_test_cfg_init (&ctrl->cfg);
966   sock_test_socket_buf_alloc (ctrl);
967   vtc_process_opts (vcm, argc, argv);
968
969   vcm->workers = calloc (vcm->n_workers, sizeof (vcl_test_client_worker_t));
970   rv = vppcom_app_create ("vcl_test_client");
971   if (rv < 0)
972     vtfail ("vppcom_app_create()", rv);
973
974   ctrl->fd = vppcom_session_create (vcm->proto, 0 /* is_nonblocking */ );
975   if (ctrl->fd < 0)
976     vtfail ("vppcom_session_create()", ctrl->fd);
977
978   vtinf ("Connecting to server...");
979   rv = vppcom_session_connect (ctrl->fd, &vcm->server_endpt);
980   if (rv)
981     vtfail ("vppcom_session_connect()", rv);
982   vtinf ("(fd %d): Control socket connected.", ctrl->fd);
983
984   rv = vtc_cfg_sync (ctrl);
985   if (rv)
986     vtfail ("vtc_cfg_sync()", rv);
987
988   ctrl->cfg.ctrl_handle = ((sock_test_cfg_t *) ctrl->rxbuf)->ctrl_handle;
989   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
990
991   while (ctrl->cfg.test != SOCK_TEST_TYPE_EXIT)
992     {
993       if (vcm->dump_cfg)
994         {
995           sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
996           vcm->dump_cfg = 0;
997         }
998
999       switch (ctrl->cfg.test)
1000         {
1001         case SOCK_TEST_TYPE_ECHO:
1002           vtc_echo_client (vcm);
1003           break;
1004
1005         case SOCK_TEST_TYPE_UNI:
1006         case SOCK_TEST_TYPE_BI:
1007           vtc_stream_client (vcm);
1008           break;
1009
1010         case SOCK_TEST_TYPE_EXIT:
1011           continue;
1012
1013         case SOCK_TEST_TYPE_NONE:
1014         default:
1015           break;
1016         }
1017       switch (vcm->post_test)
1018         {
1019         case SOCK_TEST_TYPE_EXIT:
1020           switch (ctrl->cfg.test)
1021             {
1022             case SOCK_TEST_TYPE_EXIT:
1023             case SOCK_TEST_TYPE_UNI:
1024             case SOCK_TEST_TYPE_BI:
1025             case SOCK_TEST_TYPE_ECHO:
1026               ctrl->cfg.test = SOCK_TEST_TYPE_EXIT;
1027               continue;
1028
1029             case SOCK_TEST_TYPE_NONE:
1030             default:
1031               break;
1032             }
1033           break;
1034
1035         case SOCK_TEST_TYPE_NONE:
1036         case SOCK_TEST_TYPE_ECHO:
1037         case SOCK_TEST_TYPE_UNI:
1038         case SOCK_TEST_TYPE_BI:
1039         default:
1040           break;
1041         }
1042
1043       memset (ctrl->txbuf, 0, ctrl->txbuf_size);
1044       memset (ctrl->rxbuf, 0, ctrl->rxbuf_size);
1045
1046       vtc_read_user_input (ctrl);
1047     }
1048
1049   vtc_client_exit ();
1050   vppcom_session_close (ctrl->fd);
1051   vppcom_app_destroy ();
1052   free (vcm->workers);
1053   return 0;
1054 }
1055
1056 /*
1057  * fd.io coding-style-patch-verification: ON
1058  *
1059  * Local Variables:
1060  * eval: (c-set-style "gnu")
1061  * End:
1062  */