Module: check_mk
Branch: master
Commit: 4f10ce01798a94862ea86709d2e6313256ffda5b
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=4f10ce01798a94…
Author: Sven Panne <sp(a)mathias-kettner.de>
Date: Mon Feb 26 10:39:46 2018 +0100
Use pathlib facilities instead of home-grown code.
Change-Id: I974c735a265bc1ad5451827084bfb0c08f42a21a
---
bin/mkeventd | 78 +++++++++++++++++++-----------------------------------------
1 file changed, 25 insertions(+), 53 deletions(-)
diff --git a/bin/mkeventd b/bin/mkeventd
index ebae80e..25444ea 100755
--- a/bin/mkeventd
+++ b/bin/mkeventd
@@ -76,7 +76,7 @@ logger = cmk.log.get_logger("mkeventd")
# '----------------------------------------------------------------------'
# TODO(sp) Turn these helper functions into Paths fields where they belong.
-def history_path(settings):
+def history_dir(settings):
return settings.paths.state_dir.value / 'history'
def master_config_path(settings):
@@ -1007,34 +1007,16 @@ def current_history_period():
# Delete old log files
def expire_logfiles(settings, flush=False):
- # TODO(sp) Use pathlib facilities below
- log_dir = str(history_path(settings))
- if not os.path.exists(log_dir):
- return # No historic files to delete yet.
-
try:
- now = time.time()
- min_mtime = now - g_config["history_lifetime"] * 86400
-
+ days = g_config["history_lifetime"]
+ min_mtime = time.time() - days * 86400
logger.verbose("Expiring logfiles (Horizon: %d days -> %s)" %
- (g_config["history_lifetime"], cmk.render.date_and_time(min_mtime)))
-
- for fn in os.listdir(log_dir):
- if fn.endswith(".log"):
- path = log_dir + "/" + fn
-
- if flush:
- logger.info("Flushed log file %s" % path)
- os.remove(path)
-
- else:
- file_mtime = os.stat(path).st_mtime
-
- if file_mtime < min_mtime:
- logger.info("Deleting log file %s (age %s)" %
- (path, cmk.render.date_and_time(file_mtime)))
- os.remove(path)
-
+ (days, cmk.render.date_and_time(min_mtime)))
+ for path in history_dir(settings).glob('*.log'):
+ if flush or path.stat().st_mtime < min_mtime:
+ logger.info("Deleting log file %s (age %s)" %
+ (path, cmk.render.date_and_time(path.stat().st_mtime)))
+ path.unlink()
except Exception as e:
if settings.options.debug:
raise
@@ -1056,9 +1038,7 @@ def flush_event_history_files(settings):
def get_event_history_from_file(settings, query):
filters, limit = query.filters, query.limit
history_entries = []
- # TODO(sp) Use pathlib facilities below
- log_dir = str(history_path(settings))
- if not os.path.exists(log_dir):
+ if not history_dir(settings).exists():
return []
# Optimization: use grep in order to reduce amount
@@ -1093,14 +1073,11 @@ def get_event_history_from_file(settings, query):
# already be done by the GUI, so we don't do that twice. Skipping
# this # will lead into some lines of a single file to be limited in
# wrong order. But this should be better than before.
- timestamps = sorted((int(fn[:-4])
- for fn in os.listdir(log_dir)
- if fn.endswith(".log")),
- reverse=True)
- for ts in timestamps:
+ for ts, path in sorted(((int(str(path.name)[:-4]), path)
+ for path in history_dir(settings).glob('*.log')),
+ reverse=True):
if limit is not None and limit <= 0:
break
- path = log_dir + "/%d.log" % ts
first_entry, last_entry = get_logfile_timespan(path)
for _unused_name, opfunc, argument in time_filters:
if opfunc(first_entry, argument):
@@ -1130,7 +1107,7 @@ def parse_history_file(path, query, greptexts, limit):
# If we have greptexts we pre-filter the file using the extremely
# fast GNU Grep
# Revert lines from the log file to have the newer lines processed first
- cmd = 'tac %s' % quote_shell_string(path)
+ cmd = 'tac %s' % quote_shell_string(str(path))
if greptexts:
cmd += " | egrep -i -e %s" % quote_shell_string(".*".join(greptexts))
grep = subprocess.Popen(cmd, shell=True, close_fds=True, stdout=subprocess.PIPE) # nosec
@@ -1158,8 +1135,9 @@ def parse_history_file(path, query, greptexts, limit):
def get_logfile_timespan(path):
try:
- first_entry = float(file(path).readline().split('\t', 1)[0])
- last_entry = os.stat(path).st_mtime
+ with path.open() as f:
+ first_entry = float(f.readline().split('\t', 1)[0])
+ last_entry = path.stat().st_mtime
return first_entry, last_entry
except Exception:
return 0.0, 0.0
@@ -1753,20 +1731,14 @@ class EventServer(ECServerThread):
self.logger.exception('Exception handling a SNMP trap from "%s". Skipping this one' %
(data[1][0]))
- # check whether or not spool files are available
- # TODO(sp) Use pathlib facilities below
- spool_dir = str(spool_path(self.settings))
- if os.path.exists(spool_dir):
- spool_files = [f for f in os.listdir(spool_dir) if f[0] != '.']
- if spool_files:
- # progress the first spool file we get
- this_path = spool_dir + '/' + spool_files.pop()
- self.process_raw_lines(file(this_path).read())
- os.remove(this_path)
- if spool_files:
- select_timeout = 0 # enable fast processing to process further files
- else:
- select_timeout = 1 # restore default select timeout
+ try:
+ # process the first spool file we get
+ spool_file = next(spool_path(self.settings).glob('[!.]*'))
+ self.process_raw_lines(spool_file.read_bytes())
+ spool_file.unlink()
+ select_timeout = 0 # enable fast processing to process further files
+ except StopIteration:
+ select_timeout = 1 # restore default select timeout
# Processes incoming data, just a wrapper between the real data and the
# handler function to record some statistics etc.
Module: check_mk
Branch: master
Commit: acbdf576d8af190a59b202aa3588a4e299369eaf
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=acbdf576d8af19…
Author: Lars Michelsen <lm(a)mathias-kettner.de>
Date: Mon Feb 26 11:14:33 2018 +0100
5866 FIX Fixed performance issue on ruleset search and discovery page with a large number of rules
When rule matching was analyzed in WATO, which is done for example on the ruleset rule listing
page, on the analyze rulset page of hosts or on the service discovery page, a time consuming
match function needs to be called. This function has now been optimizied which should result in
a more performant processing of these pages with a larger number of rules.
Change-Id: Ib6dc8fcf9b26a573f6aa8aacb260d97322383070
---
.werks/5866 | 13 +++++++++++++
web/htdocs/wato.py | 42 +++++++++++++++++++++---------------------
web/htdocs/watolib.py | 31 ++++++++++++++++---------------
3 files changed, 50 insertions(+), 36 deletions(-)
diff --git a/.werks/5866 b/.werks/5866
new file mode 100644
index 0000000..459fd67
--- /dev/null
+++ b/.werks/5866
@@ -0,0 +1,13 @@
+Title: Fixed performance issue on ruleset search and discovery page with a large number of rules
+Level: 1
+Component: wato
+Compatible: compat
+Edition: cre
+Version: 1.5.0i4
+Date: 1519639736
+Class: fix
+
+When rule matching was analyzed in WATO, which is done for example on the ruleset rule listing
+page, on the analyze rulset page of hosts or on the service discovery page, a time consuming
+match function needs to be called. This function has now been optimizied which should result in
+a more performant processing of these pages with a larger number of rules.
diff --git a/web/htdocs/wato.py b/web/htdocs/wato.py
index a2a6858..d41233c 100644
--- a/web/htdocs/wato.py
+++ b/web/htdocs/wato.py
@@ -13158,12 +13158,14 @@ class ModeEditRuleset(WatoMode):
if self._hostname:
table.cell(_("Ma."))
if rule.is_disabled():
- reason = _("This rule is disabled")
+ reasons = [ _("This rule is disabled") ]
else:
- reason = rule.matches_host_and_item(watolib.Folder.current(), self._hostname, self._item)
+ reasons = list(rule.get_mismatch_reasons(watolib.Folder.current(), self._hostname, self._item))
+
+ matches_rule = not reasons
# Handle case where dict is constructed from rules
- if reason == True and ruleset.match_type() == "dict":
+ if matches_rule and ruleset.match_type() == "dict":
if not rule.value:
title = _("This rule matches, but does not define any parameters.")
img = 'imatch'
@@ -13180,7 +13182,7 @@ class ModeEditRuleset(WatoMode):
img = 'pmatch'
match_keys.update(new_keys)
- elif reason == True and (not alread_matched or ruleset.match_type() == "all"):
+ elif matches_rule and (not alread_matched or ruleset.match_type() == "all"):
title = _("This rule matches for the host '%s'") % self._hostname
if ruleset.item_type():
title += _(" and the %s '%s'.") % (ruleset.item_name(), self._item)
@@ -13188,12 +13190,12 @@ class ModeEditRuleset(WatoMode):
title += "."
img = 'match'
alread_matched = True
- elif reason == True:
+ elif matches_rule:
title = _("This rule matches, but is overridden by a previous rule.")
img = 'imatch'
alread_matched = True
else:
- title = _("This rule does not match: %s") % reason
+ title = _("This rule does not match: %s") % " ".join(reasons)
img = 'nmatch'
html.img("images/icon_rule%s.png" % img, align="absmiddle", title=title, class_="icon")
@@ -15194,24 +15196,13 @@ class ModePatternEditor(WatoMode):
# Check if this rule applies to the given host/service
if self._hostname:
# If hostname (and maybe filename) try match it
- reason = rule.matches_host_and_item(watolib.Folder.current(), self._hostname, self._item)
+ rule_matches = rule.matches_host_and_item(watolib.Folder.current(), self._hostname, self._item)
elif self._item:
# If only a filename is given
- reason = rule.matches_item()
+ rule_matches = rule.matches_item()
else:
# If no host/file given match all rules
- reason = True
-
- match_img = ''
- if reason == True:
- # Applies to the given host/service
- reason_class = 'reason'
- # match_title/match_img are set below per pattern
- else:
- # not matching
- reason_class = 'noreason'
- match_img = 'nmatch'
- match_title = reason
+ rule_matches = True
html.begin_foldable_container("rule", "%s" % abs_rulenr, True,
HTML("<b>Rule #%d</b>" % (abs_rulenr + 1)), indent = False)
@@ -15228,7 +15219,11 @@ class ModePatternEditor(WatoMode):
for state, pattern, comment in pattern_list:
match_class = ''
disp_match_txt = ''
- if reason == True:
+ match_img = ''
+ if rule_matches:
+ # Applies to the given host/service
+ reason_class = 'reason'
+
matched = re.search(pattern, self._match_txt)
if matched:
@@ -15254,6 +15249,11 @@ class ModePatternEditor(WatoMode):
else:
match_img = 'nmatch'
match_title = _('This logfile pattern does not match the given string.')
+ else:
+ # rule does not match
+ reason_class = 'noreason'
+ match_img = 'nmatch'
+ match_title = _('The rule conditions do not match.')
table.row(css=reason_class)
table.cell(_('Match'))
diff --git a/web/htdocs/watolib.py b/web/htdocs/watolib.py
index c10f4be..da1f52e 100644
--- a/web/htdocs/watolib.py
+++ b/web/htdocs/watolib.py
@@ -8005,7 +8005,7 @@ class Ruleset(object):
if rule.is_disabled():
continue
- if rule.matches_host_and_item(Folder.current(), hostname, service) != True:
+ if not rule.matches_host_and_item(Folder.current(), hostname, service):
continue
if self.match_type() == "all":
@@ -8266,37 +8266,38 @@ class Rule(object):
def is_ineffective(self):
hosts = Host.all()
for host_name, host in hosts.items():
- reason = self.matches_host_and_item(host.folder(), host_name, NO_ITEM)
- if reason == True:
+ if self.matches_host_and_item(host.folder(), host_name, NO_ITEM):
return False
return True
def matches_host_and_item(self, host_folder, hostname, item):
- reasons = []
+ """Whether or not the given folder/host/item matches this rule"""
+ for reason in self.get_mismatch_reasons(host_folder, hostname, item):
+ return False
+ return True
+
+
+ def get_mismatch_reasons(self, host_folder, hostname, item):
+ """A generator that provides the reasons why a given folder/host/item not matches this rule"""
host = host_folder.host(hostname)
if not self._matches_hostname(hostname):
- reasons.append(_("The host name does not match."))
+ yield _("The host name does not match.")
for tag in self.tag_specs:
if tag[0] != '/' and tag[0] != '!' and tag not in host.tags():
- reasons.append(_("The host is missing the tag %s") % tag)
+ yield _("The host is missing the tag %s") % tag
elif tag[0] == '!' and tag[1:] in host.tags():
- reasons.append(_("The host has the tag %s") % tag)
+ yield _("The host has the tag %s") % tag
if not self.folder.is_transitive_parent_of(host_folder):
- reasons.append(_("The rule does not apply to the folder of the host."))
+ yield _("The rule does not apply to the folder of the host.")
if item != NO_ITEM and self.ruleset.item_type():
if not self.matches_item(item):
- reasons.append(_("The %s \"%s\" does not match this rule.") %
- (self.ruleset.item_name(), item))
-
- if not reasons:
- return True
- else:
- return " ".join(reasons)
+ yield _("The %s \"%s\" does not match this rule.") % \
+ (self.ruleset.item_name(), item)
def _matches_hostname(self, hostname):
Module: check_mk
Branch: master
Commit: 8586f74ce52b3d70d65e7160186dc65fa800b531
URL: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=8586f74ce52b3d…
Author: Sven Panne <sp(a)mathias-kettner.de>
Date: Mon Feb 26 10:09:10 2018 +0100
Thread settings through, so they are now local to the places where they are used.
There is quite some ugly threading going on here, but this is only a
consequence of the absence of various classes: When we have the right
abstractions in place, the threading will basically vanish...
In a nutshell: Even if the code might look a bit more ugly than before, it
is much more honest about its dependencies than before, which is a good
thing.
Change-Id: I66efdff69ed4d25f8c2983168a3aa7ec44cafc72
---
bin/mkeventd | 189 ++++++++++++++++++++++++++++++-----------------------------
1 file changed, 97 insertions(+), 92 deletions(-)
Diff: http://git.mathias-kettner.de/git/?p=check_mk.git;a=commitdiff;h=8586f74ce5…