hsa: add http throughput test server app
[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 } hts_session_t;
30
31 typedef struct hs_main_
32 {
33   hts_session_t **sessions;
34   u32 app_index;
35
36   u32 ckpair_index;
37   u8 *test_data;
38
39   /*
40    * Configs
41    */
42   u8 *uri;
43   u32 fifo_size;
44   u64 segment_size;
45   u8 debug_level;
46   u8 no_zc;
47 } hts_main_t;
48
49 static hts_main_t hts_main;
50
51 static hts_session_t *
52 hts_session_alloc (u32 thread_index)
53 {
54   hts_main_t *htm = &hts_main;
55   hts_session_t *hs;
56
57   pool_get_zero (htm->sessions[thread_index], hs);
58   hs->session_index = hs - htm->sessions[thread_index];
59   hs->thread_index = thread_index;
60
61   return hs;
62 }
63
64 static hts_session_t *
65 hts_session_get (u32 thread_index, u32 hts_index)
66 {
67   hts_main_t *htm = &hts_main;
68
69   if (pool_is_free_index (htm->sessions[thread_index], hts_index))
70     return 0;
71
72   return pool_elt_at_index (htm->sessions[thread_index], hts_index);
73 }
74
75 static void
76 hts_session_free (hts_session_t *hs)
77 {
78   hts_main_t *htm = &hts_main;
79   u32 thread = hs->thread_index;
80
81   if (CLIB_DEBUG)
82     clib_memset (hs, 0xfa, sizeof (*hs));
83
84   pool_put (htm->sessions[thread], hs);
85 }
86
87 static void
88 hts_session_tx_zc (hts_session_t *hs, session_t *ts)
89 {
90   u32 to_send, space;
91   u64 max_send;
92   int rv;
93
94   rv = svm_fifo_fill_chunk_list (ts->tx_fifo);
95   if (rv < 0)
96     {
97       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
98       return;
99     }
100
101   max_send = hs->data_len - hs->data_offset;
102   space = svm_fifo_max_enqueue (ts->tx_fifo);
103   ASSERT (space != 0);
104   to_send = clib_min (space, max_send);
105
106   svm_fifo_enqueue_nocopy (ts->tx_fifo, to_send);
107
108   hs->data_offset += to_send;
109
110   if (to_send < max_send)
111     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
112
113   if (svm_fifo_set_event (ts->tx_fifo))
114     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
115 }
116
117 static void
118 hts_session_tx_no_zc (hts_session_t *hs, session_t *ts)
119 {
120   u32 n_segs, buf_offset, buf_left;
121   u64 max_send = 32 << 10, left;
122   hts_main_t *htm = &hts_main;
123   svm_fifo_seg_t seg[2];
124   int sent;
125
126   left = hs->data_len - hs->data_offset;
127   max_send = clib_min (left, max_send);
128   buf_offset = hs->data_offset % vec_len (htm->test_data);
129   buf_left = vec_len (htm->test_data) - buf_offset;
130
131   if (buf_left < max_send)
132     {
133       seg[0].data = htm->test_data + buf_offset;
134       seg[0].len = buf_left;
135       seg[1].data = htm->test_data;
136       seg[1].len = max_send - buf_left;
137       n_segs = 2;
138     }
139   else
140     {
141       seg[0].data = htm->test_data + buf_offset;
142       seg[0].len = max_send;
143       n_segs = 1;
144     }
145
146   sent = svm_fifo_enqueue_segments (ts->tx_fifo, seg, n_segs,
147                                     1 /* allow partial */);
148
149   if (sent <= 0)
150     {
151       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
152       return;
153     }
154
155   hs->data_offset += sent;
156
157   if (sent < left)
158     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
159
160   if (svm_fifo_set_event (ts->tx_fifo))
161     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
162 }
163
164 static inline void
165 hts_session_tx (hts_session_t *hs, session_t *ts)
166 {
167   hts_main_t *htm = &hts_main;
168
169   if (!htm->no_zc)
170     hts_session_tx_zc (hs, ts);
171   else
172     hts_session_tx_no_zc (hs, ts);
173 }
174
175 static void
176 hts_start_send_data (hts_session_t *hs, http_status_code_t status)
177 {
178   http_msg_t msg;
179   session_t *ts;
180   int rv;
181
182   msg.type = HTTP_MSG_REPLY;
183   msg.code = status;
184   msg.content_type = HTTP_CONTENT_TEXT_HTML;
185   msg.data.type = HTTP_MSG_DATA_INLINE;
186   msg.data.len = hs->data_len;
187
188   ts = session_get (hs->vpp_session_index, hs->thread_index);
189   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
190   ASSERT (rv == sizeof (msg));
191
192   if (!msg.data.len)
193     {
194       if (svm_fifo_set_event (ts->tx_fifo))
195         session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
196       return;
197     }
198
199   hts_session_tx (hs, ts);
200 }
201
202 static int
203 try_test_file (hts_session_t *hs, u8 *request)
204 {
205   char *test_str = "test_file";
206   hts_main_t *htm = &hts_main;
207   unformat_input_t input;
208   uword file_size;
209   int rc = 0;
210
211   if (memcmp (request, test_str, clib_strnlen (test_str, 9)))
212     return -1;
213
214   unformat_init_vector (&input, vec_dup (request));
215   if (!unformat (&input, "test_file_%U", unformat_memory_size, &file_size))
216     {
217       rc = -1;
218       goto done;
219     }
220
221   if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
222     {
223       rc = -1;
224       goto done;
225     }
226
227   if (htm->debug_level)
228     clib_warning ("Requested file size %U", format_memory_size, file_size);
229
230   hs->data_len = file_size;
231
232   hts_start_send_data (hs, HTTP_STATUS_OK);
233
234 done:
235   unformat_free (&input);
236
237   return rc;
238 }
239
240 static int
241 hts_ts_rx_callback (session_t *ts)
242 {
243   hts_session_t *hs;
244   u8 *request = 0;
245   http_msg_t msg;
246   int rv;
247
248   hs = hts_session_get (ts->thread_index, ts->opaque);
249
250   /* Read the http message header */
251   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
252   ASSERT (rv == sizeof (msg));
253
254   if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
255     {
256       hts_start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
257       goto done;
258     }
259
260   if (!msg.data.len)
261     {
262       hts_start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
263       goto done;
264     }
265
266   vec_validate (request, msg.data.len - 1);
267   rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
268
269   if (try_test_file (hs, request))
270     hts_start_send_data (hs, HTTP_STATUS_NOT_FOUND);
271
272 done:
273
274   return 0;
275 }
276
277 static int
278 hs_ts_tx_callback (session_t *ts)
279 {
280   hts_session_t *hs;
281
282   hs = hts_session_get (ts->thread_index, ts->opaque);
283   if (!hs)
284     return 0;
285
286   hts_session_tx (hs, ts);
287
288   return 0;
289 }
290
291 static int
292 hts_ts_accept_callback (session_t *ts)
293 {
294   hts_session_t *hs;
295
296   hs = hts_session_alloc (ts->thread_index);
297   hs->vpp_session_index = ts->session_index;
298
299   ts->opaque = hs->session_index;
300   ts->session_state = SESSION_STATE_READY;
301
302   return 0;
303 }
304
305 static int
306 hts_ts_connected_callback (u32 app_index, u32 api_context, session_t *s,
307                            session_error_t err)
308 {
309   clib_warning ("called...");
310   return -1;
311 }
312
313 static void
314 hts_ts_disconnect_callback (session_t *s)
315 {
316   hts_main_t *htm = &hts_main;
317   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
318
319   a->handle = session_handle (s);
320   a->app_index = htm->app_index;
321   vnet_disconnect_session (a);
322 }
323
324 static void
325 hts_ts_reset_callback (session_t *s)
326 {
327   hts_main_t *htm = &hts_main;
328   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
329
330   a->handle = session_handle (s);
331   a->app_index = htm->app_index;
332   vnet_disconnect_session (a);
333 }
334
335 static void
336 hts_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
337 {
338   hts_session_t *hs;
339
340   if (ntf == SESSION_CLEANUP_TRANSPORT)
341     return;
342
343   hs = hts_session_get (s->thread_index, s->opaque);
344   if (!hs)
345     return;
346
347   hts_session_free (hs);
348 }
349
350 static int
351 hts_add_segment_callback (u32 client_index, u64 segment_handle)
352 {
353   return 0;
354 }
355
356 static int
357 hts_del_segment_callback (u32 client_index, u64 segment_handle)
358 {
359   return 0;
360 }
361
362 static session_cb_vft_t hs_session_cb_vft = {
363   .session_accept_callback = hts_ts_accept_callback,
364   .session_disconnect_callback = hts_ts_disconnect_callback,
365   .session_connected_callback = hts_ts_connected_callback,
366   .add_segment_callback = hts_add_segment_callback,
367   .del_segment_callback = hts_del_segment_callback,
368   .builtin_app_rx_callback = hts_ts_rx_callback,
369   .builtin_app_tx_callback = hs_ts_tx_callback,
370   .session_reset_callback = hts_ts_reset_callback,
371   .session_cleanup_callback = hts_ts_cleanup_callback,
372 };
373
374 static int
375 hts_attach (hts_main_t *hm)
376 {
377   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
378   u64 options[APP_OPTIONS_N_OPTIONS];
379   vnet_app_attach_args_t _a, *a = &_a;
380
381   clib_memset (a, 0, sizeof (*a));
382   clib_memset (options, 0, sizeof (options));
383
384   a->api_client_index = ~0;
385   a->name = format (0, "http_tps");
386   a->session_cb_vft = &hs_session_cb_vft;
387   a->options = options;
388   a->options[APP_OPTIONS_SEGMENT_SIZE] = hm->segment_size;
389   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = hm->segment_size;
390   a->options[APP_OPTIONS_RX_FIFO_SIZE] = hm->fifo_size;
391   a->options[APP_OPTIONS_TX_FIFO_SIZE] = hm->fifo_size;
392   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
393
394   if (vnet_application_attach (a))
395     {
396       vec_free (a->name);
397       clib_warning ("failed to attach server");
398       return -1;
399     }
400   vec_free (a->name);
401   hm->app_index = a->app_index;
402
403   clib_memset (ck_pair, 0, sizeof (*ck_pair));
404   ck_pair->cert = (u8 *) test_srv_crt_rsa;
405   ck_pair->key = (u8 *) test_srv_key_rsa;
406   ck_pair->cert_len = test_srv_crt_rsa_len;
407   ck_pair->key_len = test_srv_key_rsa_len;
408   vnet_app_add_cert_key_pair (ck_pair);
409   hm->ckpair_index = ck_pair->index;
410
411   return 0;
412 }
413
414 static int
415 hts_transport_needs_crypto (transport_proto_t proto)
416 {
417   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
418          proto == TRANSPORT_PROTO_QUIC;
419 }
420
421 static int
422 hts_listen (hts_main_t *htm)
423 {
424   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
425   vnet_listen_args_t _a, *a = &_a;
426   char *uri = "tcp://0.0.0.0/80";
427   u8 need_crypto;
428   int rv;
429
430   clib_memset (a, 0, sizeof (*a));
431   a->app_index = htm->app_index;
432
433   if (htm->uri)
434     uri = (char *) htm->uri;
435
436   if (parse_uri (uri, &sep))
437     return -1;
438
439   need_crypto = hts_transport_needs_crypto (sep.transport_proto);
440
441   sep.transport_proto = TRANSPORT_PROTO_HTTP;
442   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
443
444   if (need_crypto)
445     {
446       session_endpoint_alloc_ext_cfg (&a->sep_ext,
447                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
448       a->sep_ext.ext_cfg->crypto.ckpair_index = htm->ckpair_index;
449     }
450
451   rv = vnet_listen (a);
452
453   if (need_crypto)
454     clib_mem_free (a->sep_ext.ext_cfg);
455
456   return rv;
457 }
458
459 static int
460 hts_create (vlib_main_t *vm)
461 {
462   vlib_thread_main_t *vtm = vlib_get_thread_main ();
463   hts_main_t *htm = &hts_main;
464   u32 num_threads;
465
466   num_threads = 1 /* main thread */ + vtm->n_threads;
467   vec_validate (htm->sessions, num_threads - 1);
468
469   if (htm->no_zc)
470     vec_validate (htm->test_data, (64 << 10) - 1);
471
472   if (hts_attach (htm))
473     {
474       clib_warning ("failed to attach server");
475       return -1;
476     }
477   if (hts_listen (htm))
478     {
479       clib_warning ("failed to start listening");
480       return -1;
481     }
482
483   return 0;
484 }
485
486 static clib_error_t *
487 hts_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
488                        vlib_cli_command_t *cmd)
489 {
490   unformat_input_t _line_input, *line_input = &_line_input;
491   hts_main_t *htm = &hts_main;
492   clib_error_t *error = 0;
493   u64 mem_size;
494
495   /* Get a line of input. */
496   if (!unformat_user (input, unformat_line_input, line_input))
497     goto start_server;
498
499   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
500     {
501       if (unformat (line_input, "private-segment-size %U",
502                     unformat_memory_size, &mem_size))
503         htm->segment_size = mem_size;
504       else if (unformat (line_input, "fifo-size %U", unformat_memory_size,
505                          &mem_size))
506         htm->fifo_size = mem_size;
507       else if (unformat (line_input, "uri %s", &htm->uri))
508         ;
509       else if (unformat (line_input, "no-zc"))
510         htm->no_zc = 1;
511       else if (unformat (line_input, "debug"))
512         htm->debug_level = 1;
513       else
514         {
515           error = clib_error_return (0, "unknown input `%U'",
516                                      format_unformat_error, line_input);
517           break;
518         }
519     }
520
521   unformat_free (line_input);
522
523   if (error)
524     return error;
525
526 start_server:
527
528   if (htm->app_index != (u32) ~0)
529     return clib_error_return (0, "http tps is already running");
530
531   vnet_session_enable_disable (vm, 1 /* is_enable */);
532
533   if (hts_create (vm))
534     return clib_error_return (0, "http tps create failed");
535
536   return 0;
537 }
538
539 VLIB_CLI_COMMAND (http_tps_command, static) = {
540   .path = "http tps",
541   .short_help = "http tps [uri <uri>] [fifo-size <nbytes>] "
542                 "[segment-size <nMG>] [prealloc-fifos <n>] [debug] [no-zc]",
543   .function = hts_create_command_fn,
544 };
545
546 static clib_error_t *
547 hs_main_init (vlib_main_t *vm)
548 {
549   hts_main_t *htm = &hts_main;
550
551   htm->app_index = ~0;
552   htm->segment_size = 128 << 20;
553   htm->fifo_size = 64 << 10;
554
555   return 0;
556 }
557
558 VLIB_INIT_FUNCTION (hs_main_init);
559
560 /*
561  * fd.io coding-style-patch-verification: ON
562  *
563  * Local Variables:
564  * eval: (c-set-style "gnu")
565  * End:
566  */