xref: /dpdk/lib/graph/graph.c (revision 5b8d861cfe89ebcbb08760c7817be0b79b9ff6f9)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(C) 2020 Marvell International Ltd.
3  */
4 
5 #include <fnmatch.h>
6 #include <stdbool.h>
7 #include <stdlib.h>
8 
9 #include <rte_common.h>
10 #include <rte_debug.h>
11 #include <rte_errno.h>
12 #include <rte_malloc.h>
13 #include <rte_memzone.h>
14 #include <rte_spinlock.h>
15 #include <rte_string_fns.h>
16 
17 #include "graph_private.h"
18 #include "graph_pcap_private.h"
19 
20 static struct graph_head graph_list = STAILQ_HEAD_INITIALIZER(graph_list);
21 static rte_spinlock_t graph_lock = RTE_SPINLOCK_INITIALIZER;
22 
23 /* Private functions */
24 static struct graph *
25 graph_from_id(rte_graph_t id)
26 {
27 	struct graph *graph;
28 	STAILQ_FOREACH(graph, &graph_list, next) {
29 		if (graph->id == id)
30 			return graph;
31 	}
32 	rte_errno = EINVAL;
33 	return NULL;
34 }
35 
36 static rte_graph_t
37 graph_next_free_id(void)
38 {
39 	struct graph *graph;
40 	rte_graph_t id = 0;
41 
42 	STAILQ_FOREACH(graph, &graph_list, next) {
43 		if (id < graph->id)
44 			break;
45 		id = graph->id + 1;
46 	}
47 
48 	return id;
49 }
50 
51 static void
52 graph_insert_ordered(struct graph *graph)
53 {
54 	struct graph *after, *g;
55 
56 	after = NULL;
57 	STAILQ_FOREACH(g, &graph_list, next) {
58 		if (g->id < graph->id)
59 			after = g;
60 		else if (g->id > graph->id)
61 			break;
62 	}
63 	if (after == NULL) {
64 		STAILQ_INSERT_HEAD(&graph_list, graph, next);
65 	} else {
66 		STAILQ_INSERT_AFTER(&graph_list, after, graph, next);
67 	}
68 }
69 
70 struct graph_head *
71 graph_list_head_get(void)
72 {
73 	return &graph_list;
74 }
75 
76 rte_spinlock_t *
77 graph_spinlock_get(void)
78 {
79 	return &graph_lock;
80 }
81 
82 void
83 graph_spinlock_lock(void)
84 {
85 	rte_spinlock_lock(graph_spinlock_get());
86 }
87 
88 void
89 graph_spinlock_unlock(void)
90 {
91 	rte_spinlock_unlock(graph_spinlock_get());
92 }
93 
94 static int
95 graph_node_add(struct graph *graph, struct node *node)
96 {
97 	struct graph_node *graph_node;
98 	size_t sz;
99 
100 	/* Skip the duplicate nodes */
101 	STAILQ_FOREACH(graph_node, &graph->node_list, next)
102 		if (strncmp(node->name, graph_node->node->name,
103 			    RTE_NODE_NAMESIZE) == 0)
104 			return 0;
105 
106 	/* Allocate new graph node object */
107 	sz = sizeof(*graph_node) + node->nb_edges * sizeof(struct node *);
108 	graph_node = calloc(1, sz);
109 
110 	if (graph_node == NULL)
111 		SET_ERR_JMP(ENOMEM, free, "Failed to calloc %s object",
112 			    node->name);
113 
114 	/* Initialize the graph node */
115 	graph_node->node = node;
116 
117 	/* Add to graph node list */
118 	STAILQ_INSERT_TAIL(&graph->node_list, graph_node, next);
119 	return 0;
120 
121 free:
122 	free(graph_node);
123 	return -rte_errno;
124 }
125 
126 static struct graph_node *
127 node_to_graph_node(struct graph *graph, struct node *node)
128 {
129 	struct graph_node *graph_node;
130 
131 	STAILQ_FOREACH(graph_node, &graph->node_list, next)
132 		if (graph_node->node == node)
133 			return graph_node;
134 
135 	SET_ERR_JMP(ENODEV, fail, "Found isolated node %s", node->name);
136 fail:
137 	return NULL;
138 }
139 
140 static int
141 graph_node_edges_add(struct graph *graph)
142 {
143 	struct graph_node *graph_node;
144 	struct node *adjacency;
145 	const char *next;
146 	rte_edge_t i;
147 
148 	STAILQ_FOREACH(graph_node, &graph->node_list, next) {
149 		for (i = 0; i < graph_node->node->nb_edges; i++) {
150 			next = graph_node->node->next_nodes[i];
151 			adjacency = node_from_name(next);
152 			if (adjacency == NULL)
153 				SET_ERR_JMP(EINVAL, fail,
154 					    "Node %s not registered", next);
155 			if (graph_node_add(graph, adjacency))
156 				goto fail;
157 		}
158 	}
159 	return 0;
160 fail:
161 	return -rte_errno;
162 }
163 
164 static int
165 graph_adjacency_list_update(struct graph *graph)
166 {
167 	struct graph_node *graph_node, *tmp;
168 	struct node *adjacency;
169 	const char *next;
170 	rte_edge_t i;
171 
172 	STAILQ_FOREACH(graph_node, &graph->node_list, next) {
173 		for (i = 0; i < graph_node->node->nb_edges; i++) {
174 			next = graph_node->node->next_nodes[i];
175 			adjacency = node_from_name(next);
176 			if (adjacency == NULL)
177 				SET_ERR_JMP(EINVAL, fail,
178 					    "Node %s not registered", next);
179 			tmp = node_to_graph_node(graph, adjacency);
180 			if (tmp == NULL)
181 				goto fail;
182 			graph_node->adjacency_list[i] = tmp;
183 		}
184 	}
185 
186 	return 0;
187 fail:
188 	return -rte_errno;
189 }
190 
191 static int
192 expand_pattern_to_node(struct graph *graph, const char *pattern)
193 {
194 	struct node_head *node_head = node_list_head_get();
195 	bool found = false;
196 	struct node *node;
197 
198 	/* Check for pattern match */
199 	STAILQ_FOREACH(node, node_head, next) {
200 		if (fnmatch(pattern, node->name, 0) == 0) {
201 			if (graph_node_add(graph, node))
202 				goto fail;
203 			found = true;
204 		}
205 	}
206 	if (found == false)
207 		SET_ERR_JMP(EFAULT, fail, "Pattern %s node not found", pattern);
208 
209 	return 0;
210 fail:
211 	return -rte_errno;
212 }
213 
214 static void
215 graph_cleanup(struct graph *graph)
216 {
217 	struct graph_node *graph_node;
218 
219 	while (!STAILQ_EMPTY(&graph->node_list)) {
220 		graph_node = STAILQ_FIRST(&graph->node_list);
221 		STAILQ_REMOVE_HEAD(&graph->node_list, next);
222 		free(graph_node);
223 	}
224 }
225 
226 static int
227 graph_node_init(struct graph *graph)
228 {
229 	struct graph_node *graph_node;
230 	const char *name;
231 	int rc;
232 
233 	STAILQ_FOREACH(graph_node, &graph->node_list, next) {
234 		if (graph_node->node->init) {
235 			name = graph_node->node->name;
236 			rc = graph_node->node->init(
237 				graph->graph,
238 				graph_node_name_to_ptr(graph->graph, name));
239 			if (rc)
240 				SET_ERR_JMP(rc, err, "Node %s init() failed",
241 					    name);
242 		}
243 	}
244 
245 	return 0;
246 err:
247 	return -rte_errno;
248 }
249 
250 static void
251 graph_node_fini(struct graph *graph)
252 {
253 	struct graph_node *graph_node;
254 
255 	STAILQ_FOREACH(graph_node, &graph->node_list, next)
256 		if (graph_node->node->fini)
257 			graph_node->node->fini(
258 				graph->graph,
259 				graph_node_name_to_ptr(graph->graph,
260 						       graph_node->node->name));
261 }
262 
263 static struct rte_graph *
264 graph_mem_fixup_node_ctx(struct rte_graph *graph)
265 {
266 	struct rte_node *node;
267 	struct node *node_db;
268 	rte_graph_off_t off;
269 	rte_node_t count;
270 	const char *name;
271 
272 	rte_graph_foreach_node(count, off, graph, node) {
273 		if (node->parent_id == RTE_NODE_ID_INVALID) /* Static node */
274 			name = node->name;
275 		else /* Cloned node */
276 			name = node->parent;
277 
278 		node_db = node_from_name(name);
279 		if (node_db == NULL)
280 			SET_ERR_JMP(ENOLINK, fail, "Node %s not found", name);
281 
282 		if (graph->pcap_enable) {
283 			node->process = graph_pcap_dispatch;
284 			node->original_process = node_db->process;
285 		} else
286 			node->process = node_db->process;
287 	}
288 
289 	return graph;
290 fail:
291 	return NULL;
292 }
293 
294 static struct rte_graph *
295 graph_mem_fixup_secondary(struct rte_graph *graph)
296 {
297 	if (graph == NULL || rte_eal_process_type() == RTE_PROC_PRIMARY)
298 		return graph;
299 
300 	if (graph_pcap_file_open(graph->pcap_filename) || graph_pcap_mp_init())
301 		graph_pcap_exit(graph);
302 
303 	return graph_mem_fixup_node_ctx(graph);
304 }
305 
306 static bool
307 graph_src_node_avail(struct graph *graph)
308 {
309 	struct graph_node *graph_node;
310 
311 	STAILQ_FOREACH(graph_node, &graph->node_list, next)
312 		if ((graph_node->node->flags & RTE_NODE_SOURCE_F) &&
313 		    (graph_node->node->lcore_id == RTE_MAX_LCORE ||
314 		     graph->lcore_id == graph_node->node->lcore_id))
315 			return true;
316 
317 	return false;
318 }
319 
320 int
321 rte_graph_model_mcore_dispatch_core_bind(rte_graph_t id, int lcore)
322 {
323 	struct graph *graph;
324 
325 	if (graph_from_id(id) == NULL)
326 		goto fail;
327 	if (!rte_lcore_is_enabled(lcore))
328 		SET_ERR_JMP(ENOLINK, fail, "lcore %d not enabled", lcore);
329 
330 	STAILQ_FOREACH(graph, &graph_list, next)
331 		if (graph->id == id)
332 			break;
333 
334 	if (graph->graph->model != RTE_GRAPH_MODEL_MCORE_DISPATCH)
335 		goto fail;
336 
337 	graph->lcore_id = lcore;
338 	graph->graph->dispatch.lcore_id = graph->lcore_id;
339 	graph->socket = rte_lcore_to_socket_id(lcore);
340 
341 	/* check the availability of source node */
342 	if (!graph_src_node_avail(graph))
343 		graph->graph->head = 0;
344 
345 	return 0;
346 
347 fail:
348 	return -rte_errno;
349 }
350 
351 void
352 rte_graph_model_mcore_dispatch_core_unbind(rte_graph_t id)
353 {
354 	struct graph *graph;
355 
356 	if (graph_from_id(id) == NULL)
357 		goto fail;
358 	STAILQ_FOREACH(graph, &graph_list, next)
359 		if (graph->id == id)
360 			break;
361 
362 	graph->lcore_id = RTE_MAX_LCORE;
363 	graph->graph->dispatch.lcore_id = RTE_MAX_LCORE;
364 
365 fail:
366 	return;
367 }
368 
369 struct rte_graph *
370 rte_graph_lookup(const char *name)
371 {
372 	const struct rte_memzone *mz;
373 	struct rte_graph *rc = NULL;
374 
375 	mz = rte_memzone_lookup(name);
376 	if (mz)
377 		rc = mz->addr;
378 
379 	return graph_mem_fixup_secondary(rc);
380 }
381 
382 rte_graph_t
383 rte_graph_create(const char *name, struct rte_graph_param *prm)
384 {
385 	rte_node_t src_node_count;
386 	struct graph *graph;
387 	const char *pattern;
388 	uint16_t i;
389 
390 	graph_spinlock_lock();
391 
392 	/* Check arguments sanity */
393 	if (prm == NULL)
394 		SET_ERR_JMP(EINVAL, fail, "Param should not be NULL");
395 
396 	if (name == NULL)
397 		SET_ERR_JMP(EINVAL, fail, "Graph name should not be NULL");
398 
399 	/* Check for existence of duplicate graph */
400 	STAILQ_FOREACH(graph, &graph_list, next)
401 		if (strncmp(name, graph->name, RTE_GRAPH_NAMESIZE) == 0)
402 			SET_ERR_JMP(EEXIST, fail, "Found duplicate graph %s",
403 				    name);
404 
405 	/* Create graph object */
406 	graph = calloc(1, sizeof(*graph));
407 	if (graph == NULL)
408 		SET_ERR_JMP(ENOMEM, fail, "Failed to calloc graph object");
409 
410 	/* Initialize the graph object */
411 	STAILQ_INIT(&graph->node_list);
412 	if (rte_strscpy(graph->name, name, RTE_GRAPH_NAMESIZE) < 0)
413 		SET_ERR_JMP(E2BIG, free, "Too big name=%s", name);
414 
415 	/* Expand node pattern and add the nodes to the graph */
416 	for (i = 0; i < prm->nb_node_patterns; i++) {
417 		pattern = prm->node_patterns[i];
418 		if (expand_pattern_to_node(graph, pattern))
419 			goto graph_cleanup;
420 	}
421 
422 	/* Go over all the nodes edges and add them to the graph */
423 	if (graph_node_edges_add(graph))
424 		goto graph_cleanup;
425 
426 	/* Update adjacency list of all nodes in the graph */
427 	if (graph_adjacency_list_update(graph))
428 		goto graph_cleanup;
429 
430 	/* Make sure at least a source node present in the graph */
431 	src_node_count = graph_src_nodes_count(graph);
432 	if (src_node_count == 0)
433 		goto graph_cleanup;
434 
435 	/* Make sure no node is pointing to source node */
436 	if (graph_node_has_edge_to_src_node(graph))
437 		goto graph_cleanup;
438 
439 	/* Don't allow node has loop to self */
440 	if (graph_node_has_loop_edge(graph))
441 		goto graph_cleanup;
442 
443 	/* Do BFS from src nodes on the graph to find isolated nodes */
444 	if (graph_has_isolated_node(graph))
445 		goto graph_cleanup;
446 
447 	/* Initialize pcap config. */
448 	graph_pcap_enable(prm->pcap_enable);
449 
450 	/* Initialize graph object */
451 	graph->socket = prm->socket_id;
452 	graph->src_node_count = src_node_count;
453 	graph->node_count = graph_nodes_count(graph);
454 	graph->id = graph_next_free_id();
455 	graph->parent_id = RTE_GRAPH_ID_INVALID;
456 	graph->lcore_id = RTE_MAX_LCORE;
457 	graph->num_pkt_to_capture = prm->num_pkt_to_capture;
458 	if (prm->pcap_filename)
459 		rte_strscpy(graph->pcap_filename, prm->pcap_filename, RTE_GRAPH_PCAP_FILE_SZ);
460 
461 	/* Allocate the Graph fast path memory and populate the data */
462 	if (graph_fp_mem_create(graph))
463 		goto graph_cleanup;
464 
465 	/* Call init() of the all the nodes in the graph */
466 	if (graph_node_init(graph))
467 		goto graph_mem_destroy;
468 
469 	/* All good, Lets add the graph to the list */
470 	graph_insert_ordered(graph);
471 
472 	graph_spinlock_unlock();
473 	return graph->id;
474 
475 graph_mem_destroy:
476 	graph_fp_mem_destroy(graph);
477 graph_cleanup:
478 	graph_cleanup(graph);
479 free:
480 	free(graph);
481 fail:
482 	graph_spinlock_unlock();
483 	return RTE_GRAPH_ID_INVALID;
484 }
485 
486 int
487 rte_graph_destroy(rte_graph_t id)
488 {
489 	struct graph *graph, *tmp;
490 	int rc = -ENOENT;
491 
492 	graph_spinlock_lock();
493 
494 	graph = STAILQ_FIRST(&graph_list);
495 	while (graph != NULL) {
496 		tmp = STAILQ_NEXT(graph, next);
497 		if (graph->id == id) {
498 			/* Destroy the schedule work queue if has */
499 			if (rte_graph_worker_model_get(graph->graph) ==
500 			    RTE_GRAPH_MODEL_MCORE_DISPATCH)
501 				graph_sched_wq_destroy(graph);
502 
503 			/* Call fini() of the all the nodes in the graph */
504 			graph_node_fini(graph);
505 			/* Destroy graph fast path memory */
506 			rc = graph_fp_mem_destroy(graph);
507 			if (rc)
508 				SET_ERR_JMP(rc, done, "Graph %s destroy failed",
509 					    graph->name);
510 
511 			graph_cleanup(graph);
512 			STAILQ_REMOVE(&graph_list, graph, graph, next);
513 			free(graph);
514 			goto done;
515 		}
516 		graph = tmp;
517 	}
518 done:
519 	graph_spinlock_unlock();
520 	return rc;
521 }
522 
523 static rte_graph_t
524 graph_clone(struct graph *parent_graph, const char *name, struct rte_graph_param *prm)
525 {
526 	struct graph_node *graph_node;
527 	struct graph *graph;
528 
529 	graph_spinlock_lock();
530 
531 	/* Don't allow to clone a node from a cloned graph */
532 	if (parent_graph->parent_id != RTE_GRAPH_ID_INVALID)
533 		SET_ERR_JMP(EEXIST, fail, "A cloned graph is not allowed to be cloned");
534 
535 	/* Create graph object */
536 	graph = calloc(1, sizeof(*graph));
537 	if (graph == NULL)
538 		SET_ERR_JMP(ENOMEM, fail, "Failed to calloc cloned graph object");
539 
540 	/* Naming ceremony of the new graph. name is node->name + "-" + name */
541 	if (clone_name(graph->name, parent_graph->name, name))
542 		goto free;
543 
544 	/* Check for existence of duplicate graph */
545 	if (rte_graph_from_name(graph->name) != RTE_GRAPH_ID_INVALID)
546 		SET_ERR_JMP(EEXIST, free, "Found duplicate graph %s",
547 			    graph->name);
548 
549 	/* Clone nodes from parent graph firstly */
550 	STAILQ_INIT(&graph->node_list);
551 	STAILQ_FOREACH(graph_node, &parent_graph->node_list, next) {
552 		if (graph_node_add(graph, graph_node->node))
553 			goto graph_cleanup;
554 	}
555 
556 	/* Just update adjacency list of all nodes in the graph */
557 	if (graph_adjacency_list_update(graph))
558 		goto graph_cleanup;
559 
560 	/* Initialize the graph object */
561 	graph->src_node_count = parent_graph->src_node_count;
562 	graph->node_count = parent_graph->node_count;
563 	graph->parent_id = parent_graph->id;
564 	graph->lcore_id = parent_graph->lcore_id;
565 	graph->socket = parent_graph->socket;
566 	graph->id = graph_next_free_id();
567 
568 	/* Allocate the Graph fast path memory and populate the data */
569 	if (graph_fp_mem_create(graph))
570 		goto graph_cleanup;
571 
572 	/* Clone the graph model */
573 	graph->graph->model = parent_graph->graph->model;
574 
575 	/* Create the graph schedule work queue */
576 	if (rte_graph_worker_model_get(graph->graph) == RTE_GRAPH_MODEL_MCORE_DISPATCH &&
577 	    graph_sched_wq_create(graph, parent_graph, prm))
578 		goto graph_mem_destroy;
579 
580 	/* Call init() of the all the nodes in the graph */
581 	if (graph_node_init(graph))
582 		goto graph_mem_destroy;
583 
584 	/* All good, Lets add the graph to the list */
585 	graph_insert_ordered(graph);
586 
587 	graph_spinlock_unlock();
588 	return graph->id;
589 
590 graph_mem_destroy:
591 	graph_fp_mem_destroy(graph);
592 graph_cleanup:
593 	graph_cleanup(graph);
594 free:
595 	free(graph);
596 fail:
597 	graph_spinlock_unlock();
598 	return RTE_GRAPH_ID_INVALID;
599 }
600 
601 rte_graph_t
602 rte_graph_clone(rte_graph_t id, const char *name, struct rte_graph_param *prm)
603 {
604 	struct graph *graph;
605 
606 	if (graph_from_id(id) == NULL)
607 		goto fail;
608 	STAILQ_FOREACH(graph, &graph_list, next)
609 		if (graph->id == id)
610 			return graph_clone(graph, name, prm);
611 
612 fail:
613 	return RTE_GRAPH_ID_INVALID;
614 }
615 
616 rte_graph_t
617 rte_graph_from_name(const char *name)
618 {
619 	struct graph *graph;
620 
621 	STAILQ_FOREACH(graph, &graph_list, next)
622 		if (strncmp(graph->name, name, RTE_GRAPH_NAMESIZE) == 0)
623 			return graph->id;
624 
625 	return RTE_GRAPH_ID_INVALID;
626 }
627 
628 char *
629 rte_graph_id_to_name(rte_graph_t id)
630 {
631 	struct graph *graph;
632 
633 	if (graph_from_id(id) == NULL)
634 		goto fail;
635 	STAILQ_FOREACH(graph, &graph_list, next)
636 		if (graph->id == id)
637 			return graph->name;
638 
639 fail:
640 	return NULL;
641 }
642 
643 struct rte_node *
644 rte_graph_node_get(rte_graph_t gid, uint32_t nid)
645 {
646 	struct rte_node *node;
647 	struct graph *graph;
648 	rte_graph_off_t off;
649 	rte_node_t count;
650 
651 	if (graph_from_id(gid) == NULL)
652 		goto fail;
653 	STAILQ_FOREACH(graph, &graph_list, next)
654 		if (graph->id == gid) {
655 			rte_graph_foreach_node(count, off, graph->graph,
656 						node) {
657 				if (node->id == nid)
658 					return node;
659 			}
660 			break;
661 		}
662 fail:
663 	return NULL;
664 }
665 
666 struct rte_node *
667 rte_graph_node_get_by_name(const char *graph_name, const char *node_name)
668 {
669 	struct rte_node *node;
670 	struct graph *graph;
671 	rte_graph_off_t off;
672 	rte_node_t count;
673 
674 	STAILQ_FOREACH(graph, &graph_list, next)
675 		if (!strncmp(graph->name, graph_name, RTE_GRAPH_NAMESIZE)) {
676 			rte_graph_foreach_node(count, off, graph->graph,
677 						node) {
678 				if (!strncmp(node->name, node_name,
679 					     RTE_NODE_NAMESIZE))
680 					return node;
681 			}
682 			break;
683 		}
684 
685 	return NULL;
686 }
687 
688 void __rte_noinline
689 __rte_node_stream_alloc(struct rte_graph *graph, struct rte_node *node)
690 {
691 	uint16_t size = node->size;
692 
693 	RTE_VERIFY(size != UINT16_MAX);
694 	/* Allocate double amount of size to avoid immediate realloc */
695 	size = RTE_MIN(UINT16_MAX, RTE_MAX(RTE_GRAPH_BURST_SIZE, size * 2));
696 	node->objs = rte_realloc_socket(node->objs, size * sizeof(void *),
697 					RTE_CACHE_LINE_SIZE, graph->socket);
698 	RTE_VERIFY(node->objs);
699 	node->size = size;
700 	node->realloc_count++;
701 }
702 
703 void __rte_noinline
704 __rte_node_stream_alloc_size(struct rte_graph *graph, struct rte_node *node,
705 			     uint16_t req_size)
706 {
707 	uint16_t size = node->size;
708 
709 	RTE_VERIFY(size != UINT16_MAX);
710 	/* Allocate double amount of size to avoid immediate realloc */
711 	size = RTE_MIN(UINT16_MAX, RTE_MAX(RTE_GRAPH_BURST_SIZE, req_size * 2));
712 	node->objs = rte_realloc_socket(node->objs, size * sizeof(void *),
713 					RTE_CACHE_LINE_SIZE, graph->socket);
714 	RTE_VERIFY(node->objs);
715 	node->size = size;
716 	node->realloc_count++;
717 }
718 
719 static int
720 graph_to_dot(FILE *f, struct graph *graph)
721 {
722 	struct graph_node *graph_node;
723 	char *node_name;
724 	rte_edge_t i;
725 	int rc;
726 
727 	rc = fprintf(f, "digraph \"%s\" {\n\trankdir=LR;\n", graph->name);
728 	if (rc < 0)
729 		goto end;
730 
731 	rc = fprintf(f, "\tnode [margin=0.02 fontsize=11 fontname=sans];\n");
732 	if (rc < 0)
733 		goto end;
734 
735 	STAILQ_FOREACH(graph_node, &graph->node_list, next) {
736 		const char *attrs = "";
737 		node_name = graph_node->node->name;
738 
739 		rc = fprintf(f, "\t\"%s\"", node_name);
740 		if (rc < 0)
741 			goto end;
742 		if (graph_node->node->flags & RTE_NODE_SOURCE_F) {
743 			attrs = " [color=blue style=bold]";
744 			rc = fprintf(f, "%s", attrs);
745 			if (rc < 0)
746 				goto end;
747 		} else if (graph_node->node->nb_edges == 0) {
748 			rc = fprintf(f, " [fontcolor=darkorange shape=plain]");
749 			if (rc < 0)
750 				goto end;
751 		}
752 		rc = fprintf(f, ";\n");
753 		if (rc < 0)
754 			goto end;
755 		for (i = 0; i < graph_node->node->nb_edges; i++) {
756 			const char *node_attrs = attrs;
757 			if (graph_node->adjacency_list[i]->node->nb_edges == 0)
758 				node_attrs = " [color=darkorange]";
759 			rc = fprintf(f, "\t\"%s\" -> \"%s\"%s;\n", node_name,
760 				     graph_node->adjacency_list[i]->node->name,
761 				     node_attrs);
762 			if (rc < 0)
763 				goto end;
764 		}
765 	}
766 	rc = fprintf(f, "}\n");
767 	if (rc < 0)
768 		goto end;
769 
770 	return 0;
771 end:
772 	rte_errno = EBADF;
773 	return -rte_errno;
774 }
775 
776 int
777 rte_graph_export(const char *name, FILE *f)
778 {
779 	struct graph *graph;
780 	int rc = ENOENT;
781 
782 	STAILQ_FOREACH(graph, &graph_list, next) {
783 		if (strncmp(graph->name, name, RTE_GRAPH_NAMESIZE) == 0) {
784 			rc = graph_to_dot(f, graph);
785 			goto end;
786 		}
787 	}
788 end:
789 	return -rc;
790 }
791 
792 static void
793 graph_scan_dump(FILE *f, rte_graph_t id, bool all)
794 {
795 	struct graph *graph;
796 
797 	RTE_VERIFY(f);
798 	if (graph_from_id(id) == NULL)
799 		goto fail;
800 
801 	STAILQ_FOREACH(graph, &graph_list, next) {
802 		if (all == true) {
803 			graph_dump(f, graph);
804 		} else if (graph->id == id) {
805 			graph_dump(f, graph);
806 			return;
807 		}
808 	}
809 fail:
810 	return;
811 }
812 
813 void
814 rte_graph_dump(FILE *f, rte_graph_t id)
815 {
816 	graph_scan_dump(f, id, false);
817 }
818 
819 void
820 rte_graph_list_dump(FILE *f)
821 {
822 	graph_scan_dump(f, 0, true);
823 }
824 
825 rte_graph_t
826 rte_graph_max_count(void)
827 {
828 	struct graph *graph;
829 	rte_graph_t count = 0;
830 
831 	STAILQ_FOREACH(graph, &graph_list, next)
832 		count++;
833 
834 	return count;
835 }
836 
837 RTE_LOG_REGISTER_DEFAULT(rte_graph_logtype, INFO);
838