* A yang type regex cache added, this helps the performance by avoiding re-running the regcomp command on every iteration.

* An XML namespace cache added (see `xml2ns()`)
* Better performance of XML whitespace parsing/scanning.
This commit is contained in:
Olof hagsand 2019-04-19 16:01:39 +02:00
parent 728c97ab6d
commit 8c36083e16
8 changed files with 214 additions and 19 deletions

View file

@ -146,7 +146,7 @@ generic_validate(yang_stmt *yspec,
* and call application callback validations.
* @param[in] h Clicon handle
* @param[in] db The startup database. The wanted backend state
* @param[out] xtr Transformed XML
* @param[in] td Transaction
* @param[out] cbret CLIgen buffer w error stmt if retval = 0
* @retval -1 Error - or validation failed (but cbret not set)
* @retval 0 Validation failed (with cbret set)
@ -183,6 +183,10 @@ startup_common(clicon_handle h,
clicon_debug(1, "Reading startup config from %s", db);
if (xmldb_get(h, db, "/", &xt, msd) < 0)
goto done;
if (xml_child_nr(xt) == 0){ /* If empty skip */
td->td_target = xt;
goto ok;
}
if (msd){
if ((ret = clixon_module_upgrade(h, xt, msd, cbret)) < 0)
goto done;
@ -225,6 +229,7 @@ startup_common(clicon_handle h,
/* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
ok:
retval = 1;
done:
if (msd)

View file

@ -114,6 +114,7 @@ db_merge(clicon_handle h,
/*! Clixon startup startup mode: Commit startup configuration into running state
* @param[in] h Clixon handle
* @param[in] db tmp or startup
* @param[out] cbret If status is invalid contains error message
* @retval -1 Error
* @retval 0 Validation failed
@ -150,6 +151,10 @@ startup_mode_startup(clicon_handle h,
int retval = -1;
int ret;
if (strcmp(db, "running")==0){
clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db);
goto done;
}
/* Load plugins and call plugin_init() */
if (backend_plugin_initiate(h) != 0)
goto done;
@ -255,6 +260,8 @@ startup_extraxml(clicon_handle h,
goto done;
if (ret == 0)
goto fail;
if (xt==NULL /* || xml_child_nr(xt)==0 */ ) /* This gives SEGV in test_feature */
goto ok;
/* Write (potentially modified) xml tree xt back to tmp
*/
if ((ret = xmldb_put(h, "tmp", OP_REPLACE, xt,
@ -265,6 +272,7 @@ startup_extraxml(clicon_handle h,
goto fail;
if (ret == 0)
goto fail;
ok:
retval = 1;
done:
if (xt)

View file

@ -218,7 +218,9 @@ struct yang_stmt{
Y_TYPE & identity: store all derived types
*/
yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */
void *ys_regex_cache; /* regex cache */
int _ys_vector_i; /* internal use: yn_each */
};
typedef int (yang_applyfn_t)(yang_stmt *ys, void *arg);
@ -232,6 +234,8 @@ enum rfc_6020 yang_keyword_get(yang_stmt *ys);
char *yang_argument_get(yang_stmt *ys);
cg_var *yang_cv_get(yang_stmt *ys);
cvec *yang_cvec_get(yang_stmt *ys);
void *yang_regex_cache_get(yang_stmt *ys);
int yang_regex_cache_set(yang_stmt *ys, void *regex);
/* Other functions */
yang_stmt *yspec_new(void);

View file

@ -127,6 +127,7 @@ struct xml{
reference, dont free */
cg_var *x_cv; /* Cached value as cligen variable
(eg xml_cmp) */
char *x_ns_cache; /* Cached namespace */
int _x_vector_i; /* internal use: xml_child_each */
int _x_i; /* internal use for sorting:
see xml_enumerate and xml_cmp */
@ -234,6 +235,7 @@ xml_prefix_set(cxobj *xn,
* @retval 0 OK
* @retval -1 Error
* @see xmlns_check XXX can these be merged?
* @note, this function uses a cache. Any case where cache should be cleared?
*/
int
xml2ns(cxobj *x,
@ -241,9 +243,11 @@ xml2ns(cxobj *x,
char **namespace)
{
int retval = -1;
char *ns;
char *ns = NULL;
cxobj *xp;
if ((ns = x->x_ns_cache) != NULL)
goto ok;
if (prefix != NULL) /* xmlns:<prefix>="<uri>" */
ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR);
else /* xmlns="<uri>" */
@ -261,6 +265,11 @@ xml2ns(cxobj *x,
ns = DEFAULT_XML_RPC_NAMESPACE;
#endif
}
if (ns && (x->x_ns_cache = strdup(ns)) == NULL){
clicon_err(OE_XML, errno, "strdup");
goto done;
}
ok:
if (namespace)
*namespace = ns;
retval = 0;
@ -1359,6 +1368,8 @@ xml_free(cxobj *x)
free(x->x_childvec);
if (x->x_cv)
cv_free(x->x_cv);
if (x->x_ns_cache)
free(x->x_ns_cache);
free(x);
return 0;
}

View file

@ -132,10 +132,10 @@ ncname {namestart}{namechar}*
<STATEA>"<?" { BEGIN(PIDECL); return BQMARK; }
<STATEA>\< { BEGIN(START); return *clixon_xml_parsetext; }
<STATEA>& { _YA->ya_lex_state =STATEA;BEGIN(AMPERSAND);}
<STATEA>[ \t] { clixon_xml_parselval.string = yytext;return WHITESPACE; }
<STATEA>\r\n { clixon_xml_parselval.string = "\n";return WHITESPACE; }
<STATEA>[ \t]+ { clixon_xml_parselval.string = yytext;return WHITESPACE; }
<STATEA>\r\n { clixon_xml_parselval.string = "\n"; _YA->ya_linenum++; return WHITESPACE; }
<STATEA>\r { clixon_xml_parselval.string = "\n";return WHITESPACE; }
<STATEA>\n { clixon_xml_parselval.string = yytext; _YA->ya_linenum++;return WHITESPACE; }
<STATEA>\n { clixon_xml_parselval.string = "\n"; _YA->ya_linenum++;return WHITESPACE; }
<STATEA>. { clixon_xml_parselval.string = yytext; return CHARDATA; }
/* @see xml_chardata_encode */

View file

@ -112,6 +112,44 @@ xml_parse_content(struct xml_parse_yacc_arg *ya,
return retval;
}
/*! Add whitespace
* If text, ie only body, keep as is.
* But if there is an element, then skip all whitespace.
*/
static int
xml_parse_whitespace(struct xml_parse_yacc_arg *ya,
char *str)
{
cxobj *xn = ya->ya_xelement;
cxobj *xp = ya->ya_xparent;
int retval = -1;
int i;
ya->ya_xelement = NULL; /* init */
/* If there is an element already, only add one whitespace child
* otherwise, keep all whitespace.
*/
#if 1
for (i=0; i<xml_child_nr(xp); i++){
if (xml_type(xml_child_i(xp, i)) == CX_ELMNT)
goto ok; /* Skip if already element */
}
#endif
if (xn == NULL){
if ((xn = xml_new("body", xp, NULL)) == NULL)
goto done;
xml_type_set(xn, CX_BODY);
}
if (xml_value_append(xn, str)==NULL)
goto done;
ya->ya_xelement = xn;
ok:
retval = 0;
done:
return retval;
}
static int
xml_parse_version(struct xml_parse_yacc_arg *ya,
char *ver)
@ -243,10 +281,16 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya,
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL)
break;
if (xc != NULL){ /* at least one element */
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) {
xml_purge(xc);
xc = NULL; /* reset iterator */
int i;
for (i=0; i<xml_child_nr(x);){
xc = xml_child_i(x, i);
if (xml_type(xc) != CX_BODY){
i++;
continue;
}
if (xml_child_rm(x, i) < 0)
goto done;
xml_free(xc);
}
}
}
@ -425,7 +469,7 @@ content : element { clicon_debug(2, "content -> element"); }
| pi { clicon_debug(2, "content -> pi"); }
| CHARDATA { if (xml_parse_content(_YA, $1) < 0) YYABORT;
clicon_debug(2, "content -> CHARDATA %s", $1); }
| WHITESPACE { if (xml_parse_content(_YA, $1) < 0) YYABORT;
| WHITESPACE { if (xml_parse_whitespace(_YA, $1) < 0) YYABORT;
clicon_debug(2, "content -> WHITESPACE %s", $1); }
| { clicon_debug(2, "content -> "); }
;

View file

@ -69,6 +69,7 @@
#include <sys/param.h>
#include <netinet/in.h>
#include <libgen.h>
#include <regex.h>
/* cligen */
#include <cligen/cligen.h>
@ -222,6 +223,20 @@ yang_cvec_get(yang_stmt *ys)
return ys->ys_cvec;
}
void*
yang_regex_cache_get(yang_stmt *ys)
{
return ys->ys_regex_cache;
}
int
yang_regex_cache_set(yang_stmt *ys,
void *regex)
{
ys->ys_regex_cache = regex;
return 0;
}
/* End access functions */
/*! Create new yang specification
@ -266,6 +281,17 @@ ys_new(enum rfc_6020 keyw)
return ys;
}
static int
yang_regex_cache_free(yang_stmt *ys)
{
if (ys->ys_regex_cache){
regfree(ys->ys_regex_cache);
free(ys->ys_regex_cache);
}
return 0;
}
/*! Free a single yang statement */
static int
ys_free1(yang_stmt *ys)
@ -280,6 +306,7 @@ ys_free1(yang_stmt *ys)
cvec_free(ys->ys_cvec);
if (ys->ys_typecache)
yang_type_cache_free(ys->ys_typecache);
yang_regex_cache_free(ys);
free(ys);
return 0;
}

View file

@ -49,6 +49,7 @@
#include <regex.h>
#include <syslog.h>
#include <assert.h>
#include <regex.h>
#include <netinet/in.h>
/* cligen */
@ -99,6 +100,74 @@ static const map_str2int ytmap[] = {
{NULL, -1}
};
/*! Regular expression compiling
* @retval -1 Error
* @retval 0 regex problem (no match?)
* @retval 1 OK Match
* @see match_regexp the CLIgen original composite function
*/
static int
regex_compile(char *pattern0,
regex_t *re)
{
int retval = -1;
char pattern[1024];
// char errbuf[1024];
int len0;
int status;
len0 = strlen(pattern0);
if (len0 > sizeof(pattern)-5){
clicon_err(OE_XML, EINVAL, "pattern too long");
goto done;
}
strncpy(pattern, "^(", 2);
strncpy(pattern+2, pattern0, sizeof(pattern)-2);
strncat(pattern, ")$", sizeof(pattern)-len0-1);
if ((status = regcomp(re, pattern, REG_NOSUB|REG_EXTENDED)) != 0) {
#if 0 /* ignore error msg for now */
regerror(status, re, errbuf, sizeof(errbuf));
#endif
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Regular expression execution
* @retval -1 Error
* @retval 0 regex problem (no match?)
* @retval 1 OK Match
* @see match_regexp the CLIgen original composite function
*/
static int
regex_exec(regex_t *re,
char *string)
{
int retval = -1;
int status;
// char errbuf[1024];
status = regexec(re, string, (size_t) 0, NULL, 0);
if (status != 0) {
#if 0 /* ignore error msg for now */
regerror(status, re, errbuf, sizeof(errbuf));
#endif
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/* return 1 if built-in, 0 if not */
static int
yang_builtin(char *type)
@ -525,14 +594,41 @@ cv_validate1(cg_var *cv,
}
if ((options & YANG_OPTIONS_PATTERN) != 0){
char *posix = NULL;
regex_t *re = NULL;
if ((re = yang_regex_cache_get(yrestype)) == NULL){
/* Transform to posix regex */
if (regexp_xsd2posix(pattern, &posix) < 0)
goto done;
if ((retval2 = match_regexp(str?str:"", posix)) < 0){
clicon_err(OE_DB, 0, "match_regexp: %s", pattern);
return -1;
/* Create regex cache */
if ((re = malloc(sizeof(*re))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(re, 0, sizeof(*re));
/* Compute regex pattern for use in patterns */
if ((retval2 = regex_compile(posix, re)) < 0)
goto done;
if (retval2 == 0){
if (reason)
*reason = cligen_reason("regexp match fail: \"%s\" does not match %s",
str, pattern);
goto fail;
break;
}
yang_regex_cache_set(yrestype, re);
if (posix)
free(posix);
}
if ((retval2 = regex_exec(re, str?str:"")) < 0)
goto done;
if (retval2 == 0){
if (reason)
*reason = cligen_reason("regexp match fail: \"%s\" does not match %s",
str, pattern);
goto fail;
break;
}
if (retval2 == 0){
if (reason)
*reason = cligen_reason("regexp match fail: \"%s\" does not match %s",
@ -667,7 +763,7 @@ ys_cv_validate_union(yang_stmt *ys,
/*! Validate cligen variable cv using yang statement as spec
*
* @param[in] cv A cligen variable to validate. This is a correctly parsed cv.
* @param[in] ys A yang statement, must be leaf of leaf-list.
* @param[in] ys A yang statement, must be leaf or leaf-list.
* @param[out] reason If given, and if return value is 0, contains a malloced string
* describing the reason why the validation failed. Must be freed.
* @retval -1 Error (fatal), with errno set to indicate error