a7e1e4cc64280f8a7c56dae6074c026acfb1e965 — Carsten Grohmann 9 months ago 7038cce
Add support for journalctl output

The lines in the "Mem-Info:" block start with spaces instead of date /
time / hostname. As a result, removing the needless columns no longer
works correctly.

Suggested-by: Mikko Rantalainen <mikko.rantalainen@gmail.com>
3 files changed, 68 insertions(+), 2 deletions(-)

M OOMAnalyser.html
M OOMAnalyser.py
M test.py
M OOMAnalyser.html => OOMAnalyser.html +1 -0
@@ 897,6 897,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
        <li>Add support for manually triggered OOM (suggested by Mikko Rantalainen)</li>
        <li>Add support for systems w/o swap (suggested by Mikko Rantalainen)</li>
        <li>Add support for newer kernels (suggested by Mikko Rantalainen)</li>
        <li>Add support for journalctl output (suggested by Mikko Rantalainen)</li>

M OOMAnalyser.py => OOMAnalyser.py +36 -2
@@ 525,6 525,7 @@ class OOMEntity:
        oom_lines = self._remove_non_oom_lines(oom_lines)
        oom_lines = self._remove_kernel_colon(oom_lines)
        cols_to_strip = self._number_of_columns_to_strip(oom_lines[self._get_CPU_index(oom_lines)])
        oom_lines = self._journalctl_add_leading_columns_to_meminfo(oom_lines, cols_to_strip)
        oom_lines = self._strip_needless_columns(oom_lines, cols_to_strip)
        oom_lines = self._rsyslog_unescape_lf(oom_lines)

@@ 536,6 537,34 @@ class OOMEntity:
            self.state = OOMEntityState.started

    def _journalctl_add_leading_columns_to_meminfo(self, oom_lines, cols_to_add):
        Add leading columns to handle line breaks in journalctl output correctly.

        The output of the "Mem-Info:" block contains line breaks. journalctl breaks these lines accordingly, but
        inserts at the beginning spaces instead of date and time. As a result, removing the needless columns no longer
        works correctly.

        This function adds columns back in the affected rows so that the removal works cleanly over all rows.

        @see: _rsyslog_unescape_lf()
        pattern = r'^\s+ (active_file|unevictable|slab_reclaimable|mapped|free):.+$'
        rec = re.compile(pattern)

        add_cols = ""
        for i in range(cols_to_add):
            add_cols += "Col{} ".format(i)

        expanded_lines = []
        for line in oom_lines:
            match = rec.search(line)
            if match:
                line = "{} {}".format(add_cols, line.strip())

        return expanded_lines

    def _get_CPU_index(self, lines):
        Return the index of the first line with "CPU: "

@@ 605,12 634,17 @@ class OOMEntity:

    def _rsyslog_unescape_lf(self, oom_lines):
        Rsyslog replaces line breaks with their octal representation #012.
        Split lines at '#012' (octal representation of LF).

        The output of the "Mem-Info:" block contains line breaks. Rsyslog replaces these line breaks with their octal
        representation #012. This breaks the removal of needless columns as well as the detection of the OOM values.

        Splitting the lines (again) solves this issue.

        This feature can be controlled inside the rsyslog configuration with the directives
        $EscapeControlCharactersOnReceive, $Escape8BitCharactersOnReceive and $ControlCharactersEscapePrefix.

        The replacement is only in second line (active_anon:....) of the Mem-Info block.
        @see: _journalctl_add_leading_columns_to_meminfo()
        lines = []

M test.py => test.py +31 -0
@@ 304,6 304,37 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
        self.assertEqual(self.get_error_text(), "ERROR: Failed to extract kernel version from OOM text")

    def test_035_leading_journalctl_input(self):
        """Test loading input from journalctl """
        # prepare example
        example_lines = OOMAnalyser.OOMDisplay.example_rhel7.split('\n')
        res = []

        # unescape #012 - see OOMAnalyser.OOMEntity._rsyslog_unescape_lf()
        for line in example_lines:
            if '#012' in line:
        example_lines = res
        res = []

        # add date/time prefix except for "Mem-Info:" block
        pattern = r'^ (active_file|unevictable|slab_reclaimable|mapped|free):.+$'
        rec = re.compile(pattern)
        for line in example_lines:
            match = rec.search(line)
            if match:
                line = "                                             {}".format(line)
                line = "Apr 01 14:13:32 mysrv <kern.warning> kernel: {}".format(line)
        example = "\n".join(res)


    def test_040_trigger_proc_space(self):
        """Test trigger process name contains a space"""
        example = OOMAnalyser.OOMDisplay.example_rhel7