Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright(c) 2023 Marvell.
3 : : */
4 : :
5 : : #include <stdio.h>
6 : : #include <stdlib.h>
7 : : #include <string.h>
8 : :
9 : : #include <cmdline_parse.h>
10 : : #include <cmdline_parse_num.h>
11 : : #include <cmdline_parse_string.h>
12 : : #include <cmdline_socket.h>
13 : : #include <rte_ethdev.h>
14 : : #include <rte_graph_worker.h>
15 : : #include <rte_log.h>
16 : :
17 : : #include "graph_priv.h"
18 : : #include "module_api.h"
19 : :
20 : : #define RTE_LOGTYPE_APP_GRAPH RTE_LOGTYPE_USER1
21 : :
22 : : static const char
23 : : cmd_graph_help[] = "graph <usecases> bsz <size> tmo <ns> coremask <bitmask> "
24 : : "model <rtc | mcd | default> pcap_enable <0 | 1> num_pcap_pkts <num>"
25 : : "pcap_file <output_capture_file>";
26 : :
27 : : static const char * const supported_usecases[] = {"l3fwd"};
28 : : struct graph_config graph_config;
29 : : bool graph_started;
30 : :
31 : : /* Check the link rc of all ports in up to 9s, and print them finally */
32 : : static void
33 : 0 : check_all_ports_link_status(uint32_t port_mask)
34 : : {
35 : : #define CHECK_INTERVAL 100 /* 100ms */
36 : : #define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
37 : : char link_rc_text[RTE_ETH_LINK_MAX_STR_LEN];
38 : : uint8_t count, all_ports_up, print_flag = 0;
39 : : struct rte_eth_link link;
40 : : uint16_t portid;
41 : : int rc;
42 : :
43 : : printf("\nChecking link status...");
44 : 0 : fflush(stdout);
45 : 0 : for (count = 0; count <= MAX_CHECK_TIME; count++) {
46 : 0 : if (force_quit)
47 : 0 : return;
48 : :
49 : : all_ports_up = 1;
50 : 0 : RTE_ETH_FOREACH_DEV(portid)
51 : : {
52 : 0 : if (force_quit)
53 : : return;
54 : :
55 : 0 : if ((port_mask & (1 << portid)) == 0)
56 : 0 : continue;
57 : :
58 : : memset(&link, 0, sizeof(link));
59 : : rc = rte_eth_link_get_nowait(portid, &link);
60 : 0 : if (rc < 0) {
61 : : all_ports_up = 0;
62 : 0 : if (print_flag == 1)
63 : 0 : printf("Port %u link get failed: %s\n",
64 : : portid, rte_strerror(-rc));
65 : 0 : continue;
66 : : }
67 : :
68 : : /* Print link rc if flag set */
69 : 0 : if (print_flag == 1) {
70 : 0 : rte_eth_link_to_str(link_rc_text, sizeof(link_rc_text),
71 : : &link);
72 : : printf("Port %d %s\n", portid, link_rc_text);
73 : 0 : continue;
74 : : }
75 : :
76 : : /* Clear all_ports_up flag if any link down */
77 : 0 : if (link.link_status == RTE_ETH_LINK_DOWN) {
78 : : all_ports_up = 0;
79 : : break;
80 : : }
81 : : }
82 : :
83 : : /* After finally printing all link rc, get out */
84 : 0 : if (print_flag == 1)
85 : : break;
86 : :
87 : 0 : if (all_ports_up == 0) {
88 : : printf(".");
89 : 0 : fflush(stdout);
90 : : rte_delay_ms(CHECK_INTERVAL);
91 : : }
92 : :
93 : : /* Set the print_flag if all ports up or timeout */
94 : 0 : if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
95 : : print_flag = 1;
96 : : printf("Done\n");
97 : : }
98 : : }
99 : : }
100 : :
101 : : static bool
102 : 0 : parser_usecases_read(char *usecases)
103 : : {
104 : : bool valid = false;
105 : : uint32_t i, j = 0;
106 : : char *token;
107 : :
108 : 0 : token = strtok(usecases, ",");
109 : 0 : while (token != NULL) {
110 : 0 : for (i = 0; i < RTE_DIM(supported_usecases); i++) {
111 : 0 : if (strcmp(supported_usecases[i], token) == 0) {
112 : 0 : graph_config.usecases[j].enabled = true;
113 : 0 : rte_strscpy(graph_config.usecases[j].name, token, 31);
114 : : valid = true;
115 : 0 : j++;
116 : 0 : break;
117 : : }
118 : : }
119 : 0 : token = strtok(NULL, ",");
120 : : }
121 : :
122 : 0 : return valid;
123 : : }
124 : :
125 : : static uint64_t
126 : : graph_worker_count_get(void)
127 : : {
128 : : uint64_t nb_worker = 0;
129 : : uint64_t coremask;
130 : :
131 : 0 : coremask = graph_config.params.coremask;
132 : 0 : while (coremask) {
133 : 0 : if (coremask & 0x1)
134 : 0 : nb_worker++;
135 : :
136 : 0 : coremask = (coremask >> 1);
137 : : }
138 : :
139 : : return nb_worker;
140 : : }
141 : :
142 : : static struct rte_node_ethdev_config *
143 : 0 : graph_rxtx_node_config_get(uint32_t *num_conf, uint32_t *num_graphs)
144 : : {
145 : : uint32_t n_tx_queue, nb_conf = 0, lcore_id;
146 : : uint16_t queueid, portid, nb_graphs = 0;
147 : : uint8_t nb_rx_queue, queue;
148 : : struct lcore_conf *qconf;
149 : :
150 : 0 : n_tx_queue = graph_worker_count_get();
151 : : if (n_tx_queue > RTE_MAX_ETHPORTS)
152 : : n_tx_queue = RTE_MAX_ETHPORTS;
153 : :
154 : 0 : RTE_ETH_FOREACH_DEV(portid) {
155 : : /* Skip ports that are not enabled */
156 : 0 : if ((enabled_port_mask & (1 << portid)) == 0) {
157 : : printf("\nSkipping disabled port %d\n", portid);
158 : 0 : continue;
159 : : }
160 : :
161 : 0 : nb_rx_queue = ethdev_rx_num_rx_queues_get(portid);
162 : :
163 : : /* Setup ethdev node config */
164 : 0 : ethdev_conf[nb_conf].port_id = portid;
165 : 0 : ethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;
166 : 0 : ethdev_conf[nb_conf].num_tx_queues = n_tx_queue;
167 : 0 : ethdev_conf[nb_conf].mp = ethdev_mempool_list_by_portid(portid);
168 : 0 : ethdev_conf[nb_conf].mp_count = 1; /* Check with pools */
169 : :
170 : 0 : nb_conf++;
171 : : }
172 : :
173 : 0 : for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
174 : 0 : if (rte_lcore_is_enabled(lcore_id) == 0)
175 : 0 : continue;
176 : :
177 : : qconf = &lcore_conf[lcore_id];
178 : : printf("\nInitializing rx queues on lcore %u ... ", lcore_id);
179 : 0 : fflush(stdout);
180 : :
181 : : /* Init RX queues */
182 : 0 : for (queue = 0; queue < qconf->n_rx_queue; ++queue) {
183 : 0 : portid = qconf->rx_queue_list[queue].port_id;
184 : 0 : queueid = qconf->rx_queue_list[queue].queue_id;
185 : :
186 : : /* Add this queue node to its graph */
187 : 0 : snprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,
188 : : "ethdev_rx-%u-%u", portid, queueid);
189 : : }
190 : 0 : if (qconf->n_rx_queue)
191 : 0 : nb_graphs++;
192 : : }
193 : :
194 : : printf("\n");
195 : :
196 : 0 : ethdev_start();
197 : 0 : check_all_ports_link_status(enabled_port_mask);
198 : :
199 : 0 : *num_conf = nb_conf;
200 : 0 : *num_graphs = nb_graphs;
201 : 0 : return ethdev_conf;
202 : : }
203 : :
204 : : static void
205 : 0 : graph_stats_print_to_file(void)
206 : : {
207 : : struct rte_graph_cluster_stats_param s_param;
208 : : struct rte_graph_cluster_stats *stats;
209 : 0 : const char *pattern = "worker_*";
210 : : FILE *fp = NULL;
211 : : size_t sz, len;
212 : :
213 : : /* Prepare stats object */
214 : 0 : fp = fopen("/tmp/graph_stats.txt", "w+");
215 : 0 : if (fp == NULL)
216 : 0 : rte_exit(EXIT_FAILURE, "Error in opening stats file\n");
217 : :
218 : : memset(&s_param, 0, sizeof(s_param));
219 : 0 : s_param.f = fp;
220 : 0 : s_param.socket_id = SOCKET_ID_ANY;
221 : 0 : s_param.graph_patterns = &pattern;
222 : 0 : s_param.nb_graph_patterns = 1;
223 : :
224 : 0 : stats = rte_graph_cluster_stats_create(&s_param);
225 : 0 : if (stats == NULL)
226 : 0 : rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
227 : :
228 : : /* Clear screen and move to top left */
229 : 0 : rte_graph_cluster_stats_get(stats, 0);
230 : : rte_delay_ms(1E3);
231 : :
232 : 0 : fseek(fp, 0L, SEEK_END);
233 : 0 : sz = ftell(fp);
234 : 0 : fseek(fp, 0L, SEEK_SET);
235 : :
236 : 0 : len = strlen(conn->msg_out);
237 : 0 : conn->msg_out += len;
238 : :
239 : : sz = fread(conn->msg_out, sizeof(char), sz, fp);
240 : 0 : len = strlen(conn->msg_out);
241 : 0 : conn->msg_out_len_max -= len;
242 : 0 : rte_graph_cluster_stats_destroy(stats);
243 : :
244 : 0 : fclose(fp);
245 : 0 : }
246 : :
247 : : static void
248 : 0 : cli_graph_stats(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
249 : : __rte_unused void *data)
250 : : {
251 : 0 : graph_stats_print_to_file();
252 : 0 : }
253 : :
254 : : bool
255 : 0 : graph_status_get(void)
256 : : {
257 : 0 : return graph_started;
258 : : }
259 : :
260 : : static void
261 : 0 : cli_graph_start(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
262 : : __rte_unused void *data)
263 : : {
264 : : struct rte_node_ethdev_config *conf;
265 : 0 : uint32_t nb_graphs = 0, nb_conf, i;
266 : : int rc = -EINVAL;
267 : :
268 : 0 : conf = graph_rxtx_node_config_get(&nb_conf, &nb_graphs);
269 : 0 : for (i = 0; i < MAX_GRAPH_USECASES; i++) {
270 : 0 : if (!strcmp(graph_config.usecases[i].name, "l3fwd")) {
271 : 0 : if (graph_config.usecases[i].enabled) {
272 : 0 : rc = usecase_l3fwd_configure(conf, nb_conf, nb_graphs);
273 : 0 : break;
274 : : }
275 : : }
276 : : }
277 : :
278 : 0 : if (!rc)
279 : 0 : graph_started = true;
280 : 0 : }
281 : :
282 : : static int
283 : 0 : graph_config_add(char *usecases, struct graph_config *config)
284 : : {
285 : : uint64_t lcore_id, core_num;
286 : : uint64_t eal_coremask = 0;
287 : :
288 : 0 : if (!parser_usecases_read(usecases))
289 : : return -EINVAL;
290 : :
291 : 0 : for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
292 : 0 : if (rte_lcore_is_enabled(lcore_id))
293 : 0 : eal_coremask |= RTE_BIT64(lcore_id);
294 : : }
295 : :
296 : 0 : for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
297 : 0 : core_num = 1 << lcore_id;
298 : 0 : if (config->params.coremask & core_num) {
299 : 0 : if (eal_coremask & core_num)
300 : 0 : continue;
301 : : else
302 : : return -EINVAL;
303 : : }
304 : : }
305 : :
306 : 0 : graph_config.params.bsz = config->params.bsz;
307 : 0 : graph_config.params.tmo = config->params.tmo;
308 : 0 : graph_config.params.coremask = config->params.coremask;
309 : 0 : graph_config.model = config->model;
310 : 0 : graph_config.pcap_ena = config->pcap_ena;
311 : 0 : graph_config.num_pcap_pkts = config->num_pcap_pkts;
312 : 0 : graph_config.pcap_file = strdup(config->pcap_file);
313 : :
314 : 0 : return 0;
315 : : }
316 : :
317 : : void
318 : 0 : graph_pcap_config_get(uint8_t *pcap_ena, uint64_t *num_pkts, char **file)
319 : : {
320 : :
321 : 0 : *pcap_ena = graph_config.pcap_ena;
322 : 0 : *num_pkts = graph_config.num_pcap_pkts;
323 : 0 : *file = graph_config.pcap_file;
324 : 0 : }
325 : :
326 : : int
327 : 0 : graph_walk_start(void *conf)
328 : : {
329 : : struct lcore_conf *qconf;
330 : : struct rte_graph *graph;
331 : : uint32_t lcore_id;
332 : :
333 : : RTE_SET_USED(conf);
334 : :
335 : : lcore_id = rte_lcore_id();
336 : : qconf = &lcore_conf[lcore_id];
337 : 0 : graph = qconf->graph;
338 : :
339 : 0 : if (!graph) {
340 : 0 : RTE_LOG(INFO, APP_GRAPH, "Lcore %u has nothing to do\n", lcore_id);
341 : 0 : return 0;
342 : : }
343 : :
344 : 0 : RTE_LOG(INFO, APP_GRAPH, "Entering main loop on lcore %u, graph %s(%p)\n", lcore_id,
345 : : qconf->name, graph);
346 : :
347 : 0 : while (likely(!force_quit))
348 : 0 : rte_graph_walk(graph);
349 : :
350 : : return 0;
351 : : }
352 : :
353 : : void
354 : 0 : graph_stats_print(void)
355 : : {
356 : 0 : const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
357 : 0 : const char clr[] = {27, '[', '2', 'J', '\0'};
358 : : struct rte_graph_cluster_stats_param s_param;
359 : : struct rte_graph_cluster_stats *stats;
360 : 0 : const char *pattern = "worker_*";
361 : :
362 : : /* Prepare stats object */
363 : : memset(&s_param, 0, sizeof(s_param));
364 : 0 : s_param.f = stdout;
365 : 0 : s_param.socket_id = SOCKET_ID_ANY;
366 : 0 : s_param.graph_patterns = &pattern;
367 : 0 : s_param.nb_graph_patterns = 1;
368 : :
369 : 0 : stats = rte_graph_cluster_stats_create(&s_param);
370 : 0 : if (stats == NULL)
371 : 0 : rte_exit(EXIT_FAILURE, "Unable to create stats object\n");
372 : :
373 : 0 : while (!force_quit) {
374 : : /* Clear screen and move to top left */
375 : : printf("%s%s", clr, topLeft);
376 : 0 : rte_graph_cluster_stats_get(stats, 0);
377 : : rte_delay_ms(1E3);
378 : 0 : if (app_graph_exit())
379 : 0 : force_quit = true;
380 : : }
381 : :
382 : 0 : rte_graph_cluster_stats_destroy(stats);
383 : 0 : }
384 : :
385 : : uint64_t
386 : 0 : graph_coremask_get(void)
387 : : {
388 : 0 : return graph_config.params.coremask;
389 : : }
390 : :
391 : : static void
392 : 0 : cli_graph(void *parsed_result, __rte_unused struct cmdline *cl, __rte_unused void *data)
393 : : {
394 : : struct graph_config_cmd_tokens *res = parsed_result;
395 : : struct graph_config config;
396 : : char *model_name;
397 : : uint8_t model;
398 : : int rc;
399 : :
400 : 0 : model_name = res->model_name;
401 : 0 : if (strcmp(model_name, "default") == 0) {
402 : : model = GRAPH_MODEL_RTC;
403 : 0 : } else if (strcmp(model_name, "rtc") == 0) {
404 : : model = GRAPH_MODEL_RTC;
405 : 0 : } else if (strcmp(model_name, "mcd") == 0) {
406 : : model = GRAPH_MODEL_MCD;
407 : : } else {
408 : : printf(MSG_ARG_NOT_FOUND, "model arguments");
409 : 0 : return;
410 : : }
411 : :
412 : 0 : config.params.bsz = res->size;
413 : 0 : config.params.tmo = res->ns;
414 : 0 : config.params.coremask = res->mask;
415 : 0 : config.model = model;
416 : 0 : config.pcap_ena = res->pcap_ena;
417 : 0 : config.num_pcap_pkts = res->num_pcap_pkts;
418 : 0 : config.pcap_file = res->pcap_file;
419 : 0 : rc = graph_config_add(res->usecase, &config);
420 : 0 : if (rc < 0) {
421 : 0 : cli_exit();
422 : 0 : printf(MSG_CMD_FAIL, res->graph);
423 : 0 : rte_exit(EXIT_FAILURE, "coremask is Invalid\n");
424 : : }
425 : : }
426 : :
427 : : static void
428 : 0 : cli_graph_help(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,
429 : : __rte_unused void *data)
430 : : {
431 : : size_t len;
432 : :
433 : 0 : len = strlen(conn->msg_out);
434 : 0 : conn->msg_out += len;
435 : 0 : snprintf(conn->msg_out, conn->msg_out_len_max, "\n%s\n%s\n%s\n%s\n",
436 : : "----------------------------- graph command help -----------------------------",
437 : : cmd_graph_help, "graph start", "graph stats show");
438 : :
439 : 0 : len = strlen(conn->msg_out);
440 : 0 : conn->msg_out_len_max -= len;
441 : 0 : }
442 : :
443 : : cmdline_parse_token_string_t graph_display_graph =
444 : : TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, graph, "graph");
445 : : cmdline_parse_token_string_t graph_display_stats =
446 : : TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, stats, "stats");
447 : : cmdline_parse_token_string_t graph_display_show =
448 : : TOKEN_STRING_INITIALIZER(struct graph_stats_cmd_tokens, show, "show");
449 : :
450 : : cmdline_parse_inst_t graph_stats_cmd_ctx = {
451 : : .f = cli_graph_stats,
452 : : .data = NULL,
453 : : .help_str = "graph stats show",
454 : : .tokens = {
455 : : (void *)&graph_display_graph,
456 : : (void *)&graph_display_stats,
457 : : (void *)&graph_display_show,
458 : : NULL,
459 : : },
460 : : };
461 : :
462 : : cmdline_parse_token_string_t graph_config_start_graph =
463 : : TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, graph, "graph");
464 : : cmdline_parse_token_string_t graph_config_start =
465 : : TOKEN_STRING_INITIALIZER(struct graph_start_cmd_tokens, start, "start");
466 : :
467 : : cmdline_parse_inst_t graph_start_cmd_ctx = {
468 : : .f = cli_graph_start,
469 : : .data = NULL,
470 : : .help_str = "graph start",
471 : : .tokens = {
472 : : (void *)&graph_config_start_graph,
473 : : (void *)&graph_config_start,
474 : : NULL,
475 : : },
476 : : };
477 : :
478 : : cmdline_parse_token_string_t graph_config_add_graph =
479 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, graph, "graph");
480 : : cmdline_parse_token_string_t graph_config_add_usecase =
481 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, usecase, NULL);
482 : : cmdline_parse_token_string_t graph_config_add_coremask =
483 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, coremask, "coremask");
484 : : cmdline_parse_token_num_t graph_config_add_mask =
485 : : TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, mask, RTE_UINT64);
486 : : cmdline_parse_token_string_t graph_config_add_bsz =
487 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, bsz, "bsz");
488 : : cmdline_parse_token_num_t graph_config_add_size =
489 : : TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, size, RTE_UINT16);
490 : : cmdline_parse_token_string_t graph_config_add_tmo =
491 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, tmo, "tmo");
492 : : cmdline_parse_token_num_t graph_config_add_ns =
493 : : TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, ns, RTE_UINT64);
494 : : cmdline_parse_token_string_t graph_config_add_model =
495 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model, "model");
496 : : cmdline_parse_token_string_t graph_config_add_model_name =
497 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, model_name, "rtc#mcd#default");
498 : : cmdline_parse_token_string_t graph_config_add_capt_ena =
499 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_ena, "pcap_enable");
500 : : cmdline_parse_token_num_t graph_config_add_pcap_ena =
501 : : TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, pcap_ena, RTE_UINT8);
502 : : cmdline_parse_token_string_t graph_config_add_capt_pkts_count =
503 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_pkts_count, "num_pcap_pkts");
504 : : cmdline_parse_token_num_t graph_config_add_num_pcap_pkts =
505 : : TOKEN_NUM_INITIALIZER(struct graph_config_cmd_tokens, num_pcap_pkts, RTE_UINT64);
506 : : cmdline_parse_token_string_t graph_config_add_capt_file =
507 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, capt_file, "pcap_file");
508 : : cmdline_parse_token_string_t graph_config_add_pcap_file =
509 : : TOKEN_STRING_INITIALIZER(struct graph_config_cmd_tokens, pcap_file, NULL);
510 : :
511 : : cmdline_parse_inst_t graph_config_cmd_ctx = {
512 : : .f = cli_graph,
513 : : .data = NULL,
514 : : .help_str = cmd_graph_help,
515 : : .tokens = {
516 : : (void *)&graph_config_add_graph,
517 : : (void *)&graph_config_add_usecase,
518 : : (void *)&graph_config_add_coremask,
519 : : (void *)&graph_config_add_mask,
520 : : (void *)&graph_config_add_bsz,
521 : : (void *)&graph_config_add_size,
522 : : (void *)&graph_config_add_tmo,
523 : : (void *)&graph_config_add_ns,
524 : : (void *)&graph_config_add_model,
525 : : (void *)&graph_config_add_model_name,
526 : : (void *)&graph_config_add_capt_ena,
527 : : (void *)&graph_config_add_pcap_ena,
528 : : (void *)&graph_config_add_capt_pkts_count,
529 : : (void *)&graph_config_add_num_pcap_pkts,
530 : : (void *)&graph_config_add_capt_file,
531 : : (void *)&graph_config_add_pcap_file,
532 : : NULL,
533 : : },
534 : : };
535 : :
536 : : cmdline_parse_token_string_t graph_help_cmd =
537 : : TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, help, "help");
538 : : cmdline_parse_token_string_t graph_help_graph =
539 : : TOKEN_STRING_INITIALIZER(struct graph_help_cmd_tokens, graph, "graph");
540 : :
541 : : cmdline_parse_inst_t graph_help_cmd_ctx = {
542 : : .f = cli_graph_help,
543 : : .data = NULL,
544 : : .help_str = "",
545 : : .tokens = {
546 : : (void *)&graph_help_cmd,
547 : : (void *)&graph_help_graph,
548 : : NULL,
549 : : },
550 : : };
|