Merge pull request #2097 from qlyoung/show-cmdgraph

Show cmdgraph
This commit is contained in:
Russ White 2018-04-24 11:31:11 -04:00 committed by GitHub
commit 16ec3ea919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 171 additions and 114 deletions

View File

@ -49,6 +49,29 @@ DEFINE_MTYPE(LIB, HOST, "Host config")
DEFINE_MTYPE(LIB, STRVEC, "String vector")
DEFINE_MTYPE(LIB, COMPLETION, "Completion item")
#define item(x) \
{ \
x, #x \
}
/* clang-format off */
const struct message tokennames[] = {
item(WORD_TKN),
item(VARIABLE_TKN),
item(RANGE_TKN),
item(IPV4_TKN),
item(IPV4_PREFIX_TKN),
item(IPV6_TKN),
item(IPV6_PREFIX_TKN),
item(MAC_TKN),
item(MAC_PREFIX_TKN),
item(FORK_TKN),
item(JOIN_TKN),
item(START_TKN),
item(END_TKN),
{0},
};
const char *node_names[] = {
"auth", // AUTH_NODE,
"view", // VIEW_NODE,
@ -121,6 +144,7 @@ const char *node_names[] = {
"bgp ipv6 flowspec", /* BGP_FLOWSPECV6_NODE
*/
};
/* clang-format on */
/* Command vector which includes some level of command lists. Normally
each daemon maintains each own cmdvec. */
@ -1566,6 +1590,21 @@ DEFUN (show_commandtree,
return cmd_list_cmds(vty, argc == 3);
}
DEFUN_HIDDEN(show_cli_graph,
show_cli_graph_cmd,
"show cli graph",
SHOW_STR
"CLI reflection\n"
"Dump current command space as DOT graph\n")
{
struct cmd_node *cn = vector_slot(cmdvec, vty->node);
char *dot = cmd_graph_dump_dot(cn->cmdgraph);
vty_out(vty, "%s\n", dot);
XFREE(MTYPE_TMP, dot);
return CMD_SUCCESS;
}
static int vty_write_config(struct vty *vty)
{
size_t i;
@ -2573,6 +2612,7 @@ void install_default(enum node_type node)
install_element(node, &config_end_cmd);
install_element(node, &config_help_cmd);
install_element(node, &config_list_cmd);
install_element(node, &show_cli_graph_cmd);
install_element(node, &find_cmd);
install_element(node, &config_write_cmd);

View File

@ -146,6 +146,7 @@ enum node_type {
};
extern vector cmdvec;
extern const struct message tokennames[];
extern const char *node_names[];
/* Node which has some commands and prompt string and configuration

View File

@ -25,6 +25,8 @@
#include <zebra.h>
#include "command_graph.h"
#include "command.h"
#include "log.h"
DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens")
DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text")
@ -457,3 +459,92 @@ void cmd_graph_names(struct graph *graph)
cmd_node_names(start, NULL, NULL);
}
#ifndef BUILDING_CLIPPY
void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf)
{
static bool wasend;
char nbuf[512];
struct cmd_token *tok = gn->data;
const char *color;
if (wasend == true) {
wasend = false;
return;
}
if (tok->type == END_TKN) {
wasend = true;
return;
}
snprintf(nbuf, sizeof(nbuf), " n%p [ shape=box, label=<", gn);
buffer_putstr(buf, nbuf);
snprintf(nbuf, sizeof(nbuf), "<b>%s</b>",
lookup_msg(tokennames, tok->type, NULL));
buffer_putstr(buf, nbuf);
if (tok->attr == CMD_ATTR_DEPRECATED)
buffer_putstr(buf, " (d)");
else if (tok->attr == CMD_ATTR_HIDDEN)
buffer_putstr(buf, " (h)");
if (tok->text) {
if (tok->type == WORD_TKN)
snprintf(
nbuf, sizeof(nbuf),
"<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
tok->text);
else
snprintf(nbuf, sizeof(nbuf), "<br/>%s", tok->text);
buffer_putstr(buf, nbuf);
}
switch (tok->type) {
case START_TKN:
color = "#ccffcc";
break;
case FORK_TKN:
color = "#aaddff";
break;
case JOIN_TKN:
color = "#ddaaff";
break;
case WORD_TKN:
color = "#ffffff";
break;
default:
color = "#ffffff";
break;
}
snprintf(nbuf, sizeof(nbuf),
">, style = filled, fillcolor = \"%s\" ];\n", color);
buffer_putstr(buf, nbuf);
for (unsigned int i = 0; i < vector_active(gn->to); i++) {
struct graph_node *adj = vector_slot(gn->to, i);
if (((struct cmd_token *)adj->data)->type == END_TKN) {
snprintf(nbuf, sizeof(nbuf), " n%p -> end%p;\n", gn,
adj);
buffer_putstr(buf, nbuf);
snprintf(
nbuf, sizeof(nbuf),
" end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
adj);
} else
snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn,
adj);
buffer_putstr(buf, nbuf);
}
}
char *cmd_graph_dump_dot(struct graph *cmdgraph)
{
struct graph_node *start = vector_slot(cmdgraph->nodes, 0);
return graph_dump_dot(cmdgraph, start, cmd_graph_node_print_cb);
}
#endif /* BUILDING_CLIPPY */

View File

@ -116,5 +116,21 @@ extern void cmd_graph_parse(struct graph *graph, struct cmd_element *cmd);
extern void cmd_graph_names(struct graph *graph);
extern void cmd_graph_merge(struct graph *old, struct graph *new,
int direction);
/*
* Print callback for DOT dumping.
*
* See graph.h for more details.
*/
extern void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf);
/*
* Dump command graph to DOT.
*
* cmdgraph
* A command graph to dump
*
* Returns:
* String allocated with MTYPE_TMP representing this graph
*/
char *cmd_graph_dump_dot(struct graph *cmdgraph);
#endif /* _FRR_COMMAND_GRAPH_H */

View File

@ -37,9 +37,6 @@ DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc")
void grammar_sandbox_init(void);
void pretty_print_graph(struct vty *vty, struct graph_node *, int, int,
struct graph_node **, size_t);
static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start,
struct graph_node **stack, size_t stackpos,
struct graph_node **visited, size_t *visitpos);
void init_cmdgraph(struct vty *, struct graph **);
/** shim interface commands **/
@ -274,23 +271,19 @@ DEFUN (grammar_test_dot,
".dot filename\n")
{
check_nodegraph();
struct graph_node *stack[CMD_ARGC_MAX];
struct graph_node *visited[CMD_ARGC_MAX * CMD_ARGC_MAX];
size_t vpos = 0;
FILE *ofd = fopen(argv[2]->arg, "w");
if (!ofd) {
vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
return CMD_SUCCESS;
}
fprintf(ofd,
"digraph {\n graph [ rankdir = LR ];\n node [ fontname = \"Fira Mono\", fontsize = 9 ];\n\n");
pretty_print_dot(ofd, 0, vector_slot(nodegraph->nodes, 0), stack, 0,
visited, &vpos);
fprintf(ofd, "}\n");
char *dot = cmd_graph_dump_dot(nodegraph);
fprintf(ofd, "%s", dot);
fclose(ofd);
XFREE(MTYPE_TMP, dot);
return CMD_SUCCESS;
}
@ -489,24 +482,6 @@ void grammar_sandbox_init(void)
install_element(ENABLE_NODE, &grammar_access_cmd);
}
#define item(x) { x, #x }
struct message tokennames[] = {item(WORD_TKN), // words
item(VARIABLE_TKN), // almost anything
item(RANGE_TKN), // integer range
item(IPV4_TKN), // IPV4 addresses
item(IPV4_PREFIX_TKN), // IPV4 network prefixes
item(IPV6_TKN), // IPV6 prefixes
item(IPV6_PREFIX_TKN), // IPV6 network prefixes
item(MAC_TKN), // MAC address
item(MAC_PREFIX_TKN), // MAC address w/ mask
/* plumbing types */
item(FORK_TKN),
item(JOIN_TKN),
item(START_TKN), // first token in line
item(END_TKN), // last token in line
{0}};
/**
* Pretty-prints a graph, assuming it is a tree.
*
@ -571,89 +546,6 @@ void pretty_print_graph(struct vty *vty, struct graph_node *start, int level,
vty_out(vty, "\n");
}
static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start,
struct graph_node **stack, size_t stackpos,
struct graph_node **visited, size_t *visitpos)
{
// print this node
char tokennum[32];
struct cmd_token *tok = start->data;
const char *color;
for (size_t i = 0; i < (*visitpos); i++)
if (visited[i] == start)
return;
visited[(*visitpos)++] = start;
if ((*visitpos) == CMD_ARGC_MAX * CMD_ARGC_MAX)
return;
snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
fprintf(ofd, " n%p [ shape=box, label=<", start);
fprintf(ofd, "<b>%s</b>", lookup_msg(tokennames, tok->type, NULL));
if (tok->attr == CMD_ATTR_DEPRECATED)
fprintf(ofd, " (d)");
else if (tok->attr == CMD_ATTR_HIDDEN)
fprintf(ofd, " (h)");
if (tok->text) {
if (tok->type == WORD_TKN)
fprintf(ofd,
"<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
tok->text);
else
fprintf(ofd, "<br/>%s", tok->text);
}
/* if (desc)
fprintf(ofd, " ?'%s'", tok->desc); */
switch (tok->type) {
case START_TKN:
color = "#ccffcc";
break;
case FORK_TKN:
color = "#aaddff";
break;
case JOIN_TKN:
color = "#ddaaff";
break;
case WORD_TKN:
color = "#ffffff";
break;
default:
color = "#ffffff";
break;
}
fprintf(ofd, ">, style = filled, fillcolor = \"%s\" ];\n", color);
if (stackpos == CMD_ARGC_MAX)
return;
stack[stackpos++] = start;
for (unsigned int i = 0; i < vector_active(start->to); i++) {
struct graph_node *adj = vector_slot(start->to, i);
// if this node is a vararg, just print *
if (adj == start) {
fprintf(ofd, " n%p -> n%p;\n", start, start);
} else if (((struct cmd_token *)adj->data)->type == END_TKN) {
// struct cmd_token *et = adj->data;
fprintf(ofd, " n%p -> end%p;\n", start, adj);
fprintf(ofd,
" end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
adj);
} else {
fprintf(ofd, " n%p -> n%p;\n", start, adj);
size_t k;
for (k = 0; k < stackpos; k++)
if (stack[k] == adj)
break;
if (k == stackpos) {
pretty_print_dot(ofd, opts, adj, stack,
stackpos, visited, visitpos);
}
}
}
}
/** stuff that should go in command.c + command.h */
void init_cmdgraph(struct vty *vty, struct graph **graph)
{

View File

@ -43,6 +43,7 @@
#include "ns.h"
#include "vrf.h"
#include "libfrr.h"
#include "command_graph.h"
DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CMD, "Vtysh cmd copy")
@ -2957,10 +2958,26 @@ DEFUN(find,
return CMD_SUCCESS;
}
DEFUN_HIDDEN(show_cli_graph_vtysh,
show_cli_graph_vtysh_cmd,
"show cli graph",
SHOW_STR
"CLI reflection\n"
"Dump current command space as DOT graph\n")
{
struct cmd_node *cn = vector_slot(cmdvec, vty->node);
char *dot = cmd_graph_dump_dot(cn->cmdgraph);
vty_out(vty, "%s\n", dot);
XFREE(MTYPE_TMP, dot);
return CMD_SUCCESS;
}
static void vtysh_install_default(enum node_type node)
{
install_element(node, &config_list_cmd);
install_element(node, &find_cmd);
install_element(node, &show_cli_graph_vtysh_cmd);
install_element(node, &vtysh_output_file_cmd);
install_element(node, &no_vtysh_output_file_cmd);
}