Add sorting process table

This commit is contained in:
Carsten Grohmann 2020-04-05 14:28:37 +02:00
parent 54e4f566a4
commit 23f064f2b7
2 changed files with 214 additions and 33 deletions

View File

@ -66,17 +66,27 @@
width: 100%; width: 100%;
} }
.pstable__table--noborder * { .pstable__table--noborder {
border: none; border: none;
text-align: right; text-align: right;
background-color: unset; background-color: unset;
padding-left: 5px; table-layout: fixed;
padding-right: 5px;
table-layout: auto;
white-space: nowrap;
} }
.pstable__table--noborder thead {
.pstable__table--noborder thead * {
font-weight: bold; font-weight: bold;
padding-right: unset;
padding-left: unset;
white-space: nowrap;
/* overwrite the generic th/td settings */
border: none;
}
.pstable__table--noborder tbody * {
padding-left: 5px;
padding-right: 18px;
/* overwrite the generic th/td settings */
border: none;
} }
/* Align last both columns to left in the process table */ /* Align last both columns to left in the process table */
@ -95,17 +105,23 @@
} }
.pstable__row-pid--width { .pstable__row-pid--width {
width: 4ch; width: 8ch;
} }
.pstable__row-numeric--width { .pstable__row-numeric--width {
width: 6ch; width: 10ch;
} }
.pstable__row-pages--width { .pstable__row-pages--width {
width: 12ch; width: 16ch;
} }
.pstable__row-oom-score-adj--width { .pstable__row-oom-score-adj--width {
width: 16ch; width: 16ch;
} }
.pstable__row-sort--width {
padding-left: unset;
padding-right: unset;
width: 10px;
display: inline-block;
}
th { th {
font-weight: bold; font-weight: bold;
@ -631,16 +647,46 @@ function goBack() {
<table class="pstable__table--noborder"> <table class="pstable__table--noborder">
<thead> <thead>
<tr> <tr>
<td class="pstable__row-pid--width">pid</td> <td class="pstable__row-pid--width">pid
<td class="pstable__row-numeric--width">uid</td> <a class="pstable__row-sort--width" href="javascript:void(0);"
<td class="pstable__row-numeric--width">tgid</td> id="pstable_sort_pid" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('pid')"></a>
<td class="pstable__row-pages--width">total_vm</td> </td>
<td class="pstable__row-pages--width">rss</td> <td class="pstable__row-numeric--width">uid
<td class="pstable__row-pages--width">nr_ptes</td> <a class="pstable__row-sort--width" href="javascript:void(0);"
<td class="pstable__row-pages--width">swapents</td> id="pstable_sort_uid" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('uid')"></a>
<td class="pstable__row-oom-score-adj--width">oom_score_adj</td> </td>
<td>name</td> <td class="pstable__row-numeric--width">tgid
<td></td> <a class="pstable__row-sort--width" href="javascript:void(0);"
id="pstable_sort_tgid" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('tgid')"></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>
</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>
</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>
</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>
</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>
</td>
<td>name
<a class="pstable__row-sort--width" href="javascript:void(0);"
id="pstable_sort_name" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('name')"></a>
</td>
<td>notes
<a class="pstable__row-sort--width" href="javascript:void(0);"
id="pstable_sort_notes" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('notes')"></a>
</td>
</tr> </tr>
</thead> </thead>
<tbody id="process_table"> <tbody id="process_table">
@ -743,6 +789,7 @@ function goBack() {
<li>Fix calculation of requested memory in kBytes</li> <li>Fix calculation of requested memory in kBytes</li>
<li>Fix issue that prevents units from being copied</li> <li>Fix issue that prevents units from being copied</li>
<li>Show additional information in process table</li> <li>Show additional information in process table</li>
<li>Add sorting process table</li>
<li>...</li> <li>...</li>
</ol> </ol>

View File

@ -13,6 +13,10 @@ DEBUG = False
VERSION = "0.4.0 (devel)" VERSION = "0.4.0 (devel)"
"""Version number""" """Version number"""
# __pragma__ ('skip')
js_undefined = 0 # Prevent complaints by optional static checker
# __pragma__ ('noskip')
class OOMEntityState(object): class OOMEntityState(object):
"""Simple enum to track the completeness of an OOM block""" """Simple enum to track the completeness of an OOM block"""
@ -49,6 +53,11 @@ def error(msg):
show_notifybox('ERROR', msg) show_notifybox('ERROR', msg)
def internal_error(msg):
"""Show the error box and add the internal error message"""
show_notifybox('INTERNAL ERROR', msg)
def warning(msg): def warning(msg):
"""Show the error box and add the warning message""" """Show the error box and add the warning message"""
show_notifybox('WARNING', msg) show_notifybox('WARNING', msg)
@ -475,7 +484,7 @@ class OOMAnalyser(object):
self.results['call_trace'] = call_trace self.results['call_trace'] = call_trace
# extract process table # extract process table
self.results['_processes'] = {} self.results['_ps'] = {}
self.oom_entity.find_text('[ pid ]') self.oom_entity.find_text('[ pid ]')
for line in self.oom_entity: for line in self.oom_entity:
if not line.startswith('['): if not line.startswith('['):
@ -485,9 +494,10 @@ class OOMAnalyser(object):
match = self.REC_PROCESS_LINE.match(line) match = self.REC_PROCESS_LINE.match(line)
if match: if match:
details = match.groupdict() details = match.groupdict()
details['notes'] = ''
pid = details.pop('pid') pid = details.pop('pid')
self.results['_processes'][pid] = {} self.results['_ps'][pid] = {}
self.results['_processes'][pid].update(details) self.results['_ps'][pid].update(details)
def _hex2flags(self, hexvalue, flag_definition): def _hex2flags(self, hexvalue, flag_definition):
"""\ """\
@ -578,23 +588,35 @@ class OOMAnalyser(object):
def _convert_numeric_process_values_to_integer(self): def _convert_numeric_process_values_to_integer(self):
"""Convert numeric values in process table to integer values""" """Convert numeric values in process table to integer values"""
ps = self.results['_processes'] ps = self.results['_ps']
ps_index = []
# TODO Check if transcrypt issue: pragma jsiter for the whole block "for pid_str in ps: ..." # 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' # sets item in "for item in ['uid',..." to 0 instead of 'uid'
# jsiter is necessary to iterate over ps # jsiter is necessary to iterate over ps
for pid_str in ps.keys(): for pid_str in ps.keys():
converted = {} converted = {}
process = ps[pid_str] process = ps[pid_str]
for item in ['uid', 'tgid', 'total_vm_pages', 'rss_pages', 'nr_ptes_pages', 'swapents_pages', 'oom_score_adj']: for item in ['uid', 'tgid', 'total_vm_pages', 'rss_pages', 'nr_ptes_pages', 'swapents_pages',
'oom_score_adj']:
try: try:
converted[item] = int(process[item]) converted[item] = int(process[item])
except: except:
error('Converting process parameter "{}={}" to integer failed'.format(item, process[item])) error('Converting process parameter "{}={}" to integer failed'.format(item, process[item]))
converted['name'] = process['name'] converted['name'] = process['name']
converted['notes'] = process['notes']
pid_int = int(pid_str) pid_int = int(pid_str)
del ps[pid_str] del ps[pid_str]
ps[pid_int] = converted ps[pid_int] = converted
ps_index.append(pid_int)
ps_index.sort(key=int)
self.results['_ps_index'] = ps_index
def _calc_pstable_values(self):
"""Set additional notes to processes listed in the process tableX"""
self.results['_ps'][self.results['trigger_proc_pid']]['notes'] = 'trigger process'
self.results['_ps'][self.results['killed_proc_pid']]['notes'] = 'killed process'
def _calc_trigger_process_values(self): def _calc_trigger_process_values(self):
"""Calculate all values related with the trigger process""" """Calculate all values related with the trigger process"""
@ -648,8 +670,8 @@ class OOMAnalyser(object):
self.results['system_total_ram_kb'] = self.results['ram_pages'] * self.results['page_size_kb'] self.results['system_total_ram_kb'] = self.results['ram_pages'] * self.results['page_size_kb']
self.results['system_total_ramswap_kb'] = self.results['system_total_ram_kb'] + self.results['swap_total_kb'] self.results['system_total_ramswap_kb'] = self.results['system_total_ram_kb'] + self.results['swap_total_kb']
total_rss_pages = 0 total_rss_pages = 0
for pid in self.results['_processes'].keys(): for pid in self.results['_ps'].keys():
total_rss_pages += self.results['_processes'][pid]['rss_pages'] total_rss_pages += self.results['_ps'][pid]['rss_pages']
self.results['system_total_ram_used_kb'] = total_rss_pages * self.results['page_size_kb'] self.results['system_total_ram_used_kb'] = total_rss_pages * self.results['page_size_kb']
self.results['system_total_used_percent'] = int(100 * self.results['system_total_used_percent'] = int(100 *
@ -688,6 +710,7 @@ class OOMAnalyser(object):
""" """
self._convert_numeric_results_to_integer() self._convert_numeric_results_to_integer()
self._convert_numeric_process_values_to_integer() self._convert_numeric_process_values_to_integer()
self._calc_pstable_values()
self._determinate_platform_and_distribution() self._determinate_platform_and_distribution()
self._calc_system_values() self._calc_system_values()
@ -859,6 +882,12 @@ Out of memory: Kill process 6576 (java) score 651 or sacrifice child
Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0kB, shmem-rss:0kB Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0kB, shmem-rss:0kB
''' '''
sorted_column = None
"""Processes will sort by values in this column"""
sort_order = None
"""Sort order for process values"""
svg_namespace = 'http://www.w3.org/2000/svg' svg_namespace = 'http://www.w3.org/2000/svg'
# from Sasha Trubetskoy - https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/ # from Sasha Trubetskoy - https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/
@ -885,6 +914,28 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
'#808080', # Grey '#808080', # Grey
] ]
svg_array_updown = """
<svg width="8" height="11">
<polygon points="0,5 8,5 4,0"/>
<polygon points="0,6 8,6 4,11"/>
</svg>
"""
"""SVG graphics with two black triangles UP and DOWN for sorting"""
svg_array_up = """
<svg width="8" height="11">
<polygon points="0,5 8,5 4,0"/>
</svg>
"""
"""SVG graphics with one black triangle UP for sorting"""
svg_array_down = """
<svg width="8" height="11">
<polygon points="0,6 8,6 4,11"/>
</svg>
"""
"""SVG graphics with one black triangle DOWN for sorting"""
def __init__(self): def __init__(self):
self.set_HTML_defaults() self.set_HTML_defaults()
self.update_toc() self.update_toc()
@ -955,18 +1006,14 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
new_table = '' new_table = ''
table_content = document.getElementById('process_table') table_content = document.getElementById('process_table')
for pid in self.oom_details['_processes'].keys(): for pid in self.oom_details['_ps_index']:
if pid == self.oom_details['trigger_proc_pid']: if pid == self.oom_details['trigger_proc_pid']:
comment = 'trigger process'
css_class = 'class="js-pstable__triggerproc--bgcolor"' css_class = 'class="js-pstable__triggerproc--bgcolor"'
# css_class = 'class="js-pstable__killedproc--bgcolor"'
elif pid == self.oom_details['killed_proc_pid']: elif pid == self.oom_details['killed_proc_pid']:
comment = 'killed process'
css_class = 'class="js-pstable__killedproc--bgcolor"' css_class = 'class="js-pstable__killedproc--bgcolor"'
else: else:
comment = ''
css_class = '' css_class = ''
process = self.oom_details['_processes'][pid] process = self.oom_details['_ps'][pid]
line = """ line = """
<tr {}> <tr {}>
<td>{}</td> <td>{}</td>
@ -982,11 +1029,32 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
</tr> </tr>
""".format(css_class, pid, process['uid'], process['tgid'], process['total_vm_pages'], process['rss_pages'], """.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['nr_ptes_pages'], process['swapents_pages'], process['oom_score_adj'], process['name'],
comment) process['notes'])
new_table += line new_table += line
table_content.innerHTML = new_table table_content.innerHTML = new_table
self.set_sort_triangle()
def set_sort_triangle(self):
"""Set the sorting symbols for all columns in the process table"""
for column_name in ['pid', 'uid', 'tgid', 'total_vm_pages', 'rss_pages', 'nr_ptes_pages', 'swapents_pages',
'oom_score_adj', 'name', 'notes']:
element_id = "pstable_sort_{}".format(column_name)
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 self.sort_order == 'descending':
element.innerHTML=self.svg_array_down
else:
element.innerHTML=self.svg_array_up
else:
element.innerHTML=self.svg_array_updown
def set_HTML_defaults(self, clean_oom=True): def set_HTML_defaults(self, clean_oom=True):
"""Reset the HTML document but don't clean elements""" """Reset the HTML document but don't clean elements"""
if clean_oom: if clean_oom:
@ -1014,6 +1082,11 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
while element.firstChild: while element.firstChild:
element.removeChild(element.firstChild) element.removeChild(element.firstChild)
# reset sort triangles
self.sorted_column = None
self.sort_order = None
self.set_sort_triangle()
# remove svg charts # remove svg charts
for element_id in ('svg_swap', 'svg_ram'): for element_id in ('svg_swap', 'svg_ram'):
element = document.getElementById(element_id) element = document.getElementById(element_id)
@ -1231,5 +1304,66 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
element.textContent = self.oom.text element.textContent = self.oom.text
self.toggle_oom(show=False) self.toggle_oom(show=False)
def sort_pstable(self, column_name):
"""Sort process table by the values in the given column"""
if column_name not in ['pid', 'uid', 'tgid', 'total_vm_pages', 'rss_pages', 'nr_ptes_pages', 'swapents_pages',
'oom_score_adj', 'name', '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:
self.sort_order = None
self.sorted_column = column_name
if not self.sort_order or self.sort_order == 'descending':
self.sort_order = 'ascending'
self.sort_psindex_by_column(column_name)
else:
self.sort_order = 'descending'
self.sort_psindex_by_column(column_name, True)
self.sorted_column = column_name
self.update_process_table()
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'.
Is uses bubble sort with all disadvantages but just a few lines of code
"""
ps = self.oom_details['_ps']
ps_index = self.oom_details['_ps_index']
def getvalue(column_name, i):
if column_name == 'pid':
value = ps_index[i]
else:
value = ps[ps_index[i]][column_name]
# JS sorts alphanumeric by default, convert values explicit to integers to sort numerically
if column_name not in ['name', 'notes'] and value is not js_undefined:
value = int(value)
return value
# We set swapped to True so the loop looks runs at least once
swapped = True
while swapped:
swapped = False
for i in range(len(ps_index) - 1):
v1 = getvalue(column_name, i)
v2 = getvalue(column_name, i+1)
if (not reverse and v1 > v2) or (reverse and v1 < v2):
# Swap the elements
ps_index[i], ps_index[i+1] = ps_index[i+1], ps_index[i]
# Set the flag to True so we'll loop again
swapped = True
OOMDisplayInstance = OOMDisplay() OOMDisplayInstance = OOMDisplay()