Currently the log filter strings are used in a string comparison against the source filename each time log message is emitted. If no log filters at all are set, there's obviously no string comparison to be done. If any single log filter is set though, this imposes a compute burden on every logging call even if logs from the file in question are disabled. This string comparison must also be done while the logging mutex is held, which has implications for concurrency when multiple threads are emitting log messages. This changes the log filtering to be done based on the virLogSource object name. The virLogSource struct is extended to contain 'serial' and 'priority' fields. Any time the global log filter rules are changed a global serial number is incremented. When a log message is emitted, the serial in the virLogSource instance is compared with the global serial number. If out of date, then the 'priority' field in the virLogSource instance is updated based on the new filter rules. The 'priority' field is checked to see whether the log message should be sent to the log outputs. The comparisons of the 'serial' and 'priority' fields are done with no locks held. So in the common case each logging call has an overhead of 2 integer comparisons, with no locks held. Only if the decision is made to forward the message to the log output, or if the 'serial' value is out of date do locks need to be acquired. Technically the comparisons of the 'serial' and 'priority' fields should be done with locks held, or using atomic operations. Both of these options have a notable performance impact, however, and since all writes a protected by a global mutex, it is believed that worst case behaviour where the fields are read concurrently with being written would merely result in an mistaken emission or dropping of the log message in question. This is an acceptable tradeoff for the performance benefit of avoiding locking. As a quick benchmark, a demo program that registers 500 file descriptors with the event loop (eg equiv of 500 QEMU monitor commands), creates pending read I/O on every FD, and then runs virEventRunDefaultImpl() took 4.6 seconds to do 51200 iterations. After this optimization it only takes 3.3 seconds, with the log APIs no longer being a relevant factor in the running time. Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx> --- src/util/virlog.c | 83 ++++++++++++++++++++++++++----------------------------- src/util/virlog.h | 6 ++++ 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/util/virlog.c b/src/util/virlog.c index 6a20b89..21b82da 100644 --- a/src/util/virlog.c +++ b/src/util/virlog.c @@ -95,6 +95,7 @@ struct _virLogFilter { typedef struct _virLogFilter virLogFilter; typedef virLogFilter *virLogFilterPtr; +static int virLogFiltersSerial = 1; static virLogFilterPtr virLogFilters = NULL; static int virLogNbFilters = 0; @@ -514,6 +515,7 @@ virLogResetFilters(void) VIR_FREE(virLogFilters[i].match); VIR_FREE(virLogFilters); virLogNbFilters = 0; + virLogFiltersSerial++; return i; } @@ -569,6 +571,7 @@ virLogDefineFilter(const char *match, virLogFilters[i].priority = priority; virLogFilters[i].flags = flags; virLogNbFilters++; + virLogFiltersSerial++; cleanup: virLogUnlock(); if (ret < 0) @@ -576,37 +579,6 @@ cleanup: return ret; } - -/** - * virLogFiltersCheck: - * @input: the input string - * - * Check the input of the message against the existing filters. Currently - * the match is just a substring check of the category used as the input - * string, a more subtle approach could be used instead - * - * Returns 0 if not matched or the new priority if found. - */ -static int -virLogFiltersCheck(const char *input, - unsigned int *flags) -{ - int ret = 0; - size_t i; - - virLogLock(); - for (i = 0; i < virLogNbFilters; i++) { - if (strstr(input, virLogFilters[i].match)) { - ret = virLogFilters[i].priority; - *flags = virLogFilters[i].flags; - break; - } - } - virLogUnlock(); - return ret; -} - - /** * virLogResetOutputs: * @@ -745,6 +717,30 @@ virLogVersionString(const char **rawmsg, } +static void +virLogSourceUpdate(virLogSourcePtr source) +{ + virLogLock(); + if (source->serial < virLogFiltersSerial) { + unsigned int priority = virLogDefaultPriority; + unsigned int flags = 0; + size_t i; + + for (i = 0; i < virLogNbFilters; i++) { + if (strstr(source->name, virLogFilters[i].match)) { + priority = virLogFilters[i].priority; + flags = virLogFilters[i].flags; + break; + } + } + + source->priority = priority; + source->flags = flags; + source->serial = virLogFiltersSerial; + } + virLogUnlock(); +} + /** * virLogMessage: * @source: where is that message coming from @@ -805,31 +801,30 @@ virLogVMessage(virLogSourcePtr source, char *str = NULL; char *msg = NULL; char timestamp[VIR_TIME_STRING_BUFLEN]; - int fprio, ret; + int ret; size_t i; int saved_errno = errno; - bool emit = true; unsigned int filterflags = 0; if (virLogInitialize() < 0) return; if (fmt == NULL) - goto cleanup; + return; /* - * check against list of specific logging patterns + * 3 intentionally non-thread safe variable reads. + * Since writes to the variable are serialized on + * virLogLock, worst case result is a log message + * is accidentally dropped or emitted, if another + * thread is updating log filter list concurrently + * with a log message emission. */ - fprio = virLogFiltersCheck(filename, &filterflags); - if (fprio == 0) { - if (priority < virLogDefaultPriority) - emit = false; - } else if (priority < fprio) { - emit = false; - } - - if (!emit) + if (source->serial < virLogFiltersSerial) + virLogSourceUpdate(source); + if (priority < source->priority) goto cleanup; + filterflags = source->flags; /* * serialize the error message, add level and timestamp diff --git a/src/util/virlog.h b/src/util/virlog.h index 3a4abd1..c73867a 100644 --- a/src/util/virlog.h +++ b/src/util/virlog.h @@ -49,6 +49,9 @@ typedef virLogSource *virLogSourcePtr; struct _virLogSource { const char *name; + unsigned int priority; + unsigned int serial; + unsigned int flags; }; /* @@ -59,6 +62,9 @@ struct _virLogSource { # define VIR_LOG_INIT(n) \ static ATTRIBUTE_UNUSED virLogSource virLogSelf = { \ .name = "" n "", \ + .priority = VIR_LOG_ERROR, \ + .serial = 0, \ + .flags = 0, \ }; /* -- 1.8.5.3 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list