'.format(element.id, element.textContent)
toc_content.innerHTML = new_toc
def update_process_table(self):
"""
Re-create the process table with additional information
"""
new_table = ''
table_content = document.getElementById('process_table')
for pid in self.oom_details['_ps_index']:
if pid == self.oom_details['trigger_proc_pid']:
css_class = 'class="js-pstable__triggerproc--bgcolor"'
elif pid == self.oom_details['killed_proc_pid']:
css_class = 'class="js-pstable__killedproc--bgcolor"'
else:
css_class = ''
process = self.oom_details['_ps'][pid]
line = """
{}
{}
{}
{}
{}
{}
{}
{}
{}
{}
""".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'])
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"""
# TODO Check operator overloading
# Operator overloading (Pragma opov) does not work in this context.
# self.kernel_cfg.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.kernel_cfg.ps_table_items[:]
ps_table_and_notes.append('notes')
for column_name in ps_table_and_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):
"""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')
# 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 hidden rows
for element in document.querySelectorAll('table .js-text--display-none'):
element.classList.remove('js-text--display-none')
# clear notification box
element = document.getElementById('notify_box')
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)
def svg_create_element(self, height, width, css_class):
"""Return an empty SVG element"""
svg = document.createElementNS(self.svg_namespace, 'svg')
svg.setAttribute('version', '1.1')
svg.setAttribute('height', height)
svg.setAttribute('width', width)
svg.setAttribute('viewBox', '0 0 {} {}'.format(width, height))
svg.setAttribute('class', css_class)
return svg
def svg_create_rect(self, x=0, y=0, width=0, height=0, colour=None):
rect = document.createElementNS(self.svg_namespace, 'rect')
if x:
rect.setAttribute('x', x)
if y:
rect.setAttribute('y', y)
if width:
rect.setAttribute('width', width)
if height:
rect.setAttribute('height', height)
if colour:
rect.setAttribute('fill', colour)
return rect
def svg_generate_bar_chart(self, css_class, *elements):
"""Generate a SVG bar chart"""
bar_height = 100
label_height = 80
length_factor = 4
overall_height = bar_height + label_height
overall_width = 100 * length_factor
css_class = 'js-mem-usage__svg'
svg = self.svg_create_element(overall_height, overall_width, css_class)
sum_all_elements = sum([length for unused, length in elements])
current_pos = 0
bar_group = document.createElementNS(self.svg_namespace, 'g')
bar_group.setAttribute('id', 'bar_group')
bar_group.setAttribute('stroke', 'black')
bar_group.setAttribute('stroke-width', 2)
nr_processed_elements = 0
for title, length in elements:
# length is None/undefined is the regular expression doesn't find any values
if not length:
continue
rect_len = int(100 * length / sum_all_elements) * length_factor
if not rect_len:
continue
colour = self.svg_colours[nr_processed_elements % len(self.svg_colours)]
rect = self.svg_create_rect(current_pos, 0, rect_len, bar_height, colour)
bar_group.appendChild(rect)
label_group = document.createElementNS(self.svg_namespace, 'g')
label_group.setAttribute('id', title)
colour_rect = self.svg_create_rect(0, 0, 20, 20, colour)
colour_rect.setAttribute('stroke', 'black')
colour_rect.setAttribute('stroke-width', 2)
text = document.createElementNS(self.svg_namespace, 'text')
text.setAttribute('x', '30')
text.setAttribute('y', '18')
text.textContent = title
label_group.appendChild(colour_rect)
label_group.appendChild(text)
# TODO replace hardcoded values
x = 5 + 125 * (nr_processed_elements // 2)
y = bar_height + 10 + (nr_processed_elements % 2) * 40
label_group.setAttribute('transform', 'translate({}, {})'.format(x, y))
bar_group.appendChild(label_group)
current_pos += rect_len
nr_processed_elements += 1
svg.appendChild(bar_group)
return svg
def copy_example_to_form(self):
document.getElementById('textarea_oom').value = self.example
def reset_form(self):
self.set_HTML_defaults()
self.update_toc()
def toggle_oom(self, show=False):
"""Toggle the visibility of the full OOM message"""
oom_element = document.getElementById('oom')
row_with_oom = oom_element.parentNode.parentNode
toggle_msg = document.getElementById('oom_toogle_msg')
if show or row_with_oom.classList.contains('js-text--display-none'):
row_with_oom.classList.remove('js-text--display-none')
toggle_msg.text = "(click to hide)"
else:
row_with_oom.classList.add('js-text--display-none')
toggle_msg.text = "(click to show)"
def analyse_and_show(self):
"""Analyse the OOM text inserted into the form and show the results"""
self.oom = OOMEntity(self.load_from_form())
if not self.is_valid(self.oom):
self.oom = None
return
# set defaults and clear notifications
self.oom_details.clear()
self.set_HTML_defaults()
# analyse
analyser = OOMAnalyser(self.oom)
self.oom_details = analyser.analyse()
# Update kernel configuration
self.kernel_cfg = analyser.kernel_cfg
# display results
self.show()
self.update_toc()
def load_from_form(self):
element = document.getElementById('textarea_oom')
oom_text = element.value
return oom_text
def is_valid(self, oom):
"""
Return True for a complete OOM otherwise False and a warning msg for a incomplete or an error msg
if the start sequence was not found.
"""
if oom.state == OOMEntityState.complete:
return True
elif oom.state == OOMEntityState.started:
warning('The inserted OOM is incomplete!')
warning('The initial pattern was found but not the final. The result may be incomplete!')
elif oom.state == OOMEntityState.invalid:
error('The inserted text is not a valid OOM block!')
error('The initial pattern was not found!')
elif oom.state == OOMEntityState.empty:
error('The inserted text is empty! Please insert an OOM message block.')
else:
error('Invalid state "{}" after the OOM has formally checked!'.format(self.oom.state))
return False
def show(self):
"""
Show all extracted details as well as additionally generated information
"""
if DEBUG:
print(self.oom_details)
hide_element('input')
show_element('analysis')
for item in self.oom_details.keys():
# ignore internal items
if item.startswith('_'):
continue
self._set_item(item)
# generate process table
self.update_process_table()
# generate swap usage diagram
svg_swap = self.svg_generate_bar_chart(
('Swap Used', self.oom_details['swap_used_kb']),
('Swap Free', self.oom_details['swap_free_kb']),
('Swap Cached', self.oom_details['swap_cache_kb']),
)
elem_svg_swap = document.getElementById('svg_swap')
elem_svg_swap.appendChild(svg_swap)
# generate RAM usage diagram
svg_ram = self.svg_generate_bar_chart(
('Active mem', self.oom_details['active_anon_pages']),
('Inactive mem', self.oom_details['inactive_anon_pages']),
('Isolated mem', self.oom_details['isolated_anon_pages']),
('Active PC', self.oom_details['active_file_pages']),
('Inactive PC', self.oom_details['inactive_file_pages']),
('Isolated PC', self.oom_details['isolated_file_pages']),
('Unevictable', self.oom_details['unevictable_pages']),
('Dirty', self.oom_details['dirty_pages']),
('Writeback', self.oom_details['writeback_pages']),
('Unstable', self.oom_details['unstable_pages']),
('Slab reclaimable', self.oom_details['slab_reclaimable_pages']),
('Slab unreclaimable', self.oom_details['slab_unreclaimable_pages']),
('Mapped', self.oom_details['mapped_pages']),
('Shared', self.oom_details['shmem_pages']),
('Pagetable', self.oom_details['pagetables_pages']),
('Bounce', self.oom_details['bounce_pages']),
('Free', self.oom_details['free_pages']),
('Free PCP', self.oom_details['free_pcp_pages']),
('Free CMA', self.oom_details['free_cma_pages']),
)
elem_svg_ram = document.getElementById('svg_ram')
elem_svg_ram.appendChild(svg_ram)
element = document.getElementById('oom')
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"""
# TODO Check operator overloading
# Operator overloading (Pragma opov) does not work in this context.
# self.kernel_cfg.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.kernel_cfg.ps_table_items[:]
ps_table_and_notes.append('notes')
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:
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()