{fsm,vty}: add a VTY command to generate FSM state graphs

This command generates a state transitions graph for the given FSM.
The output format is DOT/Graphviz, which can be rendered using offline
or online (web) tools.  It's useful for quickly getting a graphical
representation of the FSM states and their interconnections.

https://en.wikipedia.org/wiki/DOT_(graph_description_language)
https://graphviz.org/doc/info/lang.html

Change-Id: I09ee0a8c3fc4b1aa991ab5c93c0b654fccd7ea4c
diff --git a/src/vty/fsm_vty.c b/src/vty/fsm_vty.c
index da6038f..19c35da 100644
--- a/src/vty/fsm_vty.c
+++ b/src/vty/fsm_vty.c
@@ -158,6 +158,44 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(show_fsm_state_graph, show_fsm_state_graph_cmd,
+	"show fsm-state-graph NAME",
+	SHOW_STR "Generate a state transition graph (using DOT language)\n"
+	"FSM name\n")
+{
+	const struct osmo_fsm *fsm;
+
+	fsm = osmo_fsm_find_by_name(argv[0]);
+	if (!fsm) {
+		vty_out(vty, "Error: FSM with name '%s' doesn't exist!%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty_out(vty, "digraph \"%s\" {%s", fsm->name, VTY_NEWLINE);
+
+	for (unsigned int i = 0; i < fsm->num_states; i++) {
+		const struct osmo_fsm_state *st = &fsm->states[i];
+
+		vty_out(vty, "\t\"%s\";  # out_state_mask=0x%08x%s",
+			st->name, st->out_state_mask, VTY_NEWLINE);
+
+		for (unsigned int j = 0; j < sizeof(st->out_state_mask) * 8; j++) {
+			if (~st->out_state_mask & (1 << j))
+				continue;
+			vty_out(vty, "\t\"%s\" -> \"%s\";%s",
+				st->name, osmo_fsm_state_name(fsm, j),
+				VTY_NEWLINE);
+		}
+
+		vty_out(vty, "%s", VTY_NEWLINE);
+	}
+
+	vty_out(vty, "}%s", VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
 DEFUN(show_fsm_insts, show_fsm_insts_cmd,
 	"show fsm-instances all",
 	SH_FSMI_STR
@@ -214,6 +252,7 @@
 
 	install_lib_element_ve(&show_fsm_cmd);
 	install_lib_element_ve(&show_fsms_cmd);
+	install_lib_element_ve(&show_fsm_state_graph_cmd);
 	install_lib_element_ve(&show_fsm_inst_cmd);
 	install_lib_element_ve(&show_fsm_insts_cmd);
 	osmo_fsm_vty_cmds_installed = true;