tcp: make syn-rcvd timeout configurable
[vpp.git] / src / plugins / hs_apps / http_tps.c
1 /*
2  * Copyright (c) 2022 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 <vnet/session/application.h>
17 #include <vnet/session/application_interface.h>
18 #include <vnet/session/session.h>
19 #include <http/http.h>
20
21 typedef struct
22 {
23   CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
24   u32 session_index;
25   u32 thread_index;
26   u64 data_len;
27   u64 data_offset;
28   u32 vpp_session_index;
29   union
30   {
31     /** threshold after which connection is closed */
32     f64 close_threshold;
33     /** rate at which accepted sessions are marked for random close */
34     u32 close_rate;
35   };
36   u8 *uri;
37 } hts_session_t;
38
39 typedef struct hts_listen_cfg_
40 {
41   u8 *uri;
42   u32 vrf;
43   f64 rnd_close;
44   u8 is_del;
45 } hts_listen_cfg_t;
46
47 typedef struct hs_main_
48 {
49   hts_session_t **sessions;
50   u32 app_index;
51
52   u32 ckpair_index;
53   u8 *test_data;
54
55   /** Hash table of listener uris to handles */
56   uword *uri_to_handle;
57
58   /*
59    * Configs
60    */
61   u8 *uri;
62   u32 fifo_size;
63   u64 segment_size;
64   u8 debug_level;
65   u8 no_zc;
66   u8 *default_uri;
67   u32 seed;
68 } hts_main_t;
69
70 static hts_main_t hts_main;
71
72 static hts_session_t *
73 hts_session_alloc (u32 thread_index)
74 {
75   hts_main_t *htm = &hts_main;
76   hts_session_t *hs;
77
78   pool_get_zero (htm->sessions[thread_index], hs);
79   hs->session_index = hs - htm->sessions[thread_index];
80   hs->thread_index = thread_index;
81
82   return hs;
83 }
84
85 static hts_session_t *
86 hts_session_get (u32 thread_index, u32 hts_index)
87 {
88   hts_main_t *htm = &hts_main;
89
90   if (pool_is_free_index (htm->sessions[thread_index], hts_index))
91     return 0;
92
93   return pool_elt_at_index (htm->sessions[thread_index], hts_index);
94 }
95
96 static void
97 hts_session_free (hts_session_t *hs)
98 {
99   hts_main_t *htm = &hts_main;
100   u32 thread = hs->thread_index;
101
102   if (htm->debug_level > 0)
103     clib_warning ("Freeing session %u", hs->session_index);
104
105   if (CLIB_DEBUG)
106     clib_memset (hs, 0xfa, sizeof (*hs));
107
108   pool_put (htm->sessions[thread], hs);
109 }
110
111 static void
112 hts_disconnect_transport (hts_session_t *hs)
113 {
114   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
115   hts_main_t *htm = &hts_main;
116   session_t *ts;
117
118   if (htm->debug_level > 0)
119     clib_warning ("Actively closing session %u", hs->session_index);
120
121   ts = session_get (hs->vpp_session_index, hs->thread_index);
122   a->handle = session_handle (ts);
123   a->app_index = htm->app_index;
124   vnet_disconnect_session (a);
125 }
126
127 static void
128 hts_session_tx_zc (hts_session_t *hs, session_t *ts)
129 {
130   u32 to_send, space;
131   u64 max_send;
132   int rv;
133
134   rv = svm_fifo_fill_chunk_list (ts->tx_fifo);
135   if (rv < 0)
136     {
137       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
138       return;
139     }
140
141   max_send = hs->data_len - hs->data_offset;
142   space = svm_fifo_max_enqueue (ts->tx_fifo);
143   ASSERT (space != 0);
144   to_send = clib_min (space, max_send);
145
146   svm_fifo_enqueue_nocopy (ts->tx_fifo, to_send);
147
148   hs->data_offset += to_send;
149
150   if (to_send < max_send)
151     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
152
153   if (svm_fifo_set_event (ts->tx_fifo))
154     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
155 }
156
157 static void
158 hts_session_tx_no_zc (hts_session_t *hs, session_t *ts)
159 {
160   u32 n_segs, buf_offset, buf_left;
161   u64 max_send = 32 << 10, left;
162   hts_main_t *htm = &hts_main;
163   svm_fifo_seg_t seg[2];
164   int sent;
165
166   left = hs->data_len - hs->data_offset;
167   max_send = clib_min (left, max_send);
168   buf_offset = hs->data_offset % vec_len (htm->test_data);
169   buf_left = vec_len (htm->test_data) - buf_offset;
170
171   if (buf_left < max_send)
172     {
173       seg[0].data = htm->test_data + buf_offset;
174       seg[0].len = buf_left;
175       seg[1].data = htm->test_data;
176       seg[1].len = max_send - buf_left;
177       n_segs = 2;
178     }
179   else
180     {
181       seg[0].data = htm->test_data + buf_offset;
182       seg[0].len = max_send;
183       n_segs = 1;
184     }
185
186   sent = svm_fifo_enqueue_segments (ts->tx_fifo, seg, n_segs,
187                                     1 /* allow partial */);
188
189   if (sent <= 0)
190     {
191       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
192       return;
193     }
194
195   hs->data_offset += sent;
196
197   if (sent < left)
198     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
199
200   if (svm_fifo_set_event (ts->tx_fifo))
201     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
202 }
203
204 static inline void
205 hts_session_tx (hts_session_t *hs, session_t *ts)
206 {
207   hts_main_t *htm = &hts_main;
208
209   if (!htm->no_zc)
210     hts_session_tx_zc (hs, ts);
211   else
212     hts_session_tx_no_zc (hs, ts);
213
214   if (hs->close_threshold > 0)
215     {
216       if ((f64) hs->data_offset / hs->data_len > hs->close_threshold)
217         hts_disconnect_transport (hs);
218     }
219 }
220
221 static void
222 hts_start_send_data (hts_session_t *hs, http_status_code_t status)
223 {
224   http_msg_t msg;
225   session_t *ts;
226   int rv;
227
228   msg.type = HTTP_MSG_REPLY;
229   msg.code = status;
230   msg.content_type = HTTP_CONTENT_APP_OCTET_STREAM;
231   msg.data.type = HTTP_MSG_DATA_INLINE;
232   msg.data.len = hs->data_len;
233
234   ts = session_get (hs->vpp_session_index, hs->thread_index);
235   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
236   ASSERT (rv == sizeof (msg));
237
238   if (!msg.data.len)
239     {
240       if (svm_fifo_set_event (ts->tx_fifo))
241         session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
242       return;
243     }
244
245   hts_session_tx (hs, ts);
246 }
247
248 static int
249 try_test_file (hts_session_t *hs, u8 *target)
250 {
251   char *test_str = "test_file";
252   hts_main_t *htm = &hts_main;
253   unformat_input_t input;
254   uword file_size;
255   int rc = 0;
256
257   if (memcmp (target, test_str, clib_strnlen (test_str, 9)))
258     return -1;
259
260   unformat_init_vector (&input, vec_dup (target));
261   if (!unformat (&input, "test_file_%U", unformat_memory_size, &file_size))
262     {
263       rc = -1;
264       goto done;
265     }
266
267   if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
268     {
269       rc = -1;
270       goto done;
271     }
272
273   if (htm->debug_level)
274     clib_warning ("Requested file size %U", format_memory_size, file_size);
275
276   hs->data_len = file_size;
277   hs->data_offset = 0;
278
279   if (hs->close_threshold > 0)
280     {
281       /* Disconnect if the header is already enough to fill the quota */
282       if ((f64) 30 / hs->data_len > hs->close_threshold)
283         {
284           hts_disconnect_transport (hs);
285           goto done;
286         }
287     }
288
289   hts_start_send_data (hs, HTTP_STATUS_OK);
290
291 done:
292   unformat_free (&input);
293
294   return rc;
295 }
296
297 static int
298 hts_ts_rx_callback (session_t *ts)
299 {
300   hts_main_t *htm = &hts_main;
301   hts_session_t *hs;
302   u8 *target = 0;
303   http_msg_t msg;
304   int rv;
305
306   hs = hts_session_get (ts->thread_index, ts->opaque);
307
308   /* Read the http message header */
309   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
310   ASSERT (rv == sizeof (msg));
311
312   if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
313     {
314       hts_start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
315       goto done;
316     }
317
318   if (msg.data.target_path_len == 0 ||
319       msg.data.target_form != HTTP_TARGET_ORIGIN_FORM)
320     {
321       hts_start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
322       goto done;
323     }
324
325   vec_validate (target, msg.data.target_path_len - 1);
326   rv = svm_fifo_peek (ts->rx_fifo, msg.data.target_path_offset,
327                       msg.data.target_path_len, target);
328   ASSERT (rv == msg.data.target_path_len);
329
330   if (htm->debug_level)
331     clib_warning ("Request target: %v", target);
332
333   if (try_test_file (hs, target))
334     hts_start_send_data (hs, HTTP_STATUS_NOT_FOUND);
335
336   vec_free (target);
337
338 done:
339   svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.len);
340   return 0;
341 }
342
343 static int
344 hs_ts_tx_callback (session_t *ts)
345 {
346   hts_session_t *hs;
347
348   hs = hts_session_get (ts->thread_index, ts->opaque);
349   if (!hs)
350     return 0;
351
352   hts_session_tx (hs, ts);
353
354   return 0;
355 }
356
357 static int
358 hts_ts_accept_callback (session_t *ts)
359 {
360   hts_main_t *htm = &hts_main;
361   hts_session_t *hs, *lhs;
362   session_t *ls;
363
364   hs = hts_session_alloc (ts->thread_index);
365   hs->vpp_session_index = ts->session_index;
366
367   ts->opaque = hs->session_index;
368   ts->session_state = SESSION_STATE_READY;
369
370   /* Check if listener configured for random closes */
371   ls = listen_session_get_from_handle (ts->listener_handle);
372   lhs = hts_session_get (0, ls->opaque);
373
374   if (lhs->close_rate)
375     {
376       /* overload listener's data_offset as session counter */
377       u32 cnt = __atomic_add_fetch (&lhs->data_offset, 1, __ATOMIC_RELEASE);
378       if ((cnt % lhs->close_rate) == 0)
379         hs->close_threshold = random_f64 (&htm->seed);
380     }
381
382   if (htm->debug_level > 0)
383     clib_warning ("Accepted session %u close threshold %.2f", ts->opaque,
384                   hs->close_threshold);
385
386   return 0;
387 }
388
389 static int
390 hts_ts_connected_callback (u32 app_index, u32 api_context, session_t *s,
391                            session_error_t err)
392 {
393   clib_warning ("called...");
394   return -1;
395 }
396
397 static void
398 hts_ts_disconnect_callback (session_t *ts)
399 {
400   hts_main_t *htm = &hts_main;
401   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
402
403   if (htm->debug_level > 0)
404     clib_warning ("Transport closing session %u", ts->opaque);
405
406   a->handle = session_handle (ts);
407   a->app_index = htm->app_index;
408   vnet_disconnect_session (a);
409 }
410
411 static void
412 hts_ts_reset_callback (session_t *ts)
413 {
414   hts_main_t *htm = &hts_main;
415   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
416
417   if (htm->debug_level > 0)
418     clib_warning ("Transport reset session %u", ts->opaque);
419
420   a->handle = session_handle (ts);
421   a->app_index = htm->app_index;
422   vnet_disconnect_session (a);
423 }
424
425 static void
426 hts_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
427 {
428   hts_session_t *hs;
429
430   if (ntf == SESSION_CLEANUP_TRANSPORT)
431     return;
432
433   hs = hts_session_get (s->thread_index, s->opaque);
434   if (!hs)
435     return;
436
437   hts_session_free (hs);
438 }
439
440 static int
441 hts_add_segment_callback (u32 client_index, u64 segment_handle)
442 {
443   return 0;
444 }
445
446 static int
447 hts_del_segment_callback (u32 client_index, u64 segment_handle)
448 {
449   return 0;
450 }
451
452 static session_cb_vft_t hs_session_cb_vft = {
453   .session_accept_callback = hts_ts_accept_callback,
454   .session_disconnect_callback = hts_ts_disconnect_callback,
455   .session_connected_callback = hts_ts_connected_callback,
456   .add_segment_callback = hts_add_segment_callback,
457   .del_segment_callback = hts_del_segment_callback,
458   .builtin_app_rx_callback = hts_ts_rx_callback,
459   .builtin_app_tx_callback = hs_ts_tx_callback,
460   .session_reset_callback = hts_ts_reset_callback,
461   .session_cleanup_callback = hts_ts_cleanup_callback,
462 };
463
464 static int
465 hts_attach (hts_main_t *hm)
466 {
467   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
468   u64 options[APP_OPTIONS_N_OPTIONS];
469   vnet_app_attach_args_t _a, *a = &_a;
470
471   clib_memset (a, 0, sizeof (*a));
472   clib_memset (options, 0, sizeof (options));
473
474   a->api_client_index = ~0;
475   a->name = format (0, "http_tps");
476   a->session_cb_vft = &hs_session_cb_vft;
477   a->options = options;
478   a->options[APP_OPTIONS_SEGMENT_SIZE] = hm->segment_size;
479   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = hm->segment_size;
480   a->options[APP_OPTIONS_RX_FIFO_SIZE] = hm->fifo_size;
481   a->options[APP_OPTIONS_TX_FIFO_SIZE] = hm->fifo_size;
482   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
483
484   if (vnet_application_attach (a))
485     {
486       vec_free (a->name);
487       clib_warning ("failed to attach server");
488       return -1;
489     }
490   vec_free (a->name);
491   hm->app_index = a->app_index;
492
493   clib_memset (ck_pair, 0, sizeof (*ck_pair));
494   ck_pair->cert = (u8 *) test_srv_crt_rsa;
495   ck_pair->key = (u8 *) test_srv_key_rsa;
496   ck_pair->cert_len = test_srv_crt_rsa_len;
497   ck_pair->key_len = test_srv_key_rsa_len;
498   vnet_app_add_cert_key_pair (ck_pair);
499   hm->ckpair_index = ck_pair->index;
500
501   return 0;
502 }
503
504 static int
505 hts_transport_needs_crypto (transport_proto_t proto)
506 {
507   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
508          proto == TRANSPORT_PROTO_QUIC;
509 }
510
511 static int
512 hts_start_listen (hts_main_t *htm, session_endpoint_cfg_t *sep, u8 *uri,
513                   f64 rnd_close)
514 {
515   vnet_listen_args_t _a, *a = &_a;
516   u8 need_crypto;
517   hts_session_t *hls;
518   session_t *ls;
519   u32 thread_index = 0;
520   int rv;
521
522   clib_memset (a, 0, sizeof (*a));
523   a->app_index = htm->app_index;
524
525   need_crypto = hts_transport_needs_crypto (sep->transport_proto);
526
527   sep->transport_proto = TRANSPORT_PROTO_HTTP;
528   clib_memcpy (&a->sep_ext, sep, sizeof (*sep));
529
530   if (need_crypto)
531     {
532       session_endpoint_alloc_ext_cfg (&a->sep_ext,
533                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
534       a->sep_ext.ext_cfg->crypto.ckpair_index = htm->ckpair_index;
535     }
536
537   rv = vnet_listen (a);
538
539   if (need_crypto)
540     clib_mem_free (a->sep_ext.ext_cfg);
541
542   if (rv)
543     return rv;
544
545   hls = hts_session_alloc (thread_index);
546   hls->uri = vec_dup (uri);
547   hls->close_rate = (f64) 1 / rnd_close;
548   ls = listen_session_get_from_handle (a->handle);
549   hls->vpp_session_index = ls->session_index;
550   hash_set_mem (htm->uri_to_handle, hls->uri, hls->session_index);
551
552   /* opaque holds index of hls, which is used in `hts_ts_accept_callback`
553    * to get back the pointer to hls */
554   ls->opaque = hls - htm->sessions[thread_index];
555
556   return 0;
557 }
558
559 static int
560 hts_stop_listen (hts_main_t *htm, u32 hls_index)
561 {
562   hts_session_t *hls;
563   session_t *ls;
564
565   hls = hts_session_get (0, hls_index);
566   ls = listen_session_get (hls->vpp_session_index);
567
568   vnet_unlisten_args_t ua = {
569     .handle = listen_session_get_handle (ls),
570     .app_index = htm->app_index,
571     .wrk_map_index = 0 /* default wrk */
572   };
573
574   hash_unset_mem (htm->uri_to_handle, hls->uri);
575
576   if (vnet_unlisten (&ua))
577     return -1;
578
579   vec_free (hls->uri);
580   hts_session_free (hls);
581
582   return 0;
583 }
584
585 static clib_error_t *
586 hts_listen (hts_main_t *htm, hts_listen_cfg_t *lcfg)
587 {
588   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
589   clib_error_t *error = 0;
590   u8 *uri, *uri_key;
591   uword *p;
592   int rv;
593
594   uri = lcfg->uri ? lcfg->uri : htm->default_uri;
595   uri_key = format (0, "vrf%u-%s", lcfg->vrf, uri);
596   p = hash_get_mem (htm->uri_to_handle, uri_key);
597
598   if (lcfg->is_del)
599     {
600       if (!p)
601         error = clib_error_return (0, "not listening on %v", uri);
602       else if (hts_stop_listen (htm, p[0]))
603         error = clib_error_return (0, "failed to unlisten");
604       goto done;
605     }
606
607   if (p)
608     {
609       error = clib_error_return (0, "already listening %v", uri);
610       goto done;
611     }
612
613   if (parse_uri ((char *) uri, &sep))
614     {
615       error = clib_error_return (0, "failed to parse uri %v", uri);
616       goto done;
617     }
618
619   if (lcfg->vrf)
620     {
621       fib_protocol_t fp;
622       u32 fib_index;
623
624       fp = sep.is_ip4 ? FIB_PROTOCOL_IP4 : FIB_PROTOCOL_IP6;
625       fib_index = fib_table_find (fp, lcfg->vrf);
626       if (fib_index == ~0)
627         {
628           error = clib_error_return (0, "no such vrf %u", lcfg->vrf);
629           goto done;
630         }
631       sep.fib_index = fib_index;
632     }
633
634   if ((rv = hts_start_listen (htm, &sep, uri_key, lcfg->rnd_close)))
635     {
636       error = clib_error_return (0, "failed to listen on %v: %U", uri,
637                                  format_session_error, rv);
638     }
639
640 done:
641
642   vec_free (uri_key);
643   return error;
644 }
645
646 static int
647 hts_create (vlib_main_t *vm)
648 {
649   vlib_thread_main_t *vtm = vlib_get_thread_main ();
650   hts_main_t *htm = &hts_main;
651   u32 num_threads;
652
653   num_threads = 1 /* main thread */ + vtm->n_threads;
654   vec_validate (htm->sessions, num_threads - 1);
655
656   if (htm->no_zc)
657     vec_validate (htm->test_data, (64 << 10) - 1);
658
659   if (hts_attach (htm))
660     {
661       clib_warning ("failed to attach server");
662       return -1;
663     }
664
665   htm->default_uri = format (0, "tcp://0.0.0.0/80%c", 0);
666   htm->uri_to_handle = hash_create_vec (0, sizeof (u8), sizeof (uword));
667
668   return 0;
669 }
670
671 static clib_error_t *
672 hts_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
673                        vlib_cli_command_t *cmd)
674 {
675   unformat_input_t _line_input, *line_input = &_line_input;
676   hts_main_t *htm = &hts_main;
677   hts_listen_cfg_t lcfg = {};
678   clib_error_t *error = 0;
679   u64 mem_size;
680
681   /* Get a line of input. */
682   if (!unformat_user (input, unformat_line_input, line_input))
683     goto start_server;
684
685   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
686     {
687       if (unformat (line_input, "private-segment-size %U",
688                     unformat_memory_size, &mem_size))
689         htm->segment_size = mem_size;
690       else if (unformat (line_input, "fifo-size %U", unformat_memory_size,
691                          &mem_size))
692         htm->fifo_size = mem_size;
693       else if (unformat (line_input, "no-zc"))
694         htm->no_zc = 1;
695       else if (unformat (line_input, "debug"))
696         htm->debug_level = 1;
697       else if (unformat (line_input, "vrf %u", &lcfg.vrf))
698         ;
699       else if (unformat (line_input, "uri %s", &lcfg.uri))
700         ;
701       else if (unformat (line_input, "rnd-close %f", &lcfg.rnd_close))
702         {
703           if (lcfg.rnd_close > 1.0)
704             {
705               error = clib_error_return (0, "invalid rnd close value %f",
706                                          lcfg.rnd_close);
707               break;
708             }
709         }
710       else if (unformat (line_input, "del"))
711         lcfg.is_del = 1;
712       else
713         {
714           error = clib_error_return (0, "unknown input `%U'",
715                                      format_unformat_error, line_input);
716           break;
717         }
718     }
719
720   unformat_free (line_input);
721
722   if (error)
723     goto done;
724
725 start_server:
726
727   if (htm->app_index == (u32) ~0)
728     {
729       vnet_session_enable_disable (vm, 1 /* is_enable */);
730
731       if (hts_create (vm))
732         {
733           error = clib_error_return (0, "http tps create failed");
734           goto done;
735         }
736     }
737
738   error = hts_listen (htm, &lcfg);
739
740 done:
741
742   vec_free (lcfg.uri);
743   return error;
744 }
745
746 VLIB_CLI_COMMAND (http_tps_command, static) = {
747   .path = "http tps",
748   .short_help = "http tps [uri <uri>] [fifo-size <nbytes>] "
749                 "[segment-size <nMG>] [prealloc-fifos <n>] [debug] [no-zc] "
750                 "[del]",
751   .function = hts_create_command_fn,
752 };
753
754 static clib_error_t *
755 hts_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
756                      vlib_cli_command_t *cmd)
757 {
758   unformat_input_t _line_input, *line_input = &_line_input;
759   hts_main_t *htm = &hts_main;
760   clib_error_t *error = 0;
761   u8 do_listeners = 0;
762   hts_session_t **sessions;
763   u32 n_listeners = 0, n_sessions = 0;
764
765   if (!unformat_user (input, unformat_line_input, line_input))
766     goto no_input;
767
768   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
769     {
770       if (unformat (line_input, "listeners"))
771         do_listeners = 1;
772       else
773         {
774           error = clib_error_return (0, "unknown input `%U'",
775                                      format_unformat_error, line_input);
776           break;
777         }
778     }
779
780   if (error)
781     return error;
782
783 no_input:
784
785   if (htm->app_index == ~0)
786     {
787       vlib_cli_output (vm, "http tps not enabled");
788       goto done;
789     }
790
791   if (do_listeners)
792     {
793       uword handle;
794       u8 *s = 0, *uri;
795
796       /* clang-format off */
797       hash_foreach (uri, handle, htm->uri_to_handle, ({
798         s = format (s, "%-30v%lx\n", uri, handle);
799       }));
800       /* clang-format on */
801
802       if (s)
803         {
804           vlib_cli_output (vm, "%-29s%s", "URI", "Index");
805           vlib_cli_output (vm, "%v", s);
806           vec_free (s);
807         }
808       goto done;
809     }
810
811   n_listeners = hash_elts (htm->uri_to_handle);
812   vec_foreach (sessions, htm->sessions)
813     n_sessions += pool_elts (*sessions);
814
815   vlib_cli_output (vm, " app index: %u\n listeners: %u\n sesions: %u",
816                    htm->app_index, n_listeners, n_sessions - n_listeners);
817
818 done:
819   return 0;
820 }
821
822 VLIB_CLI_COMMAND (show_http_tps_command, static) = {
823   .path = "show http tps",
824   .short_help = "http tps [listeners]",
825   .function = hts_show_command_fn,
826 };
827
828 static clib_error_t *
829 hs_main_init (vlib_main_t *vm)
830 {
831   hts_main_t *htm = &hts_main;
832
833   htm->app_index = ~0;
834   htm->segment_size = 128 << 20;
835   htm->fifo_size = 64 << 10;
836
837   return 0;
838 }
839
840 VLIB_INIT_FUNCTION (hs_main_init);
841
842 /*
843  * fd.io coding-style-patch-verification: ON
844  *
845  * Local Variables:
846  * eval: (c-set-style "gnu")
847  * End:
848  */