Add support for newer process table format

This commit is contained in:
Carsten Grohmann 2021-12-21 20:32:49 +01:00
parent f657ac5816
commit 9404c87519
3 changed files with 211 additions and 87 deletions

View File

@ -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>

View File

@ -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')
for pid in self.oom_result.details['_ps_index']:
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)
# 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,22 +1715,31 @@ 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')
while element.firstChild:
element.removeChild(element.firstChild)
# reset sort triangles
self.sorted_column = None
self.sort_order = None
self.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)
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_number = None
self.sort_order = None
self.pstable_set_sort_triangle()
# 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"""
svg = document.createElementNS(self.svg_namespace, 'svg')
@ -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

View File

@ -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):