~carstengrohmann/OOMAnalyser

9404c87519a7ef84b31e504333e5cfb19b2b86ec — Carsten Grohmann 7 months ago f657ac5
Add support for newer process table format
3 files changed, 210 insertions(+), 86 deletions(-)

M OOMAnalyser.html
M OOMAnalyser.py
M test.py
M OOMAnalyser.html => OOMAnalyser.html +50 -15
@@ 73,6 73,37 @@
            /* empty - used to show sections for manually triggered OOMs */
        }

        .js-pstable_sort_col0 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col1 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col2 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col3 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col4 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col5 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col6 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col7 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col8 {
            /* empty - used to sort process table */
        }
        .js-pstable_sort_col9 {
            /* empty - used to sort process table */
        }

        .js-swap-active--show {
            /* empty - used to show if the swap space is activated */
        }


@@ 144,6 175,9 @@
        .pstable__row-oom-score-adj--width {
            width: 16ch;
        }
        .pstable__row-notes--width {
            width: 20ch;
        }
        .pstable__row-sort--width {
            padding-left: unset;
            padding-right: unset;


@@ 749,51 783,51 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
            <td></td>
            <td class="terminal " colspan="2">
                <table class="pstable__table--noborder">
                    <thead>
                    <thead id="pstable_header">
                        <tr>
                            <td class="pstable__row-numeric--width">pid
                            <td class="pstable__row-numeric--width"><span>pid</span>
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_pid" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('pid')"></a>
                                   id="js-pstable_sort_col0" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(0)"></a>
                            </td>
                            <td class="pstable__row-numeric--width">uid
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_uid" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('uid')"></a>
                                   id="js-pstable_sort_col1" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(1)"></a>
                            </td>
                            <td class="pstable__row-numeric--width">tgid
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_tgid" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('tgid')"></a>
                                   id="js-pstable_sort_col2" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(2)"></a>
                            </td>
                            <td class="pstable__row-pages--width">total_vm
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_total_vm_pages" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('total_vm_pages')"></a>
                                   id="js-pstable_sort_col3" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(3)"></a>
                            </td>
                            <td class="pstable__row-pages--width">rss
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_rss_pages" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('rss_pages')"></a>
                                   id="js-pstable_sort_col4" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(4)"></a>
                            </td>
                            <td class="pstable__row-pages--width">nr_ptes
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_nr_ptes_pages" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('nr_ptes_pages')"></a>
                                   id="js-pstable_sort_col5" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(5)"></a>
                            </td>
                            <td class="pstable__row-pages--width">swapents
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_swapents_pages" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('swapents_pages')"></a>
                                   id="js-pstable_sort_col6" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(6)"></a>
                            </td>
                            <td class="pstable__row-oom-score-adj--width">oom_score_adj
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_oom_score_adj" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('oom_score_adj')"></a>
                                   id="js-pstable_sort_col7" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(7)"></a>
                            </td>
                            <td>name
                            <td class="pstable__row-notes--width">name
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_name" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('name')"></a>
                                   id="js-pstable_sort_col8" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(8)"></a>
                            </td>
                            <td>notes
                            <td class="pstable__row-notes--width">notes
                                <a class="pstable__row-sort--width" href="javascript:void(0);"
                                   id="pstable_sort_notes" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('notes')"></a>
                                   id="js-pstable_sort_col9" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(9)"></a>
                            </td>
                        </tr>
                    </thead>
                    <tbody id="process_table">
                    <tbody id="pstable_content">
                    </tbody>
                </table>
            </td>


@@ 898,6 932,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
        <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>
        <li>Add support for newer process table format</li>
        <li>...</li>
    </ol>


M OOMAnalyser.py => OOMAnalyser.py +154 -71
@@ 347,15 347,31 @@ class BaseKernelConfig:
          (see https://github.com/torvalds/linux/commit/e67d4ca79aaf9d13a00d229b1b1c96b86828e8ba#diff-020720d0699e3ae1afb6fcd815ca8500)
    """

    ps_table_items = ['pid', 'uid', 'tgid', 'total_vm_pages', 'rss_pages', 'nr_ptes_pages', 'swapents_pages',
                      'oom_score_adj']
    pstable_items = ['pid', 'uid', 'tgid', 'total_vm_pages', 'rss_pages', 'nr_ptes_pages', 'swapents_pages',
                      'oom_score_adj', 'name', 'notes']
    """Elements of the process table"""

    pstable_html = ['PID', 'UID', 'TGID', 'Total VM', 'RSS', 'Page Table Entries', 'Swap Entries', 'OOM Adjustment',
                    'Name', 'Notes']
    """
    Headings of the process table columns
    """

    pstable_non_ints = ['pid', 'name', 'notes']
    """Columns that are not converted to an integer"""

    REC_PROCESS_LINE = re.compile(
        r'^\[(?P<pid>[ \d]+)\]\s+(?P<uid>\d+)\s+(?P<tgid>\d+)\s+(?P<total_vm_pages>\d+)\s+(?P<rss_pages>\d+)\s+'
        r'(?P<nr_ptes_pages>\d+)\s+(?P<swapents_pages>\d+)\s+(?P<oom_score_adj>-?\d+)\s+(?P<name>.+)\s*')
    """Match content of process table"""

    pstable_start = '[ pid ]'
    """
    Pattern to find the start of the process table
    
    :type: str
    """

    rec_version4kconfig = re.compile('.+')
    """RE to match kernel version to kernel configuration"""



@@ 416,7 432,32 @@ class KernelConfig_4_9(KernelConfig_4_6):
        self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_OVERLAY_49)


class KernelConfig_5_0(KernelConfig_4_9):
class KernelConfig_4_15(KernelConfig_4_9):
    # Support changes:
    # * mm: consolidate page table accounting (af5b0f6a09e42c9f4fa87735f2a366748767b686)

    # nr_ptes -> pgtables_bytes
    # pr_info("[ pid ]   uid  tgid total_vm      rss nr_ptes nr_pmds nr_puds swapents oom_score_adj name\n");
    # pr_info("[ pid ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name\n");
    REC_PROCESS_LINE = re.compile(
        r'^\[(?P<pid>[ \d]+)\]\s+(?P<uid>\d+)\s+(?P<tgid>\d+)\s+(?P<total_vm_pages>\d+)\s+(?P<rss_pages>\d+)\s+'
        r'(?P<pgtables_bytes>\d+)\s+(?P<swapents_pages>\d+)\s+(?P<oom_score_adj>-?\d+)\s+(?P<name>.+)\s*')

    pstable_items = ['pid', 'uid', 'tgid', 'total_vm_pages', 'rss_pages', 'pgtables_bytes', 'swapents_pages',
                      'oom_score_adj', 'name', 'notes']

    pstable_html = ['PID', 'UID', 'TGID', 'Total VM', 'RSS', 'Page Table Bytes', 'Swap Entries Pages',
                    'OOM Adjustment', 'Name', 'Notes']


class KernelConfig_4_19(KernelConfig_4_15):
    # Support changes:
    # * mm, oom: describe task memory unit, larger PID pad (c3b78b11efbb2865433abf9d22c004ffe4a73f5c)

    pstable_start = '[  pid  ]'


class KernelConfig_5_0(KernelConfig_4_19):
    # Support changes:
    #  * "mm, oom: reorganize the oom report in dump_header" (ef8444ea01d7442652f8e1b8a8b94278cb57eafd)



@@ 488,6 529,8 @@ class KernelConfigRhel7(BaseKernelConfig):
AllKernelConfigs = [
    KernelConfig_5_8(),
    KernelConfig_5_0(),
    KernelConfig_4_15(),
    KernelConfig_4_19(),
    KernelConfig_4_9(),
    KernelConfig_4_6(),
    KernelConfigRhel7(),


@@ 931,21 974,24 @@ class OOMAnalyser:
            call_trace += "{}\n".format(line.strip())
        self.oom_result.details['call_trace'] = call_trace

        # extract process table
        self.oom_result.details['_ps'] = {}
        self.oom_entity.find_text('[ pid ]')
        self._extract_pstable()

    def _extract_pstable(self):
        """Extract process table"""
        self.oom_result.details['_pstable'] = {}
        self.oom_entity.find_text(self.oom_result.kconfig.pstable_start)
        for line in self.oom_entity:
            if not line.startswith('['):
                break
            if line.startswith('[ pid ]'):
            if line.startswith(self.oom_result.kconfig.pstable_start):
                continue
            match = self.oom_result.kconfig.REC_PROCESS_LINE.match(line)
            if match:
                details = match.groupdict()
                details['notes'] = ''
                pid = details.pop('pid')
                self.oom_result.details['_ps'][pid] = {}
                self.oom_result.details['_ps'][pid].update(details)
                self.oom_result.details['_pstable'][pid] = {}
                self.oom_result.details['_pstable'][pid].update(details)

    def _hex2flags(self, hexvalue, flag_definition):
        """\


@@ 1025,7 1071,7 @@ class OOMAnalyser:
            if self.oom_result.details[item] is None:
                self.oom_result.details[item] = '<not found>'
                continue
            if item.endswith('_kb') or item.endswith('_pages') or item.endswith('_pid') or \
            if item.endswith('_bytes') or item.endswith('_kb') or item.endswith('_pages') or item.endswith('_pid') or \
                    item in ['killed_proc_score', 'trigger_proc_order', 'trigger_proc_oomscore']:
                try:
                    self.oom_result.details[item] = int(self.oom_result.details[item])


@@ 1034,9 1080,9 @@ class OOMAnalyser:

        # __pragma__ ('nojsiter')

    def _convert_numeric_process_values_to_integer(self):
    def _convert_pstable_values_to_integer(self):
        """Convert numeric values in process table to integer values"""
        ps = self.oom_result.details['_ps']
        ps = self.oom_result.details['_pstable']
        ps_index = []
        # TODO Check if transcrypt issue: pragma jsiter for the whole block "for pid_str in ps: ..."
        #      sets item in "for item in ['uid',..." to 0 instead of 'uid'


@@ 1044,13 1090,17 @@ class OOMAnalyser:
        for pid_str in ps.keys():
            converted = {}
            process = ps[pid_str]
            for item in self.oom_result.kconfig.ps_table_items:
                if item == 'pid':
            for item in self.oom_result.kconfig.pstable_items:
                if item in self.oom_result.kconfig.pstable_non_ints:
                    continue
                try:
                    converted[item] = int(process[item])
                except:
                    error('Converting process parameter "{}={}" to integer failed'.format(item, process[item]))
                    if item not in process:
                        pitem = '<not in process table>'
                    else:
                        pitem = process[item]
                    error('Converting process parameter "{}={}" to integer failed'.format(item, pitem))

            converted['name'] = process['name']
            converted['notes'] = process['notes']


@@ 1060,7 1110,7 @@ class OOMAnalyser:
            ps_index.append(pid_int)

        ps_index.sort(key=int)
        self.oom_result.details['_ps_index'] = ps_index
        self.oom_result.details['_pstable_index'] = ps_index

    def _calc_pstable_values(self):
        """Set additional notes to processes listed in the process table"""


@@ 1068,12 1118,12 @@ class OOMAnalyser:
        kpid = self.oom_result.details['killed_proc_pid']

        # sometimes the trigger process isn't part of the process table
        if tpid in self.oom_result.details['_ps']:
            self.oom_result.details['_ps'][tpid]['notes'] = 'trigger process'
        if tpid in self.oom_result.details['_pstable']:
            self.oom_result.details['_pstable'][tpid]['notes'] = 'trigger process'

        # assume the killed process may also not part of the process table
        if kpid in self.oom_result.details['_ps']:
            self.oom_result.details['_ps'][kpid]['notes'] = 'killed process'
        if kpid in self.oom_result.details['_pstable']:
            self.oom_result.details['_pstable'][kpid]['notes'] = 'killed process'

    def _calc_trigger_process_values(self):
        """Calculate all values related with the trigger process"""


@@ 1138,8 1188,8 @@ class OOMAnalyser:
        else:
            self.oom_result.details['system_total_ramswap_kb'] = self.oom_result.details['system_total_ram_kb']
        total_rss_pages = 0
        for pid in self.oom_result.details['_ps'].keys():
            total_rss_pages += self.oom_result.details['_ps'][pid]['rss_pages']
        for pid in self.oom_result.details['_pstable'].keys():
            total_rss_pages += self.oom_result.details['_pstable'][pid]['rss_pages']
        self.oom_result.details['system_total_ram_used_kb'] = total_rss_pages * self.oom_result.details['page_size_kb']

        self.oom_result.details['system_total_used_percent'] = int(100 *


@@ 1176,7 1226,7 @@ class OOMAnalyser:
        @see: self.details
        """
        self._convert_numeric_results_to_integer()
        self._convert_numeric_process_values_to_integer()
        self._convert_pstable_values_to_integer()
        self._calc_pstable_values()

        self._determinate_platform_and_distribution()


@@ 1442,8 1492,12 @@ oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,glob
Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:8380kB, file-rss:12548kB, shmem-rss:0kB, UID:0 pgtables:104kB oom_score_adj:0
'''

    sorted_column = None
    """Processes will sort by values in this column"""
    sorted_column_number = None
    """
    Processes will sort by values in this column
    
    @type: int 
    """

    sort_order = None
    """Sort order for process values"""


@@ 1534,6 1588,12 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
                else:
                    content = "{} pages".format(content)

            if item.endswith('_bytes') and isinstance(content, int):
                if content == 1:
                    content = "{} Byte".format(content)
                else:
                    content = "{} Bytes".format(content)

            if item.endswith('_kb') and isinstance(content, int):
                if content == 1:
                    content = "{} kByte".format(content)


@@ 1568,21 1628,41 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:

        toc_content.innerHTML = new_toc

    def update_process_table(self):
    def pstable_fill_HTML(self):
        """
        Re-create the process table with additional information
        Create the process table with additional information
        """
        new_table = ''
        table_content = document.getElementById('process_table')
        # update table heading
        for i, element in enumerate(document.querySelectorAll('#pstable_header > tr > td')):
            element.classList.remove('pstable__row-pages--width', 'pstable__row-numeric--width',
                                     'pstable__row-oom-score-adj--width')

            key = self.oom_result.kconfig.pstable_items[i]
            if key in ['notes', 'names']:
                klass = 'pstable__row-notes--width'
            elif key == 'oom_score_adj':
                klass = 'pstable__row-oom-score-adj--width'
            elif key.endswith('_bytes') or key.endswith('_kb') or key.endswith('_pages'):
                klass = 'pstable__row-pages--width'
            else:
                klass = "pstable__row-numeric--width"
            element.firstChild.textContent = self.oom_result.kconfig.pstable_html[i]
            element.classList.add(klass)

        for pid in self.oom_result.details['_ps_index']:
        # create new table
        new_table = ''
        table_content = document.getElementById('pstable_content')
        for pid in self.oom_result.details['_pstable_index']:
            if pid == self.oom_result.details['trigger_proc_pid']:
                css_class = 'class="js-pstable__triggerproc--bgcolor"'
            elif pid == self.oom_result.details['killed_proc_pid']:
                css_class = 'class="js-pstable__killedproc--bgcolor"'
            else:
                css_class = ''
            process = self.oom_result.details['_ps'][pid]
            process = self.oom_result.details['_pstable'][pid]
            fmt_list = [process[i] for i in self.oom_result.kconfig.pstable_items if not i == 'pid']
            fmt_list.insert(0, css_class)
            fmt_list.insert(1, pid)
            line = """
            <tr {}>
                <td>{}</td>


@@ 1596,32 1676,22 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
                <td>{}</td>
                <td>{}</td>
            </tr>
            """.format(css_class, pid, process['uid'], process['tgid'], process['total_vm_pages'], process['rss_pages'],
                       process['nr_ptes_pages'], process['swapents_pages'], process['oom_score_adj'], process['name'],
                       process['notes'])
            """.format(*fmt_list)
            new_table += line

        table_content.innerHTML = new_table

        self.set_sort_triangle()

    def set_sort_triangle(self):
    def pstable_set_sort_triangle(self):
        """Set the sorting symbols for all columns in the process table"""
        # TODO Check operator overloading
        #      Operator overloading (Pragma opov) does not work in this context.
        #      self.oom_result.kconfig.ps_table_items + ['notes'] will compile to a string
        #      "pid,uid,tgid,total_vm_pages,rss_pages,nr_ptes_pages,swapents_pages,oom_score_adjNotes" and not to an
        #      array
        ps_table_and_notes = self.oom_result.kconfig.ps_table_items[:]
        ps_table_and_notes.append('notes')
        for column_name in ps_table_and_notes:
            element_id = "pstable_sort_{}".format(column_name)
        for column_name in self.oom_result.kconfig.pstable_items:
            column_number = self.oom_result.kconfig.pstable_items.index(column_name)
            element_id = "js-pstable_sort_col{}".format(column_number)
            element = document.getElementById(element_id)
            if not element:
                internal_error('Missing id "{}" in process table.'.format(element_id))
                continue

            if column_name == self.sorted_column:
            if column_number == self.sorted_column_number:
                if self.sort_order == 'descending':
                    element.innerHTML = self.svg_array_down
                else:


@@ 1645,21 1715,30 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
        while element.firstChild:
            element.removeChild(element.firstChild)

        # clear process table
        element = document.getElementById('process_table')
        # remove svg charts
        for element_id in ('svg_swap', 'svg_ram'):
            element = document.getElementById(element_id)
            while element.firstChild:
                element.removeChild(element.firstChild)

        self._clear_pstable()

    def _clear_pstable(self):
        """Clear process table"""
        element = document.getElementById('pstable_content')
        while element.firstChild:
            element.removeChild(element.firstChild)

        # reset sort triangles
        self.sorted_column = None
        self.sorted_column_number = None
        self.sort_order = None
        self.set_sort_triangle()
        self.pstable_set_sort_triangle()

        # remove svg charts
        for element_id in ('svg_swap', 'svg_ram'):
            element = document.getElementById(element_id)
            while element.firstChild:
                element.removeChild(element.firstChild)
        # reset table heading
        for i, element in enumerate(document.querySelectorAll('#pstable_header > tr > td')):
            element.classList.remove('pstable__row-pages--width', 'pstable__row-numeric--width',
                                     'pstable__row-oom-score-adj--width')
            element.firstChild.textContent = "col {}".format(i + 1)

    def svg_create_element(self, height, width, css_class):
        """Return an empty SVG element"""


@@ 1832,7 1911,8 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
            hide_elements('.js-killed-proc-score--show')

        # generate process table
        self.update_process_table()
        self.pstable_fill_HTML()
        self.pstable_set_sort_triangle()

        # show/hide swap space
        if self.oom_result.swap_active:


@@ 1883,23 1963,28 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
        element.textContent = self.oom_result.oom_text
        self.toggle_oom(show=False)

    def sort_pstable(self, column_name):
        """Sort process table by the values in the given column"""
    def sort_pstable(self, column_number):
        """
        Sort process table by values

        :param int column_number: Number of column to sort
        """
        # TODO Check operator overloading
        #      Operator overloading (Pragma opov) does not work in this context.
        #      self.oom_result.kconfig.ps_table_items + ['notes'] will compile to a string
        #      self.oom_result.kconfig.pstable_items + ['notes'] will compile to a string
        #      "pid,uid,tgid,total_vm_pages,rss_pages,nr_ptes_pages,swapents_pages,oom_score_adjNotes" and not to an
        #      array
        ps_table_and_notes = self.oom_result.kconfig.ps_table_items[:]
        ps_table_and_notes = self.oom_result.kconfig.pstable_items[:]
        ps_table_and_notes.append('notes')
        column_name = ps_table_and_notes[column_number]
        if column_name not in ps_table_and_notes:
            internal_error('Can not sort process table with an unknown column name "{}"'.format(column_name))
            return

        # reset sort order if the column has changes
        if column_name != self.sorted_column:
        if column_number != self.sorted_column_number:
            self.sort_order = None
        self.sorted_column = column_name
        self.sorted_column_number = column_number

        if not self.sort_order or self.sort_order == 'descending':
            self.sort_order = 'ascending'


@@ 1908,19 1993,17 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
            self.sort_order = 'descending'
            self.sort_psindex_by_column(column_name, True)

        self.sorted_column = column_name

        self.update_process_table()
        self.pstable_fill_HTML()
        self.pstable_set_sort_triangle()

    def sort_psindex_by_column(self, column_name, reverse=False):
        """
        Sort the pid list '_ps_index' based on the values in the process dict '_ps'.
        Sort the pid list '_pstable_index' based on the values in the process dict '_pstable'.

        Is uses bubble sort with all disadvantages but just a few lines of code
        """

        ps = self.oom_result.details['_ps']
        ps_index = self.oom_result.details['_ps_index']
        ps = self.oom_result.details['_pstable']
        ps_index = self.oom_result.details['_pstable_index']

        def getvalue(column, pos):
            if column == 'pid':


@@ 1928,7 2011,7 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
            else:
                value = ps[ps_index[pos]][column]
            # JS sorts alphanumeric by default, convert values explicit to integers to sort numerically
            if column not in ['name', 'notes'] and value is not js_undefined:
            if column not in self.oom_result.kconfig.pstable_non_ints and value is not js_undefined:
                value = int(value)
            return value


M test.py => test.py +6 -0
@@ 199,6 199,9 @@ class TestInBrowser(TestBase):
        self.assertTrue('OOM killer was automatically triggered' in explanation.text,
                        'Missing text "OOM killer was automatically triggered"')

        head = self.driver.find_element_by_id('pstable_header')
        self.assertTrue('Page Table Entries' in head.text, 'Missing column head line "Page Table Entries"')

        self.check_swap_active()

    def check_results_ubuntu2110(self):


@@ 216,6 219,9 @@ class TestInBrowser(TestBase):
        self.assertFalse('with an OOM score of' in explanation.text,
                        'No OOM score but text "with an OOM score of"')

        head = self.driver.find_element_by_id('pstable_header')
        self.assertTrue('Page Table Bytes' in head.text, 'Missing column head line "Page Table Bytes"')

        self.check_swap_inactive()

    def check_swap_inactive(self):