Index: pbx/pbx_config.c =================================================================== --- pbx/pbx_config.c (.../branches/1.2) (revision 51277) +++ pbx/pbx_config.c (.../team/murf/bug8574-1.2) (revision 51277) @@ -39,6 +39,7 @@ #include "asterisk/options.h" #include "asterisk/module.h" #include "asterisk/logger.h" +#include "asterisk/time.h" #include "asterisk/cli.h" #include "asterisk/callerid.h" @@ -117,6 +118,31 @@ "\n" "Example: extensions reload\n"; + +/* some time diff funcs for mini-profiling */ + +static int ast_tvdiff_us(struct timeval end, struct timeval start); +static struct timeval ast_tvdiff(struct timeval end, struct timeval start); + +static int ast_tvdiff_us(struct timeval end, struct timeval start) +{ + return (end.tv_sec-start.tv_sec)*1000000 + end.tv_usec - start.tv_usec; +} + +static struct timeval ast_tvdiff(struct timeval end, struct timeval start) +{ + struct timeval d; + d.tv_sec = end.tv_sec - start.tv_sec; + d.tv_usec = end.tv_usec - start.tv_usec; + if (d.tv_usec < 0) + { + d.tv_usec += 1000000; /* borrow a second */ + d.tv_sec -= 1; /* pay it back */ + } + return d; +} + + /* * Implementation of functions provided by this module */ @@ -1428,11 +1454,18 @@ } static int pbx_load_module(void); +void log_locktime(void); static int handle_reload_extensions(int fd, int argc, char *argv[]) { + struct timeval begin, end, diff; + begin = ast_tvnow(); if (argc!=2) return RESULT_SHOWUSAGE; pbx_load_module(); + end = ast_tvnow(); + diff = ast_tvdiff(end, begin); + ast_log(LOG_NOTICE, "Total Reload time was %d.%06d sec\n", (int)diff.tv_sec, (int)diff.tv_usec); + log_locktime(); return RESULT_SUCCESS; } @@ -1623,9 +1656,15 @@ char *label; char realvalue[256]; int lastpri = -2; + struct timeval begin, endt, diff; + begin = ast_tvnow(); cfg = ast_config_load(config); + endt = ast_tvnow(); + diff = ast_tvdiff(endt, begin); + ast_log(LOG_NOTICE, "Total extensions.conf Read time was %d.%06d sec\n", (int)diff.tv_sec, (int)diff.tv_usec); if (cfg) { + begin = ast_tvnow(); /* Use existing config to populate the PBX table */ static_config = ast_true(ast_variable_retrieve(cfg, "general", "static")); @@ -1645,6 +1684,10 @@ pbx_builtin_setvar_helper(NULL, v->name, realvalue); v = v->next; } + endt = ast_tvnow(); + diff = ast_tvdiff(endt, begin); + ast_log(LOG_NOTICE, "global var set time was %d.%06d sec\n", (int)diff.tv_sec, (int)diff.tv_usec); + begin = ast_tvnow(); cxt = ast_category_browse(cfg, NULL); while(cxt) { /* All categories but "general" or "globals" are considered contexts */ @@ -1652,7 +1695,7 @@ cxt = ast_category_browse(cfg, cxt); continue; } - if ((con=ast_context_create(&local_contexts,cxt, registrar))) { + if ((con=ast_context_create(&local_contexts,cxt, registrar))) { /* local contexts is empty on first invocation */ v = ast_variable_browse(cfg, cxt); while(v) { if (!strcasecmp(v->name, "exten")) { @@ -1782,7 +1825,10 @@ data = ""; if (ast_context_add_switch2(con, appl, data, !strcasecmp(v->name, "eswitch"), registrar)) ast_log(LOG_WARNING, "Unable to include switch '%s' in context '%s'\n", v->value, cxt); + } else { + ast_log(LOG_WARNING,"Unrecognized directive '%s' in context '%s' -- ignoring.\n", v->name, cxt); } + v = v->next; } } @@ -1790,13 +1836,29 @@ } ast_config_destroy(cfg); } + endt = ast_tvnow(); + diff = ast_tvdiff(endt, begin); + ast_log(LOG_NOTICE, "Conversion to internal dialplan format time was %d.%06d sec\n", (int)diff.tv_sec, (int)diff.tv_usec); + + begin = ast_tvnow(); ast_merge_contexts_and_delete(&local_contexts,registrar); + endt = ast_tvnow(); + diff = ast_tvdiff(endt, begin); + ast_log(LOG_NOTICE, "merge/delete time was %d.%06d sec\n", (int)diff.tv_sec, (int)diff.tv_usec); + begin = ast_tvnow(); for (con = ast_walk_contexts(NULL); con; con = ast_walk_contexts(con)) ast_context_verify_includes(con); + endt = ast_tvnow(); + diff = ast_tvdiff(endt, begin); + ast_log(LOG_NOTICE, "verify_include time was %d.%06d sec\n", (int)diff.tv_sec, (int)diff.tv_usec); + begin = ast_tvnow(); pbx_set_autofallthrough(autofallthrough_config); - + endt = ast_tvnow(); + diff = ast_tvdiff(endt, begin); + ast_log(LOG_NOTICE, "set autofallthru time was %d.%06d sec\n", (int)diff.tv_sec, (int)diff.tv_usec); + return 0; } Index: pbx.c =================================================================== --- pbx.c (.../branches/1.2) (revision 51277) +++ pbx.c (.../team/murf/bug8574-1.2) (revision 51277) @@ -51,6 +51,7 @@ #include "asterisk/ast_expr.h" #include "asterisk/linkedlists.h" #include "asterisk/say.h" +#include "asterisk/time.h" #include "asterisk/utils.h" #include "asterisk/causes.h" #include "asterisk/musiconhold.h" @@ -68,6 +69,44 @@ * */ +int ast_tvdiff_us(struct timeval end, struct timeval start); +struct timeval ast_tvdiff(struct timeval end, struct timeval start); +int ast_tvdiff_us(struct timeval end, struct timeval start) +{ + return (end.tv_sec-start.tv_sec)*1000000 + end.tv_usec - start.tv_usec; +} + +struct timeval ast_tvdiff(struct timeval end, struct timeval start) +{ + struct timeval d; + d.tv_sec = end.tv_sec - start.tv_sec; + d.tv_usec = end.tv_usec - start.tv_usec; + if (d.tv_usec < 0) + { + d.tv_usec += 1000000; /* borrow a second */ + d.tv_sec -= 1; /* pay it back */ + } + return d; +} + + +long globlock_total_holdtime =0; +long globlock_total_sets = 0; +long globlock_gettime = 0; + +long conlock_total_holdtime =0; +long conlock_total_sets = 0; +long conlock_gettime = 0; + +void log_locktime(void); + +void log_locktime(void) +{ + ast_log(LOG_NOTICE, "Total global var locked time was %ld usec to get the locks, %ld usec across %ld lock/unlock sets\n", globlock_gettime, globlock_total_holdtime, globlock_total_sets); + ast_log(LOG_NOTICE, "Total context locked time was %ld usec to get the locks, %ld across %ld lock/unlock sets\n", conlock_gettime, conlock_total_holdtime, conlock_total_sets); +} + + #ifdef LOW_MEMORY #define EXT_DATA_SIZE 256 #else @@ -509,6 +548,7 @@ static struct ast_context *contexts = NULL; AST_MUTEX_DEFINE_STATIC(conlock); /* Lock for the ast_context list */ +AST_MUTEX_DEFINE_STATIC(conreloadlock); /* Lock for the ast_context list; to keep modding threads from disturbing your reload */ static struct ast_app *apps = NULL; AST_MUTEX_DEFINE_STATIC(applock); /* Lock for the application list */ @@ -3646,16 +3686,19 @@ length += strlen(name) + 1; if (!extcontexts) { local_contexts = &contexts; + ast_mutex_lock(&conreloadlock); /* don't stick this in while we're reloading */ ast_mutex_lock(&conlock); } else local_contexts = extcontexts; tmp = *local_contexts; - while(tmp) { + while(tmp) { /* LINEAR SEARCH FOR CONTEXT */ if (!strcasecmp(tmp->name, name)) { ast_log(LOG_WARNING, "Tried to register context '%s', already in use\n", name); - if (!extcontexts) + if (!extcontexts) { ast_mutex_unlock(&conlock); + ast_mutex_unlock(&conreloadlock); + } return NULL; } tmp = tmp->next; @@ -3678,8 +3721,11 @@ } else ast_log(LOG_ERROR, "Out of memory\n"); - if (!extcontexts) + if (!extcontexts) { ast_mutex_unlock(&conlock); + ast_mutex_unlock(&conreloadlock); + } + return tmp; } @@ -3694,6 +3740,92 @@ char data[1]; }; +static void separate_sheep_from_goats(int *nonregistrar_count, int *registrar_count, struct ast_context ***nonregistrar_list, struct ast_context ***registrar_list, const char *registrar); + +static void separate_sheep_from_goats(int *nonregistrar_count, int *registrar_count, struct ast_context ***nonregistrar_list, struct ast_context ***registrar_list, const char *registrar) +{ + /* think of the sheep as the contexts we will keep, because their registrar entry doesn't match the one set by the reloader, and won't be deleted. + think of the goats as the contexts that are reloaded, whose registrars match that specified by the loader, and will be deleted. + So, the sheep are the nonregistrar_list, and the goats are the registrar_list. + Pass in the address of two ints to get the size of each array. And, the addresses of two pointers, which this routine will set to point + to the two arrays malloc'd by this routine. Don't forget to free these arrays when you are done! + */ + int sheep_count = 0, goat_count = 0; + int s = 0, g=0; + struct ast_context *tmp=NULL, **sheep, **goats; + tmp = contexts; + while(tmp) { + if (strcasecmp(registrar, tmp->registrar) == 0) + goat_count++; + else + sheep_count++; + tmp = tmp->next; + } + *nonregistrar_count = sheep_count; + *registrar_count = goat_count; + *nonregistrar_list = sheep = calloc(sheep_count,sizeof(struct ast_context *)); + *registrar_list = goats = calloc(goat_count,sizeof(struct ast_context *)); + /* now, go thru the list again, and fill up those arrays */ + tmp = contexts; + while(tmp) { + if (strcasecmp(registrar, tmp->registrar) == 0) { + goats[g++] = tmp; + } else { + sheep[s++] = tmp; + } + tmp = tmp->next; + } +} + +static void destroy_context(struct ast_context *con); +static void destroy_exten(struct ast_exten *e); + +static void destroy_context(struct ast_context *con) /* destroy a non-linked context */ +{ + struct ast_include *coni, *conil= NULL; + struct ast_sw *sw, *swl= NULL; + struct ast_exten *e, *el, *en; + struct ast_ignorepat *ipi, *ipl = NULL; + + if (ast_mutex_lock(&con->lock)) { /* we do this just to make sure nobody's been caught in here traversing */ + ast_log(LOG_WARNING, "Unable to lock context lock\n"); + return; + } + ast_mutex_unlock(&con->lock); + for (coni = con->includes; coni; ) { + /* Free includes */ + conil = coni; + coni = coni->next; + free(conil); + } + for (ipi = con->ignorepats; ipi; ) { + /* Free ignorepats */ + ipl = ipi; + ipi = ipi->next; + free(ipl); + } + for (sw = con->alts; sw; ) { + /* Free switches */ + swl = sw; + sw = sw->next; + free(swl); + swl = sw; + } + for (e = con->root; e;) { + for (en = e->peer; en;) { + el = en; + en = en->peer; + destroy_exten(el); + } + el = e; + e = e->next; + destroy_exten(el); + } + ast_mutex_destroy(&con->lock); + free(con); +} + + AST_LIST_HEAD(store_hints, store_hint); void ast_merge_contexts_and_delete(struct ast_context **extcontexts, const char *registrar) @@ -3703,12 +3835,68 @@ struct store_hint *this; struct ast_hint *hint; struct ast_exten *exten; - int length; + int length,i; struct ast_state_cb *thiscb, *prevcb; + int registrar_match_count; + int registrar_nonmatch_count; + struct ast_context **registrar_match_array; + struct ast_context **registrar_nonmatch_array; + struct timeval begin, end, diff; + struct timeval begin2, end2, diff2; memset(&store, 0, sizeof(store)); AST_LIST_HEAD_INIT(&store); + + ast_mutex_lock(&conreloadlock); + /* while this lock is in place, all other context-modifiers should be held off. + But, from now until the conlock is achieved, all list traversers (hopefully the + majority of threads) can do their thing as usual. + In this brief slice of time, we want to do whatever is necessary to make the time we + hold the conlock as brief as possible: + ALGORITHM 1: + Duplicate the context list, and then do all operations on the list that used + to be done with the conlock set. (remove all the matching registrar'd items, take + the remainder and add them to the new context list), but in this case, we won't need to lock + anything while all of this is done Then, simply get the lock, and swap pointers, and you are done. + Drop the conlock. Now, delete all the contexts in the old list. Drop the conreloadlock. + (You only need to dup the contexts themselves; whatever pointers are in the context can simply be + copied, leaving all the substructure the same.) + (But, if you take this short-cut, what do you destroy when you free a context? -- Some will be have + pointers to substructure which are used in the new context list, some will not!) + Advantages: Least amnt of time with conlock set. Fixed time in conlock. + Disadvantages: Time to completely rebuild the context list, and then destroy most of it. + + ALGORITHM 2: + Count the contexts that would be destroyed via the registrar matching, and alloc an array, and + store the pointers to those contexts in that array. Count and alloc another array for all those that do not + match registrars. Get a pointer to the last context in the new list of contexts. + Then, get the conlock. Make it point to the new list of contexts. Then, thread all the non-matching registrard + contexts to the end of the new list. Drop the conlock. Destroy all the contexts in matched-registrar list. You are + finished. Drop the conreloadlock. + Advantages: A little less memory and time intensive. No dups of context take place. + Disadvantages: conlock time is a function of how many contexts that will not be deleted in the reload. + (but, hopefully this is a minority in a large dialplan, and all we are doing is setting + one pointer per context in this case, so it should be quick) + + On the first pass, we will attempt to implement algorithm 2. + + */ + begin2 = ast_tvnow(); + separate_sheep_from_goats(®istrar_nonmatch_count, ®istrar_match_count, + ®istrar_nonmatch_array, ®istrar_match_array, + registrar); + + tmp = *extcontexts; + while (tmp) { + lasttmp = tmp; + tmp = tmp->next; + } + + end2 = ast_tvnow(); + diff2 = ast_tvdiff(end2, begin2); + ast_log(LOG_NOTICE, "---time spent after conreloadlock was set, before the conlock was set was %d.%06d sec\n", (int)diff2.tv_sec, (int)diff2.tv_usec); + /* it is very important that this function hold the hintlock _and_ the conlock during its operation; not only do we need to ensure that the list of contexts and extensions does not change, but also that no hint callbacks (watchers) are @@ -3717,9 +3905,13 @@ in addition, the locks _must_ be taken in this order, because there are already other code paths that use this order */ + begin = ast_tvnow(); ast_mutex_lock(&conlock); ast_mutex_lock(&hintlock); - + end = ast_tvnow(); + diff = ast_tvdiff(end, begin); + conlock_gettime += ((int)diff.tv_sec*1000000) + (int)diff.tv_usec; + /* preserve all watchers for hints associated with this registrar */ for (hint = hints; hint; hint = hint->next) { if (hint->callbacks && !strcmp(registrar, hint->exten->parent->registrar)) { @@ -3740,34 +3932,53 @@ } } +#ifdef OLD_STUFF tmp = *extcontexts; if (registrar) { - __ast_context_destroy(NULL,registrar); + begin2 = ast_tvnow(); + __ast_context_destroy(NULL,registrar); /* destroy all system contexts with matching registrar setting */ + end2 = ast_tvnow(); + diff2 = ast_tvdiff(end2, begin2); + ast_log(LOG_NOTICE, "---time spent in context_destroy(NULL,%s) was %d.%06d sec\n", registrar, (int)diff2.tv_sec, (int)diff2.tv_usec); while (tmp) { lasttmp = tmp; tmp = tmp->next; } } else { while (tmp) { - __ast_context_destroy(tmp,tmp->registrar); + __ast_context_destroy(tmp,tmp->registrar); /* destroy all system contexts that match the list in extcontexts */ lasttmp = tmp; tmp = tmp->next; } } +#endif + + begin2 = ast_tvnow(); if (lasttmp) { - lasttmp->next = contexts; + int i=0; + while (i < registrar_nonmatch_count) /* if this is not fast enough, then we have to go to algorithm 1 */ + { + lasttmp->next = registrar_nonmatch_array[i++]; + lasttmp = lasttmp->next; + } + lasttmp->next = 0; /* end the list */ + contexts = *extcontexts; *extcontexts = NULL; } else ast_log(LOG_WARNING, "Requested contexts didn't get merged\n"); + end2 = ast_tvnow(); + diff2 = ast_tvdiff(end2, begin2); + ast_log(LOG_NOTICE, "---time spent relinking in new contexts in conlock was %d.%06d sec\n", (int)diff2.tv_sec, (int)diff2.tv_usec); /* restore the watchers for hints that can be found; notify those that cannot be restored */ + begin2 = ast_tvnow(); while ((this = AST_LIST_REMOVE_HEAD(&store, list))) { exten = ast_hint_extension(NULL, this->context, this->exten); /* Find the hint in the list of hints */ - for (hint = hints; hint; hint = hint->next) { + for (hint = hints; hint; hint = hint->next) { /* LINEAR search for hints */ if (hint->exten == exten) break; } @@ -3791,10 +4002,29 @@ } free(this); } + end2 = ast_tvnow(); + diff2 = ast_tvdiff(end2, begin2); + ast_log(LOG_NOTICE, "---time spent in restoring hints was %d.%06d sec\n", (int)diff2.tv_sec, (int)diff2.tv_usec); ast_mutex_unlock(&hintlock); ast_mutex_unlock(&conlock); + end = ast_tvnow(); + diff = ast_tvdiff(end, begin); + conlock_total_holdtime += ((int)diff.tv_sec*1000000) + (int)diff.tv_usec; + conlock_total_sets++; + /* ok, now, at our leisure, we can now delete all the contexts in the matched list; + we won't need them, and they aren't pointed at anymore. */ + begin2 = ast_tvnow(); + for (i=0;iname && con && con->name && !strcasecmp(tmp->name, con->name)) || !con) && + if (((tmp->name && con && con->name && !strcasecmp(tmp->name, con->name)) || !con) && /* in the case of a non-null con arg, this equates to a LINEAR SEARCH for the matching context */ (!registrar || !strcasecmp(registrar, tmp->registrar))) { /* Okay, let's lock the structure to be sure nobody else is searching through it. */ @@ -5332,9 +5575,9 @@ return; } if (tmpl) - tmpl->next = tmp->next; + tmpl->next = tmp->next; /* remove the matching context from the system list */ else - contexts = tmp->next; + contexts = tmp->next; /* remove the first context from the system list */ /* Okay, now we're safe to let it go -- in a sense, we were ready to let it go as soon as we locked it. */ ast_mutex_unlock(&tmp->lock); @@ -6266,12 +6509,14 @@ */ int ast_lock_contexts() { + ast_mutex_lock(&conreloadlock); return ast_mutex_lock(&conlock); } int ast_unlock_contexts() { - return ast_mutex_unlock(&conlock); + ast_mutex_unlock(&conlock); + return ast_mutex_unlock(&conreloadlock); } /* Property changes on: . ___________________________________________________________________ Name: automerge + Si-senor Name: svnmerge-integrated + /branches/1.2:1-51276 Name: automerge-email + murf@digium.com