Refactoring for better performance of large auto-cli specs

* Fixed: [very slow execution of load_set_file #288](https://github.com/clicon/clixon/issues/288)
* New `clixon-lib@2021-11-11.yang` revision
  * Modified option: RPC stats extended with YANG stats
* Modified `clixon-config@2021-11-11.yang` revision
  * Added option:
    * CLICON_PLUGIN_CALLBACK_CHECK
    * Enable to make plugin context check before and after all callbacks.
* Added statistics for YANG: number of objects and memory used
* Use the treeref no-copy option of CLIgen to reduce memory
* Refactored cli-generation/autocli-start code
* Refactored cligen glue functions to use cligen_eval directly (remove clicon_eval,clixon_cligen_eval)
This commit is contained in:
Olof hagsand 2021-11-25 12:04:05 +01:00
parent b91ce762d5
commit 5388aace12
29 changed files with 760 additions and 451 deletions

View file

@ -80,9 +80,9 @@
static char *
co2apipath(cg_obj *co)
{
struct cg_callback *cb;
cvec *cvv;
cg_var *cv;
cg_callback *cb;
cvec *cvv;
cg_var *cv;
if (co == NULL)
return NULL;
@ -467,10 +467,19 @@ cli_auto_edit(clicon_handle h,
clicon_err(OE_PLUGIN, 0, "No such parsetree header: %s", treename);
goto done;
}
/* Find the matching cligen object */
if ((co = cligen_co_match(cli_cligen(h))) != NULL &&
(coorig = co->co_treeref_orig) != NULL)
cligen_ph_workpoint_set(ph, coorig);
/* Find the matching cligen object
* Note, is complictead: either an instantiated tree (co_treeref_orig)
* or actual tree (co_ref)
*/
if ((co = cligen_co_match(cli_cligen(h))) != NULL){
if ((coorig = co->co_treeref_orig) != NULL ||
(coorig = co->co_ref) != NULL)
cligen_ph_workpoint_set(ph, coorig);
else {
clicon_err(OE_YANG, EINVAL, "No workpoint found");
goto done;
}
}
else{
clicon_err(OE_YANG, EINVAL, "No workpoint found");
goto done;
@ -500,7 +509,7 @@ cli_auto_edit(clicon_handle h,
/*! CLI callback: Working point tree up to parent
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] argv Vector oif user-supplied keywords
* @param[in] argv Vector of user-supplied keywords
* Format of argv:
* <treename> Name of generated cligen parse-tree, eg "datamodel"
*/
@ -991,7 +1000,7 @@ cli_auto_sub_enter(clicon_handle h,
}
/* Find the point in the generated clispec tree where workpoint should be set */
fa.fa_str = api_path_fmt;
if (pt_apply(cligen_ph_parsetree_get(ph), cli_auto_findpt, &fa) < 0)
if (pt_apply(cligen_ph_parsetree_get(ph), cli_auto_findpt, INT32_MAX, &fa) < 0)
goto done;
if (fa.fa_co == NULL){
clicon_err(OE_PLUGIN, ENOENT, "No such cligen object found %s", api_path);

View file

@ -1113,36 +1113,27 @@ yang2cli_stmt(clicon_handle h,
return retval;
}
/*! Generate CLI code for Yang specification
* @param[in] h Clixon handle
* @param[in] yn Create parse-tree from this yang node
* @param[in] printgen Log generated CLIgen syntax
* @param[in] state Set to include state syntax
* @param[in] show_tree Is tree for show cli command
* @param[out] pt CLIgen parse-tree (must be created on input)
* @retval 0 OK
* @retval -1 Error
*/
int
yang2cli(clicon_handle h,
yang_stmt *yn,
int printgen,
int state,
int show_tree,
parse_tree *pt)
yang2cli_yspec(clicon_handle h,
yang_stmt *yn,
char *name0,
int printgen,
int state,
int show_tree)
{
int retval = -1;
cbuf *cb = NULL;
yang_stmt *yc;
cvec *globals; /* global variables from syntax */
parse_tree *pt0 = NULL;
cbuf *cb0 = NULL;
genmodel_type gt;
char *excludelist;
char **exvec = NULL;
int nexvec = 0;
int e;
yang_stmt *ym;
pt_head *ph;
if (pt == NULL){
clicon_err(OE_YANG, EINVAL, "pt is NULL");
if ((pt0 = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new");
goto done;
}
/* List of modules that should not generate autocli */
@ -1151,48 +1142,49 @@ yang2cli(clicon_handle h,
goto done;
}
gt = clicon_cli_genmodel_type(h);
if ((cb = cbuf_new()) == NULL){
if ((cb0 = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Traverse YANG, loop through all modules and generate CLI */
yc = NULL;
while ((yc = yn_each(yn, yc)) != NULL){
ym = NULL;
while ((ym = yn_each(yn, ym)) != NULL){
/* Check if module is in exclude list */
for (e = 0; e < nexvec; e++){
if (strcmp(yang_argument_get(yc), exvec[e]) == 0)
if (strcmp(yang_argument_get(ym), exvec[e]) == 0)
break;
}
if (e < nexvec)
continue;
if (yang2cli_stmt(h, yc, gt, 0, state, show_tree, cb) < 0)
if (yang2cli_stmt(h, ym, gt, 0, state, show_tree, cb0) < 0)
goto done;
}
if (printgen)
clicon_log(LOG_NOTICE, "%s: Generated CLI spec:\n%s", __FUNCTION__, cbuf_get(cb));
clicon_log(LOG_NOTICE, "%s: Top-level api-spec %s:\n%s",
__FUNCTION__, name0, cbuf_get(cb0));
else
clicon_debug(2, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cb));
/* Parse the buffer using cligen parser. XXX why this?*/
if ((globals = cvec_new(0)) == NULL)
clicon_debug(2, "%s: Top-level api-spec %s:\n%s",
__FUNCTION__, name0, cbuf_get(cb0));
/* load top-level yangspec cli syntax (that point to modules) */
if (cligen_parse_str(cli_cligen(h), cbuf_get(cb0), "yang2cli", pt0, NULL) < 0)
goto done;
/* load cli syntax */
if (cligen_parse_str(cli_cligen(h), cbuf_get(cb), "yang2cli", pt, globals) < 0)
goto done;
cvec_free(globals);
/* Resolve the expand callback functions in the generated syntax.
* This "should" only be GENERATE_EXPAND_XMLDB
* handle=NULL for global namespace, this means expand callbacks must be in
* CLICON namespace, not in a cli frontend plugin.
*/
if (cligen_expandv_str2fn(pt, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
if (cligen_expandv_str2fn(pt0, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
goto done;
/* Append cligen tree and name it */
if ((ph = cligen_ph_add(cli_cligen(h), name0)) == NULL)
goto done;
if (cligen_ph_parsetree_set(ph, pt0) < 0)
goto done;
retval = 0;
done:
if (exvec)
free(exvec);
if (cb)
cbuf_free(cb);
if (cb0)
cbuf_free(cb0);
return retval;
}

View file

@ -52,7 +52,7 @@
/*
* Prototypes
*/
int yang2cli(clicon_handle h, yang_stmt *yspec,
int printgen, int state, int show_tree, parse_tree *ptnew);
int yang2cli_yspec(clicon_handle h, yang_stmt *yspec, char *name0,
int printgen, int state, int show_tree);
#endif /* _CLI_GENERATE_H_ */

View file

@ -107,7 +107,12 @@ cli_handle_init(void)
goto done;
}
cligen_userhandle_set(clih, cl);
cligen_eval_wrap_fn_set(clih, plugin_context_check, cl);
/* To minimize memory, dont copy full treerefs on expand */
cligen_reftree_copy_set(clih, 0);
cl->cl_cligen = clih;
h = (clicon_handle)cl;
done:
return h;

View file

@ -72,7 +72,7 @@
#include "cli_handle.h"
/* Command line options to be passed to getopt(3) */
#define CLI_OPTS "hD:f:E:l:F:1a:u:d:m:qp:GLy:c:U:o:"
#define CLI_OPTS "+hD:f:E:l:F:1a:u:d:m:qp:GLy:c:U:o:"
/*! Check if there is a CLI history file and if so dump the CLI histiry to it
* Just log if file does not exist or is not readable
@ -242,57 +242,6 @@ cli_interactive(clicon_handle h)
return retval;
}
/*! Generate one autocli clispec tree
*
* @param[in] h Clixon handle
* @param[in] name Name of tree
* @param[in] gt genmodel-type, ie HOW to generate the CLI
* @param[in] printgen Print CLI syntax to stderr
* @param[in] show_tree Is tree for show cli command (1 - yes. 0 - no)
*
* Generate clispec (datamodel) from YANG dataspec and add to the set of cligen trees
* (as a separate mode)
* This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) using the "tree reference"
* syntax, ie @datamodel
* @param[in] h Clixon handle
* @param[in] state Set to include state syntax
* @param[in] printgen Print CLI syntax generated from dbspec
* @param[in] show_tree Is tree for show cli command
* @retval 0 OK
* @retval -1 Error
*
* @note that yang2cli generates syntax for ALL modules under the loaded yangspec.
*/
static int
autocli_tree(clicon_handle h,
char *name,
int state,
int printgen,
int show_tree)
{
int retval = -1;
parse_tree *pt = NULL; /* cli parse tree */
yang_stmt *yspec;
pt_head *ph;
if ((pt = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new");
goto done;
}
yspec = clicon_dbspec_yang(h);
/* Generate tree (this is where the action is) */
if (yang2cli(h, yspec, printgen, state, show_tree, pt) < 0)
goto done;
/* Append cligen tree and name it */
if ((ph = cligen_ph_add(cli_cligen(h), name)) == NULL)
goto done;
if (cligen_ph_parsetree_set(ph, pt) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Generate autocli, ie if enabled, generate clispec from YANG and add to cligen parse-trees
*
* Generate clispec (datamodel) from YANG dataspec and add to the set of cligen trees
@ -310,13 +259,16 @@ static int
autocli_start(clicon_handle h,
int printgen)
{
int retval = -1;
int autocli_model = 0;
cbuf *show_treename = NULL, *treename = NULL;
int retval = -1;
int autocli_model = 0;
cbuf *show_treename = NULL;
cbuf *treename = NULL;
yang_stmt *yspec;
/* If autocli disabled quit */
if ((autocli_model = clicon_cli_genmodel(h)) == 0)
goto ok;
yspec = clicon_dbspec_yang(h);
/* Get the autocli type, ie HOW the cli is generated (could be much more here) */
/* Create show_treename cbuf */
if ((show_treename = cbuf_new()) == NULL){
@ -330,23 +282,22 @@ autocli_start(clicon_handle h,
}
/* The tree name is by default @datamodel but can be changed by option (why would one do that?) */
cprintf(treename, "%s", clicon_cli_model_treename(h));
if (autocli_tree(h, cbuf_get(treename), 0, printgen, 0) < 0)
if (yang2cli_yspec(h, yspec, cbuf_get(treename), printgen, 0, 0) < 0)
goto done;
/* The tree name is by default @datamodelshow but can be changed by option (why would one do that?) */
cprintf(show_treename, "%s", clicon_cli_model_treename(h));
cprintf(show_treename, "show");
if (autocli_tree(h, cbuf_get(show_treename), 0, printgen, 1) < 0)
if (yang2cli_yspec(h, yspec, cbuf_get(show_treename), printgen, 0, 1) < 0)
goto done;
/* Create a tree for config+state. This tree's name has appended "state" to @datamodel
*/
if (autocli_model > 1){
cprintf(treename, "state");
if (autocli_tree(h, cbuf_get(treename), 1, printgen, 1) < 0)
if (yang2cli_yspec(h, yspec, cbuf_get(treename), printgen, 1, 1) < 0)
goto done;
}
ok:
retval = 0;
done:

View file

@ -522,105 +522,6 @@ cli_handler_err(FILE *f)
return 0;
}
/*! Variant of eval for context checking
* @see cligen_eval
*/
static int
clixon_cligen_eval(cligen_handle h,
cg_obj *co,
cvec *cvv)
{
struct cg_callback *cc;
int retval = -1;
cvec *argv;
cvec *cvv1 = NULL; /* Modified */
plugin_context_t *pc = NULL; /* Clixon-specific */
/* Save matched object for plugin use */
if (h)
cligen_co_match_set(h, co);
/* Make a copy of var argument for modifications */
if ((cvv1 = cvec_dup(cvv)) == NULL)
goto done;
/* Make modifications to cvv */
if (cligen_expand_first_get(h) &&
cvec_expand_first(cvv1) < 0)
goto done;
if (cligen_exclude_keys_get(h) &&
cvec_exclude_keys(cvv1) < 0)
goto done;
/* Traverse callbacks */
for (cc = co->co_callbacks; cc; cc=cc->cc_next){
/* Vector cvec argument to callback */
if (cc->cc_fn_vec){
argv = cc->cc_cvec ? cvec_dup(cc->cc_cvec) : NULL;
cligen_fn_str_set(h, cc->cc_fn_str);
/* Clixon-specific */
if ((pc = plugin_context_get()) == NULL)
break;
if ((retval = (*cc->cc_fn_vec)(
cligen_userhandle(h)?cligen_userhandle(h):h,
cvv1,
argv)) < 0){
if (argv != NULL)
cvec_free(argv);
cligen_fn_str_set(h, NULL);
goto done;
}
/* Clixon-specific */
if (plugin_context_check(pc, "CLIgen", cc->cc_fn_str) < 0)
break;
if (pc){
free(pc);
pc = NULL;
}
if (argv != NULL)
cvec_free(argv);
cligen_fn_str_set(h, NULL);
}
}
retval = 0;
done:
if (pc) /* Clixon-specific */
free(pc);
if (cvv1)
cvec_free(cvv1);
return retval;
}
/*! Evaluate a matched command
* @param[in] h Clicon handle
* @param[in] cmd The command string
* @param[in] match_obj
* @param[in] cvv
* @retval int If there is a callback, the return value of the callback is returned,
* @retval 0 otherwise
*/
int
clicon_eval(clicon_handle h,
char *cmd,
cg_obj *match_obj,
cvec *cvv)
{
int retval = 0;
cli_output_reset();
if (!cligen_exiting(cli_cligen(h))) {
clicon_err_reset();
if ((retval = clixon_cligen_eval(cli_cligen(h), match_obj, cvv)) < 0) {
#if 0 /* This is removed since we get two error messages on failure.
But maybe only sometime?
Both a real log when clicon_err is called, and the here again.
(Before clicon_err was silent) */
cli_handler_err(stdout);
#endif
}
}
return retval;
}
/*! Given a command string, parse and if match single command, eval it.
* Parse and evaluate the string according to
* the syntax parse tree of the syntax mode specified by *mode.
@ -644,18 +545,20 @@ clicon_parse(clicon_handle h,
cligen_result *result,
int *evalres)
{
int retval = -1;
char *modename;
char *modename0;
int r;
cli_syntax_t *stx = NULL;
int retval = -1;
char *modename;
int ret;
cli_syntax_t *stx = NULL;
cli_syntaxmode_t *csm;
parse_tree *pt; /* Orig */
cg_obj *match_obj = NULL;
cvec *cvv = NULL;
FILE *f;
char *reason = NULL;
parse_tree *pt; /* Orig */
cg_obj *match_obj = NULL;
cvec *cvv = NULL;
cg_callback *callbacks = NULL;
FILE *f;
char *reason = NULL;
cligen_handle ch;
ch = cli_cligen(h);
if (clicon_get_logflags()&CLICON_LOG_STDOUT)
f = stdout;
else
@ -672,34 +575,18 @@ clicon_parse(clicon_handle h,
}
}
if (csm != NULL){
modename0 = NULL;
if ((pt = cligen_pt_active_get(cli_cligen(h))) != NULL)
modename0 = pt_name_get(pt);
if (cligen_ph_active_set(cli_cligen(h), modename) < 0){
if (cligen_ph_active_set_byname(ch, modename) < 0){
fprintf(stderr, "No such parse-tree registered: %s\n", modename);
goto done;
}
if ((pt = cligen_pt_active_get(cli_cligen(h))) == NULL){
if ((pt = cligen_pt_active_get(ch)) == NULL){
fprintf(stderr, "No such parse-tree registered: %s\n", modename);
goto done;
}
#if 0 // switch after 5.4
if (cliread_parse2(cli_cligen(h), cmd, pt, &match_obj, &cvv, result, &reason) < 0)
if (cliread_parse(ch, cmd, pt, &match_obj, &cvv, &callbacks, result, &reason) < 0)
goto done;
#else
if ((cvv = cvec_new(0)) == NULL)
goto done;;
if (cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv, result, &reason) < 0)
goto done;
#endif
/* Debug command and result code */
clicon_debug(1, "%s result:%d command: \"%s\"", __FUNCTION__, *result, cmd);
if (*result != CG_MATCH)
pt_expand_cleanup(pt); /* XXX change to pt_expand_treeref_cleanup */
if (modename0){
cligen_ph_active_set(cli_cligen(h), modename0);
modename0 = NULL;
}
switch (*result) {
case CG_EOF: /* eof */
case CG_ERROR:
@ -713,12 +600,17 @@ clicon_parse(clicon_handle h,
*modenamep = modename;
cli_set_syntax_mode(h, modename);
}
if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0)
cli_handler_err(stdout);
pt_expand_cleanup(pt);
pt_expand_treeref_cleanup(pt);
cli_output_reset();
if (!cligen_exiting(ch)) {
clicon_err_reset();
if ((ret = cligen_eval(ch, match_obj, cvv, callbacks)) < 0)
cli_handler_err(stdout);
}
else
ret = 0;
if (evalres)
*evalres = r;
*evalres = ret;
break;
default:
fprintf(f, "CLI syntax error: \"%s\" is ambiguous\n", cmd);
@ -727,6 +619,8 @@ clicon_parse(clicon_handle h,
}
retval = 0;
done:
if (callbacks)
co_callbacks_free(&callbacks);
if (reason)
free(reason);
if (cvv)
@ -829,7 +723,7 @@ clicon_cliread(clicon_handle h,
cli_syntaxmode_t *mode;
cli_syntax_t *stx;
cli_prompthook_t *fn;
clixon_plugin_t *cp;
clixon_plugin_t *cp;
char *promptstr;
stx = cli_syntax(h);
@ -850,7 +744,7 @@ clicon_cliread(clicon_handle h,
cli_prompt_set(h, promptstr);
free(promptstr);
}
cligen_ph_active_set(cli_cligen(h), mode->csm_name);
cligen_ph_active_set_byname(cli_cligen(h), mode->csm_name);
if (cliread(cli_cligen(h), stringp) < 0){
clicon_err(OE_FATAL, errno, "CLIgen");

View file

@ -45,7 +45,10 @@
/* clicon generic callback pointer */
typedef void (clicon_callback_t)(clicon_handle h);
/* List of syntax modes */
/* List of syntax modes
* XXX: syntax modes seem not needed, could be replaced by existing (new) cligen structures, such
* as pt_head and others. But code is arcane and difficult to modify.
*/
typedef struct {
qelem_t csm_qelem; /* List header */
char *csm_name; /* Syntax mode name */
@ -54,7 +57,8 @@ typedef struct {
parse_tree *csm_pt; /* CLIgen parse tree */
} cli_syntaxmode_t;
/* Plugin group object. Just a single object, not list. part of cli_handle */
/* Plugin group object. Just a single object, not list. part of cli_handle
*/
typedef struct {
int stx_nmodes; /* Number of syntax modes */
cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */
@ -64,8 +68,6 @@ typedef struct {
void *clixon_str2fn(char *name, void *handle, char **error);
int clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr);
int clicon_parse(clicon_handle h, char *cmd, char **mode, cligen_result *result, int *evalres);
int clicon_cliread(clicon_handle h, char **stringp);