Add sorting process table
This commit is contained in:
parent
54e4f566a4
commit
23f064f2b7
@ -66,17 +66,27 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pstable__table--noborder * {
|
||||
.pstable__table--noborder {
|
||||
border: none;
|
||||
text-align: right;
|
||||
background-color: unset;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
table-layout: auto;
|
||||
white-space: nowrap;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.pstable__table--noborder thead {
|
||||
|
||||
.pstable__table--noborder thead * {
|
||||
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 */
|
||||
@ -95,17 +105,23 @@
|
||||
}
|
||||
|
||||
.pstable__row-pid--width {
|
||||
width: 4ch;
|
||||
width: 8ch;
|
||||
}
|
||||
.pstable__row-numeric--width {
|
||||
width: 6ch;
|
||||
width: 10ch;
|
||||
}
|
||||
.pstable__row-pages--width {
|
||||
width: 12ch;
|
||||
width: 16ch;
|
||||
}
|
||||
.pstable__row-oom-score-adj--width {
|
||||
width: 16ch;
|
||||
}
|
||||
.pstable__row-sort--width {
|
||||
padding-left: unset;
|
||||
padding-right: unset;
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
@ -631,16 +647,46 @@ function goBack() {
|
||||
<table class="pstable__table--noborder">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="pstable__row-pid--width">pid</td>
|
||||
<td class="pstable__row-numeric--width">uid</td>
|
||||
<td class="pstable__row-numeric--width">tgid</td>
|
||||
<td class="pstable__row-pages--width">total_vm</td>
|
||||
<td class="pstable__row-pages--width">rss</td>
|
||||
<td class="pstable__row-pages--width">nr_ptes</td>
|
||||
<td class="pstable__row-pages--width">swapents</td>
|
||||
<td class="pstable__row-oom-score-adj--width">oom_score_adj</td>
|
||||
<td>name</td>
|
||||
<td></td>
|
||||
<td class="pstable__row-pid--width">pid
|
||||
<a class="pstable__row-sort--width" href="javascript:void(0);"
|
||||
id="pstable_sort_pid" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable('pid')"></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>
|
||||
</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>
|
||||
</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>
|
||||
</thead>
|
||||
<tbody id="process_table">
|
||||
@ -743,6 +789,7 @@ function goBack() {
|
||||
<li>Fix calculation of requested memory in kBytes</li>
|
||||
<li>Fix issue that prevents units from being copied</li>
|
||||
<li>Show additional information in process table</li>
|
||||
<li>Add sorting process table</li>
|
||||
<li>...</li>
|
||||
</ol>
|
||||
|
||||
|
162
OOMAnalyser.py
162
OOMAnalyser.py
@ -13,6 +13,10 @@ DEBUG = False
|
||||
VERSION = "0.4.0 (devel)"
|
||||
"""Version number"""
|
||||
|
||||
# __pragma__ ('skip')
|
||||
js_undefined = 0 # Prevent complaints by optional static checker
|
||||
# __pragma__ ('noskip')
|
||||
|
||||
|
||||
class OOMEntityState(object):
|
||||
"""Simple enum to track the completeness of an OOM block"""
|
||||
@ -49,6 +53,11 @@ def 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):
|
||||
"""Show the error box and add the warning message"""
|
||||
show_notifybox('WARNING', msg)
|
||||
@ -475,7 +484,7 @@ class OOMAnalyser(object):
|
||||
self.results['call_trace'] = call_trace
|
||||
|
||||
# extract process table
|
||||
self.results['_processes'] = {}
|
||||
self.results['_ps'] = {}
|
||||
self.oom_entity.find_text('[ pid ]')
|
||||
for line in self.oom_entity:
|
||||
if not line.startswith('['):
|
||||
@ -485,9 +494,10 @@ class OOMAnalyser(object):
|
||||
match = self.REC_PROCESS_LINE.match(line)
|
||||
if match:
|
||||
details = match.groupdict()
|
||||
details['notes'] = ''
|
||||
pid = details.pop('pid')
|
||||
self.results['_processes'][pid] = {}
|
||||
self.results['_processes'][pid].update(details)
|
||||
self.results['_ps'][pid] = {}
|
||||
self.results['_ps'][pid].update(details)
|
||||
|
||||
def _hex2flags(self, hexvalue, flag_definition):
|
||||
"""\
|
||||
@ -578,23 +588,35 @@ class OOMAnalyser(object):
|
||||
|
||||
def _convert_numeric_process_values_to_integer(self):
|
||||
"""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: ..."
|
||||
# sets item in "for item in ['uid',..." to 0 instead of 'uid'
|
||||
# jsiter is necessary to iterate over ps
|
||||
for pid_str in ps.keys():
|
||||
converted = {}
|
||||
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:
|
||||
converted[item] = int(process[item])
|
||||
except:
|
||||
error('Converting process parameter "{}={}" to integer failed'.format(item, process[item]))
|
||||
|
||||
converted['name'] = process['name']
|
||||
converted['notes'] = process['notes']
|
||||
pid_int = int(pid_str)
|
||||
del ps[pid_str]
|
||||
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):
|
||||
"""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_ramswap_kb'] = self.results['system_total_ram_kb'] + self.results['swap_total_kb']
|
||||
total_rss_pages = 0
|
||||
for pid in self.results['_processes'].keys():
|
||||
total_rss_pages += self.results['_processes'][pid]['rss_pages']
|
||||
for pid in self.results['_ps'].keys():
|
||||
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_used_percent'] = int(100 *
|
||||
@ -688,6 +710,7 @@ class OOMAnalyser(object):
|
||||
"""
|
||||
self._convert_numeric_results_to_integer()
|
||||
self._convert_numeric_process_values_to_integer()
|
||||
self._calc_pstable_values()
|
||||
|
||||
self._determinate_platform_and_distribution()
|
||||
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
|
||||
'''
|
||||
|
||||
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'
|
||||
|
||||
# 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
|
||||
]
|
||||
|
||||
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):
|
||||
self.set_HTML_defaults()
|
||||
self.update_toc()
|
||||
@ -955,18 +1006,14 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
|
||||
new_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']:
|
||||
comment = 'trigger process'
|
||||
css_class = 'class="js-pstable__triggerproc--bgcolor"'
|
||||
# css_class = 'class="js-pstable__killedproc--bgcolor"'
|
||||
elif pid == self.oom_details['killed_proc_pid']:
|
||||
comment = 'killed process'
|
||||
css_class = 'class="js-pstable__killedproc--bgcolor"'
|
||||
else:
|
||||
comment = ''
|
||||
css_class = ''
|
||||
process = self.oom_details['_processes'][pid]
|
||||
process = self.oom_details['_ps'][pid]
|
||||
line = """
|
||||
<tr {}>
|
||||
<td>{}</td>
|
||||
@ -982,11 +1029,32 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
|
||||
</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'],
|
||||
comment)
|
||||
process['notes'])
|
||||
new_table += line
|
||||
|
||||
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):
|
||||
"""Reset the HTML document but don't clean elements"""
|
||||
if clean_oom:
|
||||
@ -1014,6 +1082,11 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
|
||||
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)
|
||||
@ -1231,5 +1304,66 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
|
||||
element.textContent = self.oom.text
|
||||
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()
|
||||
|
Loading…
Reference in New Issue
Block a user