~carstengrohmann/OOMAnalyser

8ed7678a741fb046f4bfbadbbe9db6bf698b55ea — Carsten Grohmann a month ago 7af0c1d
Add support for manually triggered OOM

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

M OOMAnalyser.html
M OOMAnalyser.py
M test.py
M OOMAnalyser.html => OOMAnalyser.html +35 -15
@@ 23,6 23,9 @@
        .js-text--default-show {
            /* empty just used to show elements in the default view */
        }
        .js-text--display-none {
            display: none;
        }

        .table__sub-section--bold {
            font-weight: bold;


@@ 59,6 62,13 @@
            max-height: 200px;
        }

        .js-oom-automatic--show {
            /* empty - used to show sections for automatically triggered OOMs */
        }
        .js-oom-manual--show {
            /* empty - used to show sections for manually triggered OOMs */
        }

        .result__table {
            border-collapse: collapse;
            padding: 10px;


@@ 149,9 159,6 @@
            color: #D8000C;
            background-color: #FFD2D2;
        }
        .js-text--display-none {
            display: none;
        }

        .license__text {
            font-size: small;


@@ 282,16 289,28 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {

    <h3>Summary</h3>
    <div id="explanation">
        <p>
            The system couldn't satisfy this request and started the OOM killer to free memory. The OOM killer
            calculates a score for each process and terminates the process with the highest score to satisfy the
            initial memory request.
        </p>
        <p>
            The process &quot;<span class="trigger_proc_name"></span>&quot; (PID <span class="trigger_proc_pid"></span>)
            requested <span class="trigger_proc_requested_memory_pages_kb"></span>
            (<span class="trigger_proc_requested_memory_pages"></span>) memory.
        </p>
        <div class="js-text--default-show js-oom-automatic--show">
            <p>
                The OOM killer was automatically triggered to free memory, because the system couldn't satisfy the
                memory request.
                The OOM killer calculates a score for each process and terminates the process with the highest score
                to satisfy the original memory request.
            </p>
            <p>
                The process &quot;<span class="trigger_proc_name"></span>&quot; (PID <span
                    class="trigger_proc_pid"></span>)
                requested <span class="trigger_proc_requested_memory_pages_kb"></span>
                (<span class="trigger_proc_requested_memory_pages"></span>) memory.
            </p>
        </div>
        <div class="js-text--default-show js-oom-manual--show">
            <p>
                The OOM killer was manually triggered (e.g. with &quot;<code>echo f > /proc/sysrq-trigger</code>&quot;)
                by a user with root privileges.
                There is no demand to free memory but the OOM killer started nevertheless.
                The OOM killer calculates a score for each process and terminates the process with the highest score.
            </p>
        </div>
        <p>
            The process &quot;<span class="killed_proc_name"></span>&quot;
            (PID <span class="killed_proc_pid"></span>) with an OOM score of <span class="killed_proc_score"></span>


@@ 326,7 345,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
        <tr>
            <th colspan="3" scope="row">Trigger Process</th>
        </tr>
        <tr>
        <tr class="js-oom-automatic--show">
            <td></td>
            <td class="text--align-right"><span class="trigger_proc_name"></span> (PID <span class="trigger_proc_pid"></span>)</td>
            <td>This process requests memory and is triggering thereby the OOM situation.</td>


@@ 342,7 361,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
            <td class="trigger_proc_nodemask text--align-right"></td>
            <td>Bit mask indicating the cores on which the process can run.</td>
        </tr>
        <tr>
        <tr class="js-oom-automatic--show">
            <td>Requested memory<br>(order)</td>
            <td class="text--align-right">
                <span class="trigger_proc_requested_memory_pages"></span> (2<span class="trigger_proc_order text__superscript"></span>) pages /


@@ 842,6 861,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
        <li>Fix to allow process names with spaces</li>
        <li>Rework removal of unused information</li>
        <li>Report uncaught errors to the user</li>
        <li>Add support for manually triggered OOM (suggested by Mikko Rantalainen)</li>
        <li>...</li>
    </ol>


M OOMAnalyser.py => OOMAnalyser.py +41 -7
@@ 63,6 63,13 @@ class OOMEntityState:
    complete = 4


class OOMEntityType:
    """Enum for the type of the OOM"""
    unknown = 0
    automatic = 1
    manual = 2


def is_visible(element):
    return element.offsetWidth > 0 and element.offsetHeight > 0



@@ 79,6 86,18 @@ def show_element(element_id):
    element.classList.remove('js-text--display-none')


def hide_elements(selector):
    """Hide all matching elements by adding class js-text--display-none"""
    for element in document.querySelectorAll(selector):
        element.classList.add('js-text--display-none')


def show_elements(selector):
    """Show all matching elements by removing class js-text--display-none"""
    for element in document.querySelectorAll(selector):
        element.classList.remove('js-text--display-none')


def toggle(element_id):
    """Toggle the visibility of the given HTML element"""
    element = document.getElementById(element_id)


@@ 420,6 439,13 @@ class OOMResult:
    :type: OOMEntityState
    """

    oom_type = OOMEntityType.unknown
    """
    Type of this OOM (manually or automatically triggered)
    
    :type: OOMEntityType
    """

    error_msg = ""
    """
    Error message


@@ 450,7 476,7 @@ class OOMAnalyser:
            r'^(?P<trigger_proc_name>[\S ]+) invoked oom-killer: '
            r'gfp_mask=(?P<trigger_proc_gfp_mask>0x[a-z0-9]+)(\((?P<trigger_proc_gfp_flags>[A-Z_|]+)\))?, '
            r'(nodemask=(?P<trigger_proc_nodemask>([\d,-]+|\(null\))), )?'
            r'order=(?P<trigger_proc_order>\d+), '
            r'order=(?P<trigger_proc_order>-?\d+), '
            r'oom_score_adj=(?P<trigger_proc_oomscore>\d+)',
            True,
        ),


@@ 674,6 700,11 @@ class OOMAnalyser:
                      'does not find anything. This will cause subsequent errors.'.format(k, pattern))
        # __pragma__ ('nojsiter')

        if self.oom_result.details['trigger_proc_order'] == "-1":
            self.oom_result.oom_type = OOMEntityType.manual
        else:
            self.oom_result.oom_type = OOMEntityType.automatic

        self.oom_result.details['hardware_info'] = self._extract_block_from_next_pos('Hardware name:')

        # strip "Call Trace" line at beginning and remove leading spaces


@@ 1308,16 1339,13 @@ Killed process 6576 (mysqld) total-vm:33914892kB, anon-rss:20629004kB, file-rss:
    def set_HTML_defaults(self):
        """Reset the HTML document but don't clean elements"""
        # hide all elements marked to be hidden by default
        for element in document.querySelectorAll('.js-text--default-hide'):
            element.classList.add('js-text--display-none')
        hide_elements('.js-text--default-hide')

        # show all elements marked to be shown by default
        for element in document.querySelectorAll('.js-text--default-show'):
            element.classList.remove('js-text--display-none')
        show_elements('.js-text--default-show')

        # show hidden rows
        for element in document.querySelectorAll('table .js-text--display-none'):
            element.classList.remove('js-text--display-none')
        show_elements('table .js-text--display-none')

        # clear notification box
        element = document.getElementById('notify_box')


@@ 1490,6 1518,12 @@ Killed process 6576 (mysqld) total-vm:33914892kB, anon-rss:20629004kB, file-rss:

        hide_element('input')
        show_element('analysis')
        if self.oom_result.oom_type == OOMEntityType.manual:
            hide_elements('.js-oom-automatic--show')
            show_elements('.js-oom-manual--show')
        else:
            show_elements('.js-oom-automatic--show')
            hide_elements('.js-oom-manual--show')

        for item in self.oom_result.details.keys():
            # ignore internal items

M test.py => test.py +15 -0
@@ 190,6 190,10 @@ class TestInBrowser(TestBase):
        swap_total_kb = self.driver.find_element_by_class_name('swap_total_kb')
        self.assertEqual(swap_total_kb.text, '8388604 kBytes')

        explanation = self.driver.find_element_by_id('explanation')
        self.assertTrue('OOM killer was automatically triggered' in explanation.text,
                        'Missing text "OOM killer was automatically triggered"')

    def test_010_load_page(self):
        """Test if the page is loading"""
        assert "OOM Analyser" in self.driver.title


@@ 291,6 295,17 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
            self.check_results()
            self.click_reset()

    def test_070_manually_triggered_OOM(self):
        """Test for manually triggered OOM"""
        example = OOMAnalyser.OOMDisplay.example
        example = example.replace('order=0', 'order=-1')
        self.analyse_oom(example)
        self.assert_on_warn_error()

        explanation = self.driver.find_element_by_id('explanation')
        self.assertTrue('OOM killer was manually triggered' in explanation.text,
                        'Missing text "OOM killer was manually triggered"')


class TestPython(TestBase):