diff --git a/OOMAnalyser.py b/OOMAnalyser.py
index dda6729..b30c912 100644
--- a/OOMAnalyser.py
+++ b/OOMAnalyser.py
@@ -20,7 +20,6 @@ js_undefined = 0
class classList:
-
def add(self, *args, **kwargs):
pass
@@ -29,8 +28,10 @@ class classList:
class document:
-
- def querySelectorAll(self, *args,):
+ def querySelectorAll(
+ self,
+ *args,
+ ):
return [Node()]
@staticmethod
@@ -90,11 +91,14 @@ class Node:
def setAttribute(self, *args, **kwargs):
return
+
+
# __pragma__ ('noskip')
class OOMEntityState:
"""Enum for completeness of the OOM block"""
+
unknown = 0
empty = 1
invalid = 2
@@ -104,6 +108,7 @@ class OOMEntityState:
class OOMEntityType:
"""Enum for the type of the OOM"""
+
unknown = 0
automatic = 1
manual = 2
@@ -116,31 +121,31 @@ def is_visible(element):
def hide_element(element_id):
"""Hide the given HTML element"""
element = document.getElementById(element_id)
- element.classList.add('js-text--display-none')
+ element.classList.add("js-text--display-none")
def show_element(element_id):
"""Show the given HTML element"""
element = document.getElementById(element_id)
- element.classList.remove('js-text--display-none')
+ element.classList.remove("js-text--display-none")
def hide_elements(selector):
"""Hide all matching elements by adding class js-text--display-none"""
for element in document.querySelectorAll(selector):
- element.classList.add('js-text--display-none')
+ element.classList.add("js-text--display-none")
def show_elements(selector):
"""Show all matching elements by removing class js-text--display-none"""
for element in document.querySelectorAll(selector):
- element.classList.remove('js-text--display-none')
+ element.classList.remove("js-text--display-none")
def toggle(element_id):
"""Toggle the visibility of the given HTML element"""
element = document.getElementById(element_id)
- element.classList.toggle('js-text--display-none')
+ element.classList.toggle("js-text--display-none")
def escape_html(unsafe):
@@ -150,46 +155,48 @@ def escape_html(unsafe):
@type unsafe: str
@rtype: str
"""
- return unsafe.replace('&', "&")\
- .replace('<', "<")\
- .replace('>', ">")\
- .replace('"', """)\
+ return (
+ unsafe.replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace('"', """)
.replace("'", "'")
+ )
def error(msg):
"""Show the error box and add the error message"""
- 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)
+ show_notifybox("INTERNAL ERROR", msg)
def warning(msg):
"""Show the error box and add the warning message"""
- show_notifybox('WARNING', msg)
+ show_notifybox("WARNING", msg)
def show_notifybox(prefix, msg):
"""Show escaped message in the notification box"""
- if prefix == 'WARNING':
- css_class = 'js-notify_box__msg--warning'
+ if prefix == "WARNING":
+ css_class = "js-notify_box__msg--warning"
else:
- css_class = 'js-notify_box__msg--error'
- show_element('notify_box')
- notify_box = document.getElementById('notify_box')
- notification = document.createElement('div')
+ css_class = "js-notify_box__msg--error"
+ show_element("notify_box")
+ notify_box = document.getElementById("notify_box")
+ notification = document.createElement("div")
notification.classList.add(css_class)
- notification.innerHTML = '{}: {}
'.format(prefix, escape_html(msg))
+ notification.innerHTML = "{}: {}
".format(prefix, escape_html(msg))
notify_box.appendChild(notification)
class BaseKernelConfig:
"""Base class for all kernel specific configuration"""
- name = 'Base configuration for all kernels'
+ name = "Base configuration for all kernels"
"""Name/description of this kernel configuration"""
EXTRACT_PATTERN = None
@@ -203,99 +210,94 @@ class BaseKernelConfig:
"""
EXTRACT_PATTERN_BASE = {
- 'invoked oom-killer': (
- r'^(?P[\S ]+) invoked oom-killer: '
- r'gfp_mask=(?P0x[a-z0-9]+)(\((?P[A-Z_|]+)\))?, '
- r'(nodemask=(?P([\d,-]+|\(null\))), )?'
- r'order=(?P-?\d+), '
- r'oom_score_adj=(?P\d+)',
+ "invoked oom-killer": (
+ r"^(?P[\S ]+) invoked oom-killer: "
+ r"gfp_mask=(?P0x[a-z0-9]+)(\((?P[A-Z_|]+)\))?, "
+ r"(nodemask=(?P([\d,-]+|\(null\))), )?"
+ r"order=(?P-?\d+), "
+ r"oom_score_adj=(?P\d+)",
True,
),
- 'Trigger process and kernel version': (
- r'^CPU: \d+ PID: (?P\d+) '
- r'Comm: .* (Not tainted|Tainted:.*) '
- r'(?P\d[\w.-]+) #\d',
+ "Trigger process and kernel version": (
+ r"^CPU: \d+ PID: (?P\d+) "
+ r"Comm: .* (Not tainted|Tainted:.*) "
+ r"(?P\d[\w.-]+) #\d",
True,
),
-
# split caused by a limited number of iterations during converting PY regex into JS regex
- 'Mem-Info (part 1)': (
- r'^Mem-Info:.*'
- r'(?:\n)'
-
+ "Mem-Info (part 1)": (
+ r"^Mem-Info:.*" r"(?:\n)"
# first line (starting w/o a space)
- r'^active_anon:(?P\d+) inactive_anon:(?P\d+) '
- r'isolated_anon:(?P\d+)'
- r'(?:\n)'
-
+ r"^active_anon:(?P\d+) inactive_anon:(?P\d+) "
+ r"isolated_anon:(?P\d+)"
+ r"(?:\n)"
# remaining lines (w/ leading space)
- r'^ active_file:(?P\d+) inactive_file:(?P\d+) '
- r'isolated_file:(?P\d+)'
- r'(?:\n)'
-
- r'^ unevictable:(?P\d+) dirty:(?P\d+) writeback:(?P\d+) '
- r'unstable:(?P\d+)',
+ r"^ active_file:(?P\d+) inactive_file:(?P\d+) "
+ r"isolated_file:(?P\d+)"
+ r"(?:\n)"
+ r"^ unevictable:(?P\d+) dirty:(?P\d+) writeback:(?P\d+) "
+ r"unstable:(?P\d+)",
True,
),
- 'Mem-Info (part 2)': (
- r'^ slab_reclaimable:(?P\d+) slab_unreclaimable:(?P\d+)'
- r'(?:\n)'
- r'^ mapped:(?P\d+) shmem:(?P\d+) pagetables:(?P\d+) '
- r'bounce:(?P\d+)'
- r'(?:\n)'
- r'^ free:(?P\d+) free_pcp:(?P\d+) free_cma:(?P\d+)',
+ "Mem-Info (part 2)": (
+ r"^ slab_reclaimable:(?P\d+) slab_unreclaimable:(?P\d+)"
+ r"(?:\n)"
+ r"^ mapped:(?P\d+) shmem:(?P\d+) pagetables:(?P\d+) "
+ r"bounce:(?P\d+)"
+ r"(?:\n)"
+ r"^ free:(?P\d+) free_pcp:(?P\d+) free_cma:(?P\d+)",
True,
),
- 'Memory node information': (
- r'(^Node \d+ (DMA|Normal|hugepages).*(:?\n))+',
+ "Memory node information": (
+ r"(^Node \d+ (DMA|Normal|hugepages).*(:?\n))+",
False,
),
- 'Page cache': (
- r'^(?P\d+) total pagecache pages.*$',
+ "Page cache": (
+ r"^(?P\d+) total pagecache pages.*$",
True,
),
- 'Swap usage information': (
- r'^(?P\d+) pages in swap cache'
- r'(?:\n)'
- r'^Swap cache stats: add \d+, delete \d+, find \d+\/\d+'
- r'(?:\n)'
- r'^Free swap = (?P\d+)kB'
- r'(?:\n)'
- r'^Total swap = (?P\d+)kB',
+ "Swap usage information": (
+ r"^(?P\d+) pages in swap cache"
+ r"(?:\n)"
+ r"^Swap cache stats: add \d+, delete \d+, find \d+\/\d+"
+ r"(?:\n)"
+ r"^Free swap = (?P\d+)kB"
+ r"(?:\n)"
+ r"^Total swap = (?P\d+)kB",
False,
),
- 'Page information': (
- r'^(?P\d+) pages RAM'
- r'('
- r'(?:\n)'
- r'^(?P\d+) pages HighMem/MovableOnly'
- r')?'
- r'(?:\n)'
- r'^(?P\d+) pages reserved'
- r'('
- r'(?:\n)'
- r'^(?P\d+) pages cma reserved'
- r')?'
- r'('
- r'(?:\n)'
- r'^(?P\d+) pages in pagetable cache'
- r')?'
- r'('
- r'(?:\n)'
- r'^(?P\d+) pages hwpoisoned'
- r')?',
+ "Page information": (
+ r"^(?P\d+) pages RAM"
+ r"("
+ r"(?:\n)"
+ r"^(?P\d+) pages HighMem/MovableOnly"
+ r")?"
+ r"(?:\n)"
+ r"^(?P\d+) pages reserved"
+ r"("
+ r"(?:\n)"
+ r"^(?P\d+) pages cma reserved"
+ r")?"
+ r"("
+ r"(?:\n)"
+ r"^(?P\d+) pages in pagetable cache"
+ r")?"
+ r"("
+ r"(?:\n)"
+ r"^(?P\d+) pages hwpoisoned"
+ r")?",
True,
),
- 'Process killed by OOM': (
- r'^Out of memory: Kill process (?P\d+) \((?P[\S ]+)\) '
- r'score (?P\d+) or sacrifice child',
+ "Process killed by OOM": (
+ r"^Out of memory: Kill process (?P\d+) \((?P[\S ]+)\) "
+ r"score (?P\d+) or sacrifice child",
True,
),
- 'Details of process killed by OOM': (
- r'^Killed process \d+ \(.*\)'
- r'(, UID \d+,)?'
- r' total-vm:(?P\d+)kB, anon-rss:(?P\d+)kB, '
- r'file-rss:(?P\d+)kB, shmem-rss:(?P\d+)kB.*',
+ "Details of process killed by OOM": (
+ r"^Killed process \d+ \(.*\)"
+ r"(, UID \d+,)?"
+ r" total-vm:(?P\d+)kB, anon-rss:(?P\d+)kB, "
+ r"file-rss:(?P\d+)kB, shmem-rss:(?P\d+)kB.*",
True,
),
}
@@ -319,45 +321,47 @@ class BaseKernelConfig:
"""
GFP_FLAGS = {
- 'GFP_ATOMIC': {'value': '__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM'},
- 'GFP_KERNEL': {'value': '__GFP_RECLAIM | __GFP_IO | __GFP_FS'},
- 'GFP_KERNEL_ACCOUNT': {'value': 'GFP_KERNEL | __GFP_ACCOUNT'},
- 'GFP_NOWAIT': {'value': '__GFP_KSWAPD_RECLAIM'},
- 'GFP_NOIO': {'value': '__GFP_RECLAIM'},
- 'GFP_NOFS': {'value': '__GFP_RECLAIM | __GFP_IO'},
- 'GFP_USER': {'value': '__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL'},
- 'GFP_DMA': {'value': '__GFP_DMA'},
- 'GFP_DMA32': {'value': '__GFP_DMA32'},
- 'GFP_HIGHUSER': {'value': 'GFP_USER | __GFP_HIGHMEM'},
- 'GFP_HIGHUSER_MOVABLE': {'value': 'GFP_HIGHUSER | __GFP_MOVABLE'},
- 'GFP_TRANSHUGE_LIGHT': {'value': 'GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM'},
- 'GFP_TRANSHUGE': {'value': 'GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM'},
- '__GFP_DMA': {'value': 0x01},
- '__GFP_HIGHMEM': {'value': 0x02},
- '__GFP_DMA32': {'value': 0x04},
- '__GFP_MOVABLE': {'value': 0x08},
- '__GFP_RECLAIMABLE': {'value': 0x10},
- '__GFP_HIGH': {'value': 0x20},
- '__GFP_IO': {'value': 0x40},
- '__GFP_FS': {'value': 0x80},
- '__GFP_COLD': {'value': 0x100},
- '__GFP_NOWARN': {'value': 0x200},
- '__GFP_RETRY_MAYFAIL': {'value': 0x400},
- '__GFP_NOFAIL': {'value': 0x800},
- '__GFP_NORETRY': {'value': 0x1000},
- '__GFP_MEMALLOC': {'value': 0x2000},
- '__GFP_COMP': {'value': 0x4000},
- '__GFP_ZERO': {'value': 0x8000},
- '__GFP_NOMEMALLOC': {'value': 0x10000},
- '__GFP_HARDWALL': {'value': 0x20000},
- '__GFP_THISNODE': {'value': 0x40000},
- '__GFP_ATOMIC': {'value': 0x80000},
- '__GFP_ACCOUNT': {'value': 0x100000},
- '__GFP_DIRECT_RECLAIM': {'value': 0x400000},
- '__GFP_WRITE': {'value': 0x800000},
- '__GFP_KSWAPD_RECLAIM': {'value': 0x1000000},
- '__GFP_NOLOCKDEP': {'value': 0x2000000},
- '__GFP_RECLAIM': {'value': '__GFP_DIRECT_RECLAIM|__GFP_KSWAPD_RECLAIM'},
+ "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"},
+ "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"},
+ "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"},
+ "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"},
+ "GFP_NOIO": {"value": "__GFP_RECLAIM"},
+ "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"},
+ "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"},
+ "GFP_DMA": {"value": "__GFP_DMA"},
+ "GFP_DMA32": {"value": "__GFP_DMA32"},
+ "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"},
+ "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"},
+ "GFP_TRANSHUGE_LIGHT": {
+ "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM"
+ },
+ "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"},
+ "__GFP_DMA": {"value": 0x01},
+ "__GFP_HIGHMEM": {"value": 0x02},
+ "__GFP_DMA32": {"value": 0x04},
+ "__GFP_MOVABLE": {"value": 0x08},
+ "__GFP_RECLAIMABLE": {"value": 0x10},
+ "__GFP_HIGH": {"value": 0x20},
+ "__GFP_IO": {"value": 0x40},
+ "__GFP_FS": {"value": 0x80},
+ "__GFP_COLD": {"value": 0x100},
+ "__GFP_NOWARN": {"value": 0x200},
+ "__GFP_RETRY_MAYFAIL": {"value": 0x400},
+ "__GFP_NOFAIL": {"value": 0x800},
+ "__GFP_NORETRY": {"value": 0x1000},
+ "__GFP_MEMALLOC": {"value": 0x2000},
+ "__GFP_COMP": {"value": 0x4000},
+ "__GFP_ZERO": {"value": 0x8000},
+ "__GFP_NOMEMALLOC": {"value": 0x10000},
+ "__GFP_HARDWALL": {"value": 0x20000},
+ "__GFP_THISNODE": {"value": 0x40000},
+ "__GFP_ATOMIC": {"value": 0x80000},
+ "__GFP_ACCOUNT": {"value": 0x100000},
+ "__GFP_DIRECT_RECLAIM": {"value": 0x400000},
+ "__GFP_WRITE": {"value": 0x800000},
+ "__GFP_KSWAPD_RECLAIM": {"value": 0x1000000},
+ "__GFP_NOLOCKDEP": {"value": 0x2000000},
+ "__GFP_RECLAIM": {"value": "__GFP_DIRECT_RECLAIM|__GFP_KSWAPD_RECLAIM"},
}
"""
Definition of GFP flags
@@ -373,38 +377,59 @@ class BaseKernelConfig:
(see https://github.com/torvalds/linux/commit/e67d4ca79aaf9d13a00d229b1b1c96b86828e8ba#diff-020720d0699e3ae1afb6fcd815ca8500)
"""
- pstable_items = ['pid', 'uid', 'tgid', 'total_vm_pages', 'rss_pages', 'nr_ptes_pages', 'swapents_pages',
- 'oom_score_adj', 'name', 'notes']
+ 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']
+ 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']
+ pstable_non_ints = ["pid", "name", "notes"]
"""Columns that are not converted to an integer"""
REC_PROCESS_LINE = re.compile(
- r'^\[(?P[ \d]+)\]\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+'
- r'(?P\d+)\s+(?P\d+)\s+(?P-?\d+)\s+(?P.+)\s*')
+ r"^\[(?P[ \d]+)\]\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+"
+ r"(?P\d+)\s+(?P\d+)\s+(?P-?\d+)\s+(?P.+)\s*"
+ )
"""Match content of process table"""
- pstable_start = '[ pid ]'
+ pstable_start = "[ pid ]"
"""
Pattern to find the start of the process table
:type: str
"""
- rec_version4kconfig = re.compile('.+')
+ rec_version4kconfig = re.compile(".+")
"""RE to match kernel version to kernel configuration"""
- rec_oom_begin = re.compile(r'invoked oom-killer:', re.MULTILINE)
+ rec_oom_begin = re.compile(r"invoked oom-killer:", re.MULTILINE)
"""RE to match the first line of an OOM block"""
- rec_oom_end = re.compile(r'^Killed process \d+', re.MULTILINE)
+ rec_oom_end = re.compile(r"^Killed process \d+", re.MULTILINE)
"""RE to match the last line of an OOM block"""
def __init__(self):
@@ -425,12 +450,14 @@ class KernelConfig_4_6(BaseKernelConfig):
# Support changes:
# * "mm, oom_reaper: report success/failure" (bc448e897b6d24aae32701763b8a1fe15d29fa26)
- name = 'Configuration for Linux kernel 4.6 or later'
- rec_version4kconfig = re.compile(r'^4\.([6-9]\.|[12][0-9]\.).+')
+ name = "Configuration for Linux kernel 4.6 or later"
+ rec_version4kconfig = re.compile(r"^4\.([6-9]\.|[12][0-9]\.).+")
# The "oom_reaper" line is optionally
- rec_oom_end = re.compile(r'^((Out of memory.*|Memory cgroup out of memory): Killed process \d+|oom_reaper:)',
- re.MULTILINE)
+ rec_oom_end = re.compile(
+ r"^((Out of memory.*|Memory cgroup out of memory): Killed process \d+|oom_reaper:)",
+ re.MULTILINE,
+ )
def __init__(self):
super().__init__()
@@ -440,15 +467,15 @@ class KernelConfig_4_9(KernelConfig_4_6):
# Support changes:
# * "mm: oom: deduplicate victim selection code for memcg and global oom" (7c5f64f84483bd13886348edda8b3e7b799a7fdb)
- name = 'Configuration for Linux kernel 4.9 or later'
- rec_version4kconfig = re.compile(r'^4\.([9]\.|[12][0-9]\.).+')
+ name = "Configuration for Linux kernel 4.9 or later"
+ rec_version4kconfig = re.compile(r"^4\.([9]\.|[12][0-9]\.).+")
EXTRACT_PATTERN_OVERLAY_49 = {
- 'Details of process killed by OOM': (
- r'^(Out of memory.*|Memory cgroup out of memory): Killed process \d+ \(.*\)'
- r'(, UID \d+,)?'
- r' total-vm:(?P\d+)kB, anon-rss:(?P\d+)kB, '
- r'file-rss:(?P\d+)kB, shmem-rss:(?P\d+)kB.*',
+ "Details of process killed by OOM": (
+ r"^(Out of memory.*|Memory cgroup out of memory): Killed process \d+ \(.*\)"
+ r"(, UID \d+,)?"
+ r" total-vm:(?P\d+)kB, anon-rss:(?P\d+)kB, "
+ r"file-rss:(?P\d+)kB, shmem-rss:(?P\d+)kB.*",
True,
),
}
@@ -466,39 +493,59 @@ class KernelConfig_4_15(KernelConfig_4_9):
# 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[ \d]+)\]\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+'
- r'(?P\d+)\s+(?P\d+)\s+(?P-?\d+)\s+(?P.+)\s*')
+ r"^\[(?P[ \d]+)\]\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+"
+ r"(?P\d+)\s+(?P\d+)\s+(?P-?\d+)\s+(?P.+)\s*"
+ )
- pstable_items = ['pid', 'uid', 'tgid', 'total_vm_pages', 'rss_pages', 'pgtables_bytes', 'swapents_pages',
- 'oom_score_adj', 'name', 'notes']
+ 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']
+ 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 ]'
+ pstable_start = "[ pid ]"
class KernelConfig_5_0(KernelConfig_4_19):
# Support changes:
# * "mm, oom: reorganize the oom report in dump_header" (ef8444ea01d7442652f8e1b8a8b94278cb57eafd)
- name = 'Configuration for Linux kernel 5.0 or later'
- rec_version4kconfig = re.compile(r'^[5-9]\..+')
+ name = "Configuration for Linux kernel 5.0 or later"
+ rec_version4kconfig = re.compile(r"^[5-9]\..+")
EXTRACT_PATTERN_OVERLAY_50 = {
# third last line - not integrated yet
# oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/,task=sed,pid=29481,uid=12345
-
- 'Process killed by OOM': (
- r'^Out of memory: Killed process (?P\d+) \((?P[\S ]+)\) '
- r'total-vm:(?P\d+)kB, anon-rss:(?P\d+)kB, '
- r'file-rss:(?P\d+)kB, shmem-rss:(?P\d+)kB, '
- r'UID:\d+ pgtables:(?P\d+)kB oom_score_adj:(?P\d+)',
+ "Process killed by OOM": (
+ r"^Out of memory: Killed process (?P\d+) \((?P[\S ]+)\) "
+ r"total-vm:(?P\d+)kB, anon-rss:(?P\d+)kB, "
+ r"file-rss:(?P\d+)kB, shmem-rss:(?P\d+)kB, "
+ r"UID:\d+ pgtables:(?P\d+)kB oom_score_adj:(?P\d+)",
True,
),
}
@@ -512,26 +559,22 @@ class KernelConfig_5_8(KernelConfig_5_0):
# Support changes:
# * "mm/writeback: discard NR_UNSTABLE_NFS, use NR_WRITEBACK instead" (8d92890bd6b8502d6aee4b37430ae6444ade7a8c)
- name = 'Configuration for Linux kernel 5.8 or later'
+ name = "Configuration for Linux kernel 5.8 or later"
- rec_version4kconfig = re.compile(r'^(5\.[8-9]\.|5\.[1-9][0-9]\.|[6-9]\.).+')
+ rec_version4kconfig = re.compile(r"^(5\.[8-9]\.|5\.[1-9][0-9]\.|[6-9]\.).+")
EXTRACT_PATTERN_OVERLAY_58 = {
- 'Mem-Info (part 1)': (
- r'^Mem-Info:.*'
- r'(?:\n)'
-
+ "Mem-Info (part 1)": (
+ r"^Mem-Info:.*" r"(?:\n)"
# first line (starting w/o a space)
- r'^active_anon:(?P\d+) inactive_anon:(?P\d+) '
- r'isolated_anon:(?P\d+)'
- r'(?:\n)'
-
+ r"^active_anon:(?P\d+) inactive_anon:(?P\d+) "
+ r"isolated_anon:(?P\d+)"
+ r"(?:\n)"
# remaining lines (w/ leading space)
- r'^ active_file:(?P\d+) inactive_file:(?P\d+) '
- r'isolated_file:(?P\d+)'
- r'(?:\n)'
-
- r'^ unevictable:(?P\d+) dirty:(?P\d+) writeback:(?P\d+)',
+ r"^ active_file:(?P\d+) inactive_file:(?P\d+) "
+ r"isolated_file:(?P\d+)"
+ r"(?:\n)"
+ r"^ unevictable:(?P\d+) dirty:(?P\d+) writeback:(?P\d+)",
True,
),
}
@@ -544,9 +587,9 @@ class KernelConfig_5_8(KernelConfig_5_0):
class KernelConfigRhel7(BaseKernelConfig):
"""RHEL7 / CentOS7 specific configuration"""
- name = 'Configuration for RHEL7 / CentOS7 specific Linux kernel (3.10)'
+ name = "Configuration for RHEL7 / CentOS7 specific Linux kernel (3.10)"
- rec_version4kconfig = re.compile(r'^3\..+')
+ rec_version4kconfig = re.compile(r"^3\..+")
def __init__(self):
super().__init__()
@@ -588,9 +631,9 @@ class OOMEntity:
def __init__(self, text):
# use Unix LF only
- text = text.replace('\r\n', '\n')
+ text = text.replace("\r\n", "\n")
text = text.strip()
- oom_lines = text.split('\n')
+ oom_lines = text.split("\n")
self.current_line = 0
self.lines = oom_lines
@@ -600,21 +643,25 @@ class OOMEntity:
if not text:
self.state = OOMEntityState.empty
return
- elif 'invoked oom-killer:' not in text:
+ elif "invoked oom-killer:" not in text:
self.state = OOMEntityState.invalid
return
oom_lines = self._remove_non_oom_lines(oom_lines)
oom_lines = self._remove_kernel_colon(oom_lines)
- cols_to_strip = self._number_of_columns_to_strip(oom_lines[self._get_CPU_index(oom_lines)])
- oom_lines = self._journalctl_add_leading_columns_to_meminfo(oom_lines, cols_to_strip)
+ cols_to_strip = self._number_of_columns_to_strip(
+ oom_lines[self._get_CPU_index(oom_lines)]
+ )
+ oom_lines = self._journalctl_add_leading_columns_to_meminfo(
+ oom_lines, cols_to_strip
+ )
oom_lines = self._strip_needless_columns(oom_lines, cols_to_strip)
oom_lines = self._rsyslog_unescape_lf(oom_lines)
self.lines = oom_lines
- self.text = '\n'.join(oom_lines)
+ self.text = "\n".join(oom_lines)
- if 'Killed process' in text:
+ if "Killed process" in text:
self.state = OOMEntityState.complete
else:
self.state = OOMEntityState.started
@@ -631,7 +678,7 @@ class OOMEntity:
@see: _rsyslog_unescape_lf()
"""
- pattern = r'^\s+ (active_file|unevictable|slab_reclaimable|mapped|free):.+$'
+ pattern = r"^\s+ (active_file|unevictable|slab_reclaimable|mapped|free):.+$"
rec = re.compile(pattern)
add_cols = ""
@@ -654,7 +701,7 @@ class OOMEntity:
Depending on the OOM version the "CPU: " pattern is in second or third oom line.
"""
for i in range(len(lines)):
- if 'CPU: ' in lines[i]:
+ if "CPU: " in lines[i]:
return i
return 0
@@ -675,7 +722,7 @@ class OOMEntity:
# Apr 01 14:13:32 mysrv kernel: [11686.888109] CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1
try:
# strip all excl. "CPU:"
- if 'CPU:' in line:
+ if "CPU:" in line:
to_strip = columns.index("CPU:")
except ValueError:
pass
@@ -699,13 +746,13 @@ class OOMEntity:
# OOM blocks ends with the second last only or both lines
# Out of memory: Killed process ...
# oom_reaper: reaped process ...
- if 'Killed process' in line:
+ if "Killed process" in line:
killed_process = True
continue
# next line after "Killed process \d+ ..."
if killed_process:
- if 'oom_reaper' in line:
+ if "oom_reaper" in line:
break
else:
# remove this line
@@ -731,8 +778,8 @@ class OOMEntity:
lines = []
for line in oom_lines:
- if '#012' in line:
- lines.extend(line.split('#012'))
+ if "#012" in line:
+ lines.extend(line.split("#012"))
else:
lines.append(line)
@@ -745,7 +792,7 @@ class OOMEntity:
Some OOM messages don't have a space between "kernel:" and the process name. _strip_needless_columns() will
fail in such cases. Therefore the pattern is removed.
"""
- oom_lines = [i.replace('kernel:', '') for i in oom_lines]
+ oom_lines = [i.replace("kernel:", "") for i in oom_lines]
return oom_lines
def _strip_needless_columns(self, oom_lines, cols_to_strip=0):
@@ -888,13 +935,13 @@ class OOMAnalyser:
@rtype: bool
"""
- pattern = r'CPU: \d+ PID: \d+ Comm: .* (Not tainted|Tainted: [A-Z ]+) (?P\d[\w.-]+) #.+'
+ pattern = r"CPU: \d+ PID: \d+ Comm: .* (Not tainted|Tainted: [A-Z ]+) (?P\d[\w.-]+) #.+"
rec = re.compile(pattern, re.MULTILINE)
match = rec.search(self.oom_entity.text)
if not match:
- self.oom_result.error_msg = 'Failed to extract kernel version from OOM text'
+ self.oom_result.error_msg = "Failed to extract kernel version from OOM text"
return False
- self.oom_result.kversion = match.group('kernel_version')
+ self.oom_result.kversion = match.group("kernel_version")
return True
def _choose_kernel_config(self):
@@ -910,7 +957,11 @@ class OOMAnalyser:
break
if not self.oom_result.kconfig:
- warning('Failed to find a proper configuration for kernel "{}"'.format(self.oom_result.kversion))
+ warning(
+ 'Failed to find a proper configuration for kernel "{}"'.format(
+ self.oom_result.kversion
+ )
+ )
self.oom_result.kconfig = BaseKernelConfig()
return True
@@ -922,7 +973,9 @@ class OOMAnalyser:
"""
if not self.oom_entity.text:
self.state = OOMEntityState.empty
- self.oom_result.error_msg = 'Empty OOM text. Please insert an OOM message block.'
+ self.oom_result.error_msg = (
+ "Empty OOM text. Please insert an OOM message block."
+ )
return False
return True
@@ -933,17 +986,19 @@ class OOMAnalyser:
@rtype: bool
"""
self.oom_state = OOMEntityState.unknown
- self.oom_result.error_msg = 'Unknown OOM format'
+ self.oom_result.error_msg = "Unknown OOM format"
if not self.oom_result.kconfig.rec_oom_begin.search(self.oom_entity.text):
self.state = OOMEntityState.invalid
- self.oom_result.error_msg = 'The inserted text is not a valid OOM block! The initial pattern was not found!'
+ self.oom_result.error_msg = "The inserted text is not a valid OOM block! The initial pattern was not found!"
return False
if not self.oom_result.kconfig.rec_oom_end.search(self.oom_entity.text):
self.state = OOMEntityState.started
- self.oom_result.error_msg = 'The inserted OOM is incomplete! The initial pattern was found but not the '\
- 'final.'
+ self.oom_result.error_msg = (
+ "The inserted OOM is incomplete! The initial pattern was found but not the "
+ "final."
+ )
return False
self.state = OOMEntityState.complete
@@ -955,14 +1010,14 @@ class OOMAnalyser:
Extract a block that starts with the marker and contains all lines up to the next line with ":".
:rtype: str
"""
- block = ''
+ block = ""
if not self.oom_entity.find_text(marker):
return block
line = self.oom_entity.current()
block += "{}\n".format(line)
for line in self.oom_entity:
- if ':' in line:
+ if ":" in line:
self.oom_entity.back()
break
block += "{}\n".format(line)
@@ -980,44 +1035,50 @@ class OOMAnalyser:
if match:
self.oom_result.details.update(match.groupdict())
elif is_mandatory:
- error('Failed to extract information from OOM text. The regular expression "{}" (pattern "{}") '
- 'does not find anything. This can lead to errors later on.'.format(k, pattern))
+ error(
+ 'Failed to extract information from OOM text. The regular expression "{}" (pattern "{}") '
+ "does not find anything. This can lead to errors later on.".format(
+ k, pattern
+ )
+ )
# __pragma__ ('nojsiter')
- if self.oom_result.details['trigger_proc_order'] == "-1":
+ if self.oom_result.details["trigger_proc_order"] == "-1":
self.oom_result.oom_type = OOMEntityType.manual
else:
self.oom_result.oom_type = OOMEntityType.automatic
- self.oom_result.details['hardware_info'] = self._extract_block_from_next_pos('Hardware name:')
+ self.oom_result.details["hardware_info"] = self._extract_block_from_next_pos(
+ "Hardware name:"
+ )
# strip "Call Trace" line at beginning and remove leading spaces
- call_trace = ''
- block = self._extract_block_from_next_pos('Call Trace:')
- for line in block.split('\n'):
- if line.startswith('Call Trace'):
+ call_trace = ""
+ block = self._extract_block_from_next_pos("Call Trace:")
+ for line in block.split("\n"):
+ if line.startswith("Call Trace"):
continue
call_trace += "{}\n".format(line.strip())
- self.oom_result.details['call_trace'] = call_trace
+ self.oom_result.details["call_trace"] = call_trace
self._extract_pstable()
def _extract_pstable(self):
"""Extract process table"""
- self.oom_result.details['_pstable'] = {}
+ 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('['):
+ if not line.startswith("["):
break
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['_pstable'][pid] = {}
- self.oom_result.details['_pstable'][pid].update(details)
+ details["notes"] = ""
+ pid = details.pop("pid")
+ self.oom_result.details["_pstable"][pid] = {}
+ self.oom_result.details["_pstable"][pid].update(details)
def _hex2flags(self, hexvalue, flag_definition):
"""\
@@ -1044,14 +1105,14 @@ class OOMAnalyser:
Convert a single flag into a decimal value
"""
if flag not in flag_definition:
- error('No definition for flag {} found'.format(flag))
+ error("No definition for flag {} found".format(flag))
return 0
- value = flag_definition[flag]['value']
+ value = flag_definition[flag]["value"]
if isinstance(value, int):
return value
- tokenlist = iter(re.split('([|&])', value))
+ tokenlist = iter(re.split("([|&])", value))
operator = None
negate_rvalue = False
lvalue = 0
@@ -1061,17 +1122,17 @@ class OOMAnalyser:
except StopIteration:
break
token = token.strip()
- if token in ['|', '&']:
+ if token in ["|", "&"]:
operator = token
continue
- if token.startswith('~'):
+ if token.startswith("~"):
token = token[1:]
negate_rvalue = True
if token.isdigit():
rvalue = int(token)
- elif token.startswith('0x') and token[2:].isdigit():
+ elif token.startswith("0x") and token[2:].isdigit():
rvalue = int(token, 16)
else:
# it's not a decimal nor a hexadecimal value - reiterate assuming it's a flag string
@@ -1080,9 +1141,9 @@ class OOMAnalyser:
if negate_rvalue:
rvalue = ~rvalue
- if operator == '|':
+ if operator == "|":
lvalue |= rvalue
- elif operator == '&':
+ elif operator == "&":
lvalue &= rvalue
operator = None
@@ -1095,19 +1156,29 @@ class OOMAnalyser:
# __pragma__ ('jsiter')
for item in self.oom_result.details:
if self.oom_result.details[item] is None:
- self.oom_result.details[item] = ''
+ self.oom_result.details[item] = ""
continue
- 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']:
+ 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])
except:
- error('Converting item "{}={}" to integer failed'.format(item, self.oom_result.details[item]))
+ error(
+ 'Converting item "{}={}" to integer failed'.format(
+ item, self.oom_result.details[item]
+ )
+ )
# __pragma__ ('nojsiter')
def _convert_pstable_values_to_integer(self):
"""Convert numeric values in process table to integer values"""
- ps = self.oom_result.details['_pstable']
+ 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'
@@ -1122,131 +1193,168 @@ class OOMAnalyser:
converted[item] = int(process[item])
except:
if item not in process:
- pitem = ''
+ pitem = ""
else:
pitem = process[item]
- error('Converting process parameter "{}={}" to integer failed'.format(item, pitem))
+ error(
+ 'Converting process parameter "{}={}" to integer failed'.format(
+ item, pitem
+ )
+ )
- converted['name'] = process['name']
- converted['notes'] = process['notes']
+ 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.oom_result.details['_pstable_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"""
- tpid = self.oom_result.details['trigger_proc_pid']
- kpid = self.oom_result.details['killed_proc_pid']
+ tpid = self.oom_result.details["trigger_proc_pid"]
+ 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['_pstable']:
- self.oom_result.details['_pstable'][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['_pstable']:
- self.oom_result.details['_pstable'][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"""
- self.oom_result.details['trigger_proc_requested_memory_pages'] = 2 ** self.oom_result.details['trigger_proc_order']
- self.oom_result.details['trigger_proc_requested_memory_pages_kb'] = self.oom_result.details['trigger_proc_requested_memory_pages'] * \
- self.oom_result.details['page_size_kb']
+ self.oom_result.details["trigger_proc_requested_memory_pages"] = (
+ 2 ** self.oom_result.details["trigger_proc_order"]
+ )
+ self.oom_result.details["trigger_proc_requested_memory_pages_kb"] = (
+ self.oom_result.details["trigger_proc_requested_memory_pages"]
+ * self.oom_result.details["page_size_kb"]
+ )
# process gfp_mask
- if self.oom_result.details['trigger_proc_gfp_flags'] != '': # None has been is converted to ''
- flags = self.oom_result.details['trigger_proc_gfp_flags']
- del self.oom_result.details['trigger_proc_gfp_flags']
+ if (
+ self.oom_result.details["trigger_proc_gfp_flags"] != ""
+ ): # None has been is converted to ''
+ flags = self.oom_result.details["trigger_proc_gfp_flags"]
+ del self.oom_result.details["trigger_proc_gfp_flags"]
else:
- flags, unknown = self._hex2flags(self.oom_result.details['trigger_proc_gfp_mask'], self.oom_result.kconfig.GFP_FLAGS)
+ flags, unknown = self._hex2flags(
+ self.oom_result.details["trigger_proc_gfp_mask"],
+ self.oom_result.kconfig.GFP_FLAGS,
+ )
if unknown:
- flags.append('0x{0:x}'.format(unknown))
- flags = ' | '.join(flags)
+ flags.append("0x{0:x}".format(unknown))
+ flags = " | ".join(flags)
- self.oom_result.details['trigger_proc_gfp_mask'] = '{} ({})'.format(self.oom_result.details['trigger_proc_gfp_mask'], flags)
+ self.oom_result.details["trigger_proc_gfp_mask"] = "{} ({})".format(
+ self.oom_result.details["trigger_proc_gfp_mask"], flags
+ )
# already fully processed and no own element to display -> delete otherwise an error msg will be shown
- del self.oom_result.details['trigger_proc_gfp_flags']
+ del self.oom_result.details["trigger_proc_gfp_flags"]
def _calc_killed_process_values(self):
"""Calculate all values related with the killed process"""
- self.oom_result.details['killed_proc_total_rss_kb'] = self.oom_result.details['killed_proc_anon_rss_kb'] + \
- self.oom_result.details['killed_proc_file_rss_kb'] + \
- self.oom_result.details['killed_proc_shmem_rss_kb']
+ self.oom_result.details["killed_proc_total_rss_kb"] = (
+ self.oom_result.details["killed_proc_anon_rss_kb"]
+ + self.oom_result.details["killed_proc_file_rss_kb"]
+ + self.oom_result.details["killed_proc_shmem_rss_kb"]
+ )
- self.oom_result.details['killed_proc_rss_percent'] = int(100 *
- self.oom_result.details['killed_proc_total_rss_kb'] /
- int(self.oom_result.details['system_total_ram_kb']))
+ self.oom_result.details["killed_proc_rss_percent"] = int(
+ 100
+ * self.oom_result.details["killed_proc_total_rss_kb"]
+ / int(self.oom_result.details["system_total_ram_kb"])
+ )
def _calc_swap_values(self):
"""Calculate all swap related values"""
try:
- self.oom_result.swap_active = self.oom_result.details['swap_total_kb'] > 0
+ self.oom_result.swap_active = self.oom_result.details["swap_total_kb"] > 0
except KeyError:
self.oom_result.swap_active = False
if not self.oom_result.swap_active:
return
- self.oom_result.details['swap_cache_kb'] = self.oom_result.details['swap_cache_pages'] * self.oom_result.details['page_size_kb']
- del self.oom_result.details['swap_cache_pages']
+ self.oom_result.details["swap_cache_kb"] = (
+ self.oom_result.details["swap_cache_pages"]
+ * self.oom_result.details["page_size_kb"]
+ )
+ del self.oom_result.details["swap_cache_pages"]
# SwapUsed = SwapTotal - SwapFree - SwapCache
- self.oom_result.details['swap_used_kb'] = self.oom_result.details['swap_total_kb'] - self.oom_result.details['swap_free_kb'] - \
- self.oom_result.details['swap_cache_kb']
- self.oom_result.details['system_swap_used_percent'] = int(100 *
- self.oom_result.details['swap_used_kb'] /
- self.oom_result.details['swap_total_kb'])
+ self.oom_result.details["swap_used_kb"] = (
+ self.oom_result.details["swap_total_kb"]
+ - self.oom_result.details["swap_free_kb"]
+ - self.oom_result.details["swap_cache_kb"]
+ )
+ self.oom_result.details["system_swap_used_percent"] = int(
+ 100
+ * self.oom_result.details["swap_used_kb"]
+ / self.oom_result.details["swap_total_kb"]
+ )
def _calc_system_values(self):
"""Calculate system memory"""
# calculate remaining explanation values
- self.oom_result.details['system_total_ram_kb'] = self.oom_result.details['ram_pages'] * \
- self.oom_result.details['page_size_kb']
+ self.oom_result.details["system_total_ram_kb"] = (
+ self.oom_result.details["ram_pages"]
+ * self.oom_result.details["page_size_kb"]
+ )
if self.oom_result.swap_active:
- self.oom_result.details['system_total_ramswap_kb'] = self.oom_result.details['system_total_ram_kb'] + \
- self.oom_result.details['swap_total_kb']
+ self.oom_result.details["system_total_ramswap_kb"] = (
+ self.oom_result.details["system_total_ram_kb"]
+ + self.oom_result.details["swap_total_kb"]
+ )
else:
- self.oom_result.details['system_total_ramswap_kb'] = self.oom_result.details['system_total_ram_kb']
+ self.oom_result.details[
+ "system_total_ramswap_kb"
+ ] = self.oom_result.details["system_total_ram_kb"]
# TODO: Used RSS calculation based on process table is probably incorrect, because it don't differentiates
# between processes and threads
total_rss_pages = 0
- 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']
+ 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 *
- self.oom_result.details['system_total_ram_used_kb'] /
- self.oom_result.details['system_total_ram_kb'])
+ self.oom_result.details["system_total_used_percent"] = int(
+ 100
+ * self.oom_result.details["system_total_ram_used_kb"]
+ / self.oom_result.details["system_total_ram_kb"]
+ )
def _determinate_platform_and_distribution(self):
"""Determinate platform and distribution"""
- kernel_version = self.oom_result.details.get('kernel_version', '')
- if 'x86_64' in kernel_version:
- self.oom_result.details['platform'] = 'x86 64bit'
+ kernel_version = self.oom_result.details.get("kernel_version", "")
+ if "x86_64" in kernel_version:
+ self.oom_result.details["platform"] = "x86 64bit"
else:
- self.oom_result.details['platform'] = 'unknown'
+ self.oom_result.details["platform"] = "unknown"
- dist = 'unknown'
- if '.el7uek' in kernel_version:
- dist = 'Oracle Linux 7 (Unbreakable Enterprise Kernel)'
- elif '.el7' in kernel_version:
- dist = 'RHEL 7/CentOS 7'
- elif '.el6' in kernel_version:
- dist = 'RHEL 6/CentOS 6'
- elif '.el5' in kernel_version:
- dist = 'RHEL 5/CentOS 5'
- elif 'ARCH' in kernel_version:
- dist = 'Arch Linux'
- elif '-generic' in kernel_version:
- dist = 'Ubuntu'
- self.oom_result.details['dist'] = dist
+ dist = "unknown"
+ if ".el7uek" in kernel_version:
+ dist = "Oracle Linux 7 (Unbreakable Enterprise Kernel)"
+ elif ".el7" in kernel_version:
+ dist = "RHEL 7/CentOS 7"
+ elif ".el6" in kernel_version:
+ dist = "RHEL 6/CentOS 6"
+ elif ".el5" in kernel_version:
+ dist = "RHEL 5/CentOS 5"
+ elif "ARCH" in kernel_version:
+ dist = "Arch Linux"
+ elif "-generic" in kernel_version:
+ dist = "Ubuntu"
+ self.oom_result.details["dist"] = dist
# educated guess
- self.oom_result.details['page_size_kb'] = 4
+ self.oom_result.details["page_size_kb"] = 4
def _calc_from_oom_details(self):
"""
@@ -1298,8 +1406,8 @@ class OOMAnalyser:
class SVGChart:
"""
Creates a horizontal stacked bar chart with a legend underneath.
-
- The entries of the legend are arranged from left to right and from top to bottom.
+
+ The entries of the legend are arranged from left to right and from top to bottom.
"""
cfg = dict(
@@ -1310,60 +1418,75 @@ class SVGChart:
legend_margin=7,
title_height=20,
title_margin=10,
- css_class='js-mem-usage__svg', # CSS class for SVG diagram
+ css_class="js-mem-usage__svg", # CSS class for SVG diagram
)
"""Basic chart configuration"""
# generated with Colorgorical http://vrl.cs.brown.edu/color
colors = [
- '#aee39a',
- '#344b46',
- '#1ceaf9',
- '#5d99aa',
- '#32e195',
- '#b02949',
- '#deae9e',
- '#805257',
- '#add51f',
- '#544793',
- '#a794d3',
- '#e057e1',
- '#769b5a',
- '#76f014',
- '#621da6',
- '#ffce54',
- '#d64405',
- '#bb8801',
- '#096013',
- '#ff0087'
+ "#aee39a",
+ "#344b46",
+ "#1ceaf9",
+ "#5d99aa",
+ "#32e195",
+ "#b02949",
+ "#deae9e",
+ "#805257",
+ "#add51f",
+ "#544793",
+ "#a794d3",
+ "#e057e1",
+ "#769b5a",
+ "#76f014",
+ "#621da6",
+ "#ffce54",
+ "#d64405",
+ "#bb8801",
+ "#096013",
+ "#ff0087",
]
"""20 different colors for memory usage diagrams"""
max_entries_per_row = 3
"""Maximum chart legend entries per row"""
- namespace = 'http://www.w3.org/2000/svg'
+ namespace = "http://www.w3.org/2000/svg"
def __init__(self):
super().__init__()
- self.cfg['bar_topleft_x'] = 0
- self.cfg['bar_topleft_y'] = self.cfg['title_height'] + self.cfg['title_margin']
- self.cfg['bar_bottomleft_x'] = self.cfg['bar_topleft_x']
- self.cfg['bar_bottomleft_y'] = self.cfg['bar_topleft_y'] + self.cfg['chart_height']
+ self.cfg["bar_topleft_x"] = 0
+ self.cfg["bar_topleft_y"] = self.cfg["title_height"] + self.cfg["title_margin"]
+ self.cfg["bar_bottomleft_x"] = self.cfg["bar_topleft_x"]
+ self.cfg["bar_bottomleft_y"] = (
+ self.cfg["bar_topleft_y"] + self.cfg["chart_height"]
+ )
- self.cfg['bar_bottomright_x'] = self.cfg['bar_topleft_x'] + self.cfg['chart_width']
- self.cfg['bar_bottomright_y'] = self.cfg['bar_topleft_y'] + self.cfg['chart_height']
+ self.cfg["bar_bottomright_x"] = (
+ self.cfg["bar_topleft_x"] + self.cfg["chart_width"]
+ )
+ self.cfg["bar_bottomright_y"] = (
+ self.cfg["bar_topleft_y"] + self.cfg["chart_height"]
+ )
- self.cfg['legend_topleft_x'] = self.cfg['bar_topleft_x']
- self.cfg['legend_topleft_y'] = self.cfg['bar_topleft_y'] + self.cfg['legend_margin']
- self.cfg['legend_width'] = self.cfg['legend_entry_width'] + self.cfg['legend_margin'] + \
- self.cfg['legend_entry_width']
+ self.cfg["legend_topleft_x"] = self.cfg["bar_topleft_x"]
+ self.cfg["legend_topleft_y"] = (
+ self.cfg["bar_topleft_y"] + self.cfg["legend_margin"]
+ )
+ self.cfg["legend_width"] = (
+ self.cfg["legend_entry_width"]
+ + self.cfg["legend_margin"]
+ + self.cfg["legend_entry_width"]
+ )
- self.cfg['diagram_height'] = self.cfg['chart_height'] + self.cfg['title_margin'] + self.cfg['title_height']
- self.cfg['diagram_width'] = self.cfg['chart_width']
+ self.cfg["diagram_height"] = (
+ self.cfg["chart_height"]
+ + self.cfg["title_margin"]
+ + self.cfg["title_height"]
+ )
+ self.cfg["diagram_width"] = self.cfg["chart_width"]
- self.cfg['title_bottommiddle_y'] = self.cfg['title_height']
- self.cfg['title_bottommiddle_x'] = self.cfg['diagram_width'] // 2
+ self.cfg["title_bottommiddle_y"] = self.cfg["title_height"]
+ self.cfg["title_bottommiddle_x"] = self.cfg["diagram_width"] // 2
# __pragma__ ('kwargs')
def create_element(self, tag, **kwargs):
@@ -1377,10 +1500,11 @@ class SVGChart:
element = document.createElementNS(self.namespace, tag)
# __pragma__ ('jsiter')
for k in kwargs:
- k2 = k.replace('_', '-')
+ k2 = k.replace("_", "-")
element.setAttribute(k2, kwargs[k])
# __pragma__ ('nojsiter')
return element
+
# __pragma__ ('nokwargs')
# __pragma__ ('kwargs')
@@ -1392,17 +1516,23 @@ class SVGChart:
@param str text: Text
@rtype: Node
"""
- element = self.create_element('text', **kwargs)
+ element = self.create_element("text", **kwargs)
element.textContent = text
return element
+
# __pragma__ ('nokwargs')
def create_element_svg(self, height, width, css_class=None):
"""Return a SVG element"""
- svg = self.create_element('svg', version='1.1', height=height, width=width,
- viewBox='0 0 {} {}'.format(width, height))
+ svg = self.create_element(
+ "svg",
+ version="1.1",
+ height=height,
+ width=width,
+ viewBox="0 0 {} {}".format(width, height),
+ )
if css_class:
- svg.setAttribute('class', css_class)
+ svg.setAttribute("class", css_class)
return svg
def create_rectangle(self, x, y, width, height, color=None, title=None):
@@ -1411,12 +1541,12 @@ class SVGChart:
If a title is given, the container also contains a element.
"""
- g = self.create_element('g')
- rect = self.create_element('rect', x=x, y=y, width=width, height=height)
+ g = self.create_element("g")
+ rect = self.create_element("rect", x=x, y=y, width=width, height=height)
if color:
- rect.setAttribute('fill', color)
+ rect.setAttribute("fill", color)
if title:
- t = self.create_element('title')
+ t = self.create_element("title")
t.textContent = title
g.appendChild(t)
g.appendChild(rect)
@@ -1431,17 +1561,17 @@ class SVGChart:
@param int pos: Continuous position
@rtype: Node
"""
- label_group = self.create_element('g', id=desc)
+ label_group = self.create_element("g", id=desc)
color_rect = self.create_rectangle(0, 0, 20, 20, color)
label_group.appendChild(color_rect)
- desc_element = self.create_element_text(desc, x='30', y='18')
+ desc_element = self.create_element_text(desc, x="30", y="18")
desc_element.textContent = desc
label_group.appendChild(desc_element)
# move group to right position
x, y = self.legend_calc_xy(pos)
- label_group.setAttribute('transform', 'translate({}, {})'.format(x, y))
+ label_group.setAttribute("transform", "translate({}, {})".format(x, y))
return label_group
@@ -1472,8 +1602,8 @@ class SVGChart:
@type column: int
@rtype: int
"""
- x = self.cfg['bar_bottomleft_x'] + self.cfg['legend_margin']
- x += column * (self.cfg['legend_margin'] + self.cfg['legend_entry_width'])
+ x = self.cfg["bar_bottomleft_x"] + self.cfg["legend_margin"]
+ x += column * (self.cfg["legend_margin"] + self.cfg["legend_entry_width"])
return x
def legend_calc_y(self, row):
@@ -1483,7 +1613,7 @@ class SVGChart:
@type row: int
@rtype: int
"""
- y = self.cfg['bar_bottomleft_y'] + self.cfg['legend_margin']
+ y = self.cfg["bar_bottomleft_y"] + self.cfg["legend_margin"]
y += row * 40
return y
@@ -1501,9 +1631,9 @@ class SVGChart:
col = pos % self.max_entries_per_row
row = math.floor(pos / self.max_entries_per_row)
- x = self.cfg['bar_bottomleft_x'] + self.cfg['legend_margin']
- y = self.cfg['bar_bottomleft_y'] + self.cfg['legend_margin']
- x += col * (self.cfg['legend_margin'] + self.cfg['legend_entry_width'])
+ x = self.cfg["bar_bottomleft_x"] + self.cfg["legend_margin"]
+ y = self.cfg["bar_bottomleft_y"] + self.cfg["legend_margin"]
+ x += col * (self.cfg["legend_margin"] + self.cfg["legend_entry_width"])
y += row * 40
return x, y
@@ -1514,18 +1644,26 @@ class SVGChart:
@rtype: Node
"""
- bar_group = self.create_element('g', id='bar_group', stroke='black', stroke_width=2)
+ bar_group = self.create_element(
+ "g", id="bar_group", stroke="black", stroke_width=2
+ )
current_x = 0
total_length = sum([length for unused, length in elements])
for i, two in enumerate(elements):
name, length = two
color = self.colors[i % len(self.colors)]
- rect_len = int(length / total_length * self.cfg['chart_width'])
+ rect_len = int(length / total_length * self.cfg["chart_width"])
if rect_len == 0:
rect_len = 1
- rect = self.create_rectangle(current_x, self.cfg['bar_topleft_y'], rect_len, self.cfg['chart_height'],
- color, name)
+ rect = self.create_rectangle(
+ current_x,
+ self.cfg["bar_topleft_y"],
+ rect_len,
+ self.cfg["chart_height"],
+ color,
+ name,
+ )
current_x += rect_len
bar_group.appendChild(rect)
@@ -1537,7 +1675,7 @@ class SVGChart:
@rtype: Node
"""
- legend_group = self.create_element('g', id='legend_group')
+ legend_group = self.create_element("g", id="legend_group")
for i, two in enumerate(elements):
element_name = two[0]
color = self.colors[i % len(self.colors)]
@@ -1545,7 +1683,9 @@ class SVGChart:
legend_group.appendChild(label_group)
# re-calculate chart height after all legend entries added
- self.cfg['diagram_height'] = self.legend_calc_y(self.legend_max_row(len(elements)))
+ self.cfg["diagram_height"] = self.legend_calc_y(
+ self.legend_max_row(len(elements))
+ )
return legend_group
@@ -1560,10 +1700,18 @@ class SVGChart:
filtered_elements = [(name, length) for name, length in elements if length > 0]
bar_group = self.generate_bar_area(filtered_elements)
legend_group = self.generate_legend(filtered_elements)
- svg = self.create_element_svg(self.cfg['diagram_height'], self.cfg['diagram_width'], self.cfg['css_class'])
- chart_title = self.create_element_text(title, font_size=self.cfg['title_height'], font_weight="bold",
- stroke_width='0', text_anchor='middle',
- x=self.cfg['title_bottommiddle_x'], y=self.cfg['title_bottommiddle_y'])
+ svg = self.create_element_svg(
+ self.cfg["diagram_height"], self.cfg["diagram_width"], self.cfg["css_class"]
+ )
+ chart_title = self.create_element_text(
+ title,
+ font_size=self.cfg["title_height"],
+ font_weight="bold",
+ stroke_width="0",
+ text_anchor="middle",
+ x=self.cfg["title_bottommiddle_x"],
+ y=self.cfg["title_bottommiddle_y"],
+ )
svg.appendChild(chart_title)
svg.appendChild(bar_group)
svg.appendChild(legend_group)
@@ -1581,7 +1729,7 @@ class OOMDisplay:
@rtype: OOMResult
"""
- example_rhel7 = u'''\
+ example_rhel7 = """\
sed invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
sed cpuset=/ mems_allowed=0-1
CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1
@@ -1722,9 +1870,9 @@ Total swap = 8388604kB
[29752] 12345 29752 7522 296 19 0 0 rotatelogs
Out of memory: Kill process 6576 (mysqld) score 651 or sacrifice child
Killed process 6576 (mysqld) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0kB, shmem-rss:0kB
-'''
+"""
- example_ubuntu2110 = u'''\
+ example_ubuntu2110 = """\
kworker/0:2 invoked oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=-1, oom_score_adj=0
CPU: 0 PID: 735 Comm: kworker/0:2 Not tainted 5.13.0-19-generic #19-Ubuntu
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS ArchLinux 1.14.0-1 04/01/2014
@@ -1794,7 +1942,7 @@ Tasks state (memory values in pages):
[ 877] 0 877 1899 1052 53248 0 0 bash
oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/unattended-upgrades.service,task=unattended-upgr,pid=651,uid=0
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_number = None
"""
@@ -1832,7 +1980,7 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
self.set_HTML_defaults()
self.update_toc()
- element = document.getElementById('version')
+ element = document.getElementById("version")
element.textContent = "v{}".format(VERSION)
def _set_item(self, item):
@@ -1843,39 +1991,39 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
"""
elements = document.getElementsByClassName(item)
for element in elements:
- content = self.oom_result.details.get(item, '')
+ content = self.oom_result.details.get(item, "")
if isinstance(content, str):
content = content.strip()
- if content == '':
+ if content == "":
row = element.parentNode
- row.classList.add('js-text--display-none')
+ row.classList.add("js-text--display-none")
- if item.endswith('_pages') and isinstance(content, int):
+ if item.endswith("_pages") and isinstance(content, int):
if content == 1:
content = "{} page".format(content)
else:
content = "{} pages".format(content)
- if item.endswith('_bytes') and isinstance(content, int):
+ 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 item.endswith("_kb") and isinstance(content, int):
if content == 1:
content = "{} kByte".format(content)
else:
content = "{} kBytes".format(content)
- if item.endswith('_percent') and isinstance(content, int):
+ if item.endswith("_percent") and isinstance(content, int):
content = "{}%".format(content)
element.textContent = content
if DEBUG:
- show_element('notify_box')
+ show_element("notify_box")
def update_toc(self):
"""
@@ -1885,15 +2033,17 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
* the headline is visible
* the id attribute is set
"""
- new_toc = ''
+ new_toc = ""
- toc_content = document.querySelectorAll('nav > ul')[0]
+ toc_content = document.querySelectorAll("nav > ul")[0]
- for element in document.querySelectorAll('h2'):
+ for element in document.querySelectorAll("h2"):
if not (is_visible(element) and element.id):
continue
- new_toc += '{}'.format(element.id, element.textContent)
+ new_toc += '{}'.format(
+ element.id, element.textContent
+ )
toc_content.innerHTML = new_toc
@@ -1902,34 +2052,45 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
Create the process table with additional information
"""
# 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 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'
+ 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']:
+ 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']:
+ elif pid == self.oom_result.details["killed_proc_pid"]:
css_class = 'class="js-pstable__killedproc--bgcolor"'
else:
- css_class = ''
- process = self.oom_result.details['_pstable'][pid]
- fmt_list = [process[i] for i in self.oom_result.kconfig.pstable_items if not i == 'pid']
+ css_class = ""
+ 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 = """
@@ -1945,7 +2106,9 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
{} |
{} |
- """.format(*fmt_list)
+ """.format(
+ *fmt_list
+ )
new_table += line
table_content.innerHTML = new_table
@@ -1961,7 +2124,7 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
continue
if column_number == self.sorted_column_number:
- if self.sort_order == 'descending':
+ if self.sort_order == "descending":
element.innerHTML = self.svg_array_down
else:
element.innerHTML = self.svg_array_up
@@ -1971,21 +2134,21 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
def set_HTML_defaults(self):
"""Reset the HTML document but don't clean elements"""
# hide all elements marked to be hidden by default
- hide_elements('.js-text--default-hide')
+ hide_elements(".js-text--default-hide")
# show all elements marked to be shown by default
- show_elements('.js-text--default-show')
+ show_elements(".js-text--default-show")
# show hidden rows
- show_elements('table .js-text--display-none')
+ show_elements("table .js-text--display-none")
# clear notification box
- element = document.getElementById('notify_box')
+ element = document.getElementById("notify_box")
while element.firstChild:
element.removeChild(element.firstChild)
# remove svg charts
- for element_id in ('svg_swap', 'svg_ram'):
+ for element_id in ("svg_swap", "svg_ram"):
element = document.getElementById(element_id)
while element.firstChild:
element.removeChild(element.firstChild)
@@ -1994,7 +2157,7 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
def _clear_pstable(self):
"""Clear process table"""
- element = document.getElementById('pstable_content')
+ element = document.getElementById("pstable_content")
while element.firstChild:
element.removeChild(element.firstChild)
@@ -2004,33 +2167,38 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
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')
+ 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 copy_example_rhel7_to_form(self):
- document.getElementById('textarea_oom').value = self.example_rhel7
+ document.getElementById("textarea_oom").value = self.example_rhel7
def copy_example_ubuntu_to_form(self):
- document.getElementById('textarea_oom').value = self.example_ubuntu2110
+ document.getElementById("textarea_oom").value = self.example_ubuntu2110
def reset_form(self):
- document.getElementById('textarea_oom').value = ""
+ document.getElementById("textarea_oom").value = ""
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')
+ oom_element = document.getElementById("oom")
row_with_oom = oom_element.parentNode.parentNode
- toggle_msg = document.getElementById('oom_toogle_msg')
+ 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')
+ 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')
+ row_with_oom.classList.add("js-text--display-none")
toggle_msg.text = "(click to show)"
def analyse_and_show(self):
@@ -2053,10 +2221,10 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
def load_from_form(self):
"""
Return the OOM text from textarea element
-
- @rtype: str
+
+ @rtype: str
"""
- element = document.getElementById('textarea_oom')
+ element = document.getElementById("textarea_oom")
oom_text = element.value
return oom_text
@@ -2064,27 +2232,27 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
"""
Show all extracted details as well as additionally generated information
"""
- hide_element('input')
- show_element('analysis')
+ hide_element("input")
+ show_element("analysis")
if self.oom_result.oom_type == OOMEntityType.manual:
- hide_elements('.js-oom-automatic--show')
- show_elements('.js-oom-manual--show')
+ hide_elements(".js-oom-automatic--show")
+ show_elements(".js-oom-manual--show")
else:
- show_elements('.js-oom-automatic--show')
- hide_elements('.js-oom-manual--show')
+ show_elements(".js-oom-automatic--show")
+ hide_elements(".js-oom-manual--show")
for item in self.oom_result.details.keys():
# ignore internal items
- if item.startswith('_'):
+ if item.startswith("_"):
continue
self._set_item(item)
# Hide "OOM Score" if not available
# since KernelConfig_5_0.EXTRACT_PATTERN_OVERLAY_50['Process killed by OOM']
- if 'killed_proc_score' in self.oom_result.details:
- show_elements('.js-killed-proc-score--show')
+ if "killed_proc_score" in self.oom_result.details:
+ show_elements(".js-killed-proc-score--show")
else:
- hide_elements('.js-killed-proc-score--show')
+ hide_elements(".js-killed-proc-score--show")
# generate process table
self.pstable_fill_HTML()
@@ -2094,48 +2262,53 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
if self.oom_result.swap_active:
# generate swap usage diagram
svg = SVGChart()
- svg_swap = svg.generate_chart('Swap Summary',
- ('Swap Used', self.oom_result.details['swap_used_kb']),
- ('Swap Free', self.oom_result.details['swap_free_kb']),
- ('Swap Cached', self.oom_result.details['swap_cache_kb']))
- elem_svg_swap = document.getElementById('svg_swap')
+ svg_swap = svg.generate_chart(
+ "Swap Summary",
+ ("Swap Used", self.oom_result.details["swap_used_kb"]),
+ ("Swap Free", self.oom_result.details["swap_free_kb"]),
+ ("Swap Cached", self.oom_result.details["swap_cache_kb"]),
+ )
+ elem_svg_swap = document.getElementById("svg_swap")
elem_svg_swap.appendChild(svg_swap)
- show_elements('.js-swap-active--show')
- hide_elements('.js-swap-inactive--show')
+ show_elements(".js-swap-active--show")
+ hide_elements(".js-swap-inactive--show")
else:
- hide_elements('.js-swap-active--show')
- show_elements('.js-swap-inactive--show')
+ hide_elements(".js-swap-active--show")
+ show_elements(".js-swap-inactive--show")
# generate RAM usage diagram
ram_title_attr = (
- ('Active mem', 'active_anon_pages'),
- ('Inactive mem', 'inactive_anon_pages'),
- ('Isolated mem', 'isolated_anon_pages'),
- ('Active PC', 'active_file_pages'),
- ('Inactive PC', 'inactive_file_pages'),
- ('Isolated PC', 'isolated_file_pages'),
- ('Unevictable', 'unevictable_pages'),
- ('Dirty', 'dirty_pages'),
- ('Writeback', 'writeback_pages'),
- ('Unstable', 'unstable_pages'),
- ('Slab reclaimable', 'slab_reclaimable_pages'),
- ('Slab unreclaimable', 'slab_unreclaimable_pages'),
- ('Mapped', 'mapped_pages'),
- ('Shared', 'shmem_pages'),
- ('Pagetable', 'pagetables_pages'),
- ('Bounce', 'bounce_pages'),
- ('Free', 'free_pages'),
- ('Free PCP', 'free_pcp_pages'),
- ('Free CMA', 'free_cma_pages'),
+ ("Active mem", "active_anon_pages"),
+ ("Inactive mem", "inactive_anon_pages"),
+ ("Isolated mem", "isolated_anon_pages"),
+ ("Active PC", "active_file_pages"),
+ ("Inactive PC", "inactive_file_pages"),
+ ("Isolated PC", "isolated_file_pages"),
+ ("Unevictable", "unevictable_pages"),
+ ("Dirty", "dirty_pages"),
+ ("Writeback", "writeback_pages"),
+ ("Unstable", "unstable_pages"),
+ ("Slab reclaimable", "slab_reclaimable_pages"),
+ ("Slab unreclaimable", "slab_unreclaimable_pages"),
+ ("Mapped", "mapped_pages"),
+ ("Shared", "shmem_pages"),
+ ("Pagetable", "pagetables_pages"),
+ ("Bounce", "bounce_pages"),
+ ("Free", "free_pages"),
+ ("Free PCP", "free_pcp_pages"),
+ ("Free CMA", "free_cma_pages"),
)
- chart_elements = [(title, self.oom_result.details[value]) for title, value in ram_title_attr
- if value in self.oom_result.details]
+ chart_elements = [
+ (title, self.oom_result.details[value])
+ for title, value in ram_title_attr
+ if value in self.oom_result.details
+ ]
svg = SVGChart()
- svg_ram = svg.generate_chart('RAM Summary', *chart_elements)
- elem_svg_ram = document.getElementById('svg_ram')
+ svg_ram = svg.generate_chart("RAM Summary", *chart_elements)
+ elem_svg_ram = document.getElementById("svg_ram")
elem_svg_ram.appendChild(svg_ram)
- element = document.getElementById('oom')
+ element = document.getElementById("oom")
element.textContent = self.oom_result.oom_text
self.toggle_oom(show=False)
@@ -2151,10 +2324,14 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
# "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.pstable_items[:]
- ps_table_and_notes.append('notes')
+ 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))
+ internal_error(
+ 'Can not sort process table with an unknown column name "{}"'.format(
+ column_name
+ )
+ )
return
# reset sort order if the column has changes
@@ -2162,11 +2339,11 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
self.sort_order = None
self.sorted_column_number = column_number
- if not self.sort_order or self.sort_order == 'descending':
- self.sort_order = 'ascending'
+ 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_order = "descending"
self.sort_psindex_by_column(column_name, True)
self.pstable_fill_HTML()
@@ -2178,16 +2355,19 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
Is uses bubble sort with all disadvantages but just a few lines of code
"""
- ps = self.oom_result.details['_pstable']
- ps_index = self.oom_result.details['_pstable_index']
+ ps = self.oom_result.details["_pstable"]
+ ps_index = self.oom_result.details["_pstable_index"]
def getvalue(column, pos):
- if column == 'pid':
+ if column == "pid":
value = ps_index[pos]
else:
value = ps[ps_index[pos]][column]
# JS sorts alphanumeric by default, convert values explicit to integers to sort numerically
- if column not in self.oom_result.kconfig.pstable_non_ints 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
@@ -2198,11 +2378,11 @@ Out of memory: Killed process 651 (unattended-upgr) total-vm:108020kB, anon-rss:
for i in range(len(ps_index) - 1):
v1 = getvalue(column_name, i)
- v2 = getvalue(column_name, i+1)
+ 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]
+ 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
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..d140219
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[tool.black]
+line-length = 88
+target-version = ['py37']
diff --git a/test.py b/test.py
index 0412baf..3c595e3 100755
--- a/test.py
+++ b/test.py
@@ -21,7 +21,6 @@ import OOMAnalyser
class MyRequestHandler(http.server.SimpleHTTPRequestHandler):
-
def __init__(self, request, client_address, server, directory=None):
self.directory = os.getcwd()
super().__init__(request, client_address, server)
@@ -37,7 +36,6 @@ class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
class TestBase(unittest.TestCase):
-
def get_lines(self, text, count):
"""
Return the number of lines specified by count from given text
@@ -50,7 +48,7 @@ class TestBase(unittest.TestCase):
lines.reverse()
count = count * -1
lines = lines[:count]
- res = '\n'.join(lines)
+ res = "\n".join(lines)
return res
def get_first_line(self, text):
@@ -75,18 +73,18 @@ class TestInBrowser(TestBase):
warnings.simplefilter("ignore", ResourceWarning)
ThreadedTCPServer.allow_reuse_address = True
- self.httpd = ThreadedTCPServer(('127.0.0.1', 8000), MyRequestHandler)
+ self.httpd = ThreadedTCPServer(("127.0.0.1", 8000), MyRequestHandler)
server_thread = threading.Thread(target=self.httpd.serve_forever, args=(0.1,))
server_thread.daemon = True
server_thread.start()
# silent Webdriver Manager
- os.environ['WDM_LOG_LEVEL'] = '0'
+ os.environ["WDM_LOG_LEVEL"] = "0"
# store driver locally
- os.environ['WDM_LOCAL'] = '1'
+ os.environ["WDM_LOCAL"] = "1"
- s=Service(ChromeDriverManager().install())
+ s = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=s)
self.driver.get("http://127.0.0.1:8000/OOMAnalyser.html")
@@ -96,9 +94,11 @@ class TestInBrowser(TestBase):
self.httpd.server_close()
def assert_on_warn(self):
- notify_box = self.driver.find_element(By.ID, 'notify_box')
+ notify_box = self.driver.find_element(By.ID, "notify_box")
try:
- warning = notify_box.find_element(By.CLASS_NAME, 'js-notify_box__msg--warning')
+ warning = notify_box.find_element(
+ By.CLASS_NAME, "js-notify_box__msg--warning"
+ )
except NoSuchElementException:
pass
else:
@@ -109,9 +109,9 @@ class TestInBrowser(TestBase):
if error:
self.fail('Unexpected error message: "%s"' % error)
- for event in self.driver.get_log('browser'):
+ for event in self.driver.get_log("browser"):
# ignore favicon.ico errors
- if 'favicon.ico' in event['message']:
+ if "favicon.ico" in event["message"]:
continue
self.fail('Error on browser console reported: "%s"' % event)
@@ -129,9 +129,9 @@ class TestInBrowser(TestBase):
@rtype: str
"""
- notify_box = self.driver.find_element(By.ID, 'notify_box')
+ notify_box = self.driver.find_element(By.ID, "notify_box")
try:
- notify_box.find_element(By.CLASS_NAME, 'js-notify_box__msg--error')
+ notify_box.find_element(By.CLASS_NAME, "js-notify_box__msg--error")
except NoSuchElementException:
return ""
return notify_box.text
@@ -141,7 +141,9 @@ class TestInBrowser(TestBase):
if reset.is_displayed():
reset.click()
else:
- new_analysis = self.driver.find_element(By.XPATH, '//a[contains(text(), "Step 1 - Enter your OOM message")]')
+ new_analysis = self.driver.find_element(
+ By.XPATH, '//a[contains(text(), "Step 1 - Enter your OOM message")]'
+ )
new_analysis.click()
self.assert_on_warn_error()
@@ -151,14 +153,19 @@ class TestInBrowser(TestBase):
:param str text: OOM text to analyse
"""
- textarea = self.driver.find_element(By.ID, 'textarea_oom')
- self.assertEqual(textarea.get_attribute('value'), '', 'Empty textarea expected')
+ textarea = self.driver.find_element(By.ID, "textarea_oom")
+ self.assertEqual(textarea.get_attribute("value"), "", "Empty textarea expected")
textarea.send_keys(text)
- self.assertNotEqual(textarea.get_attribute('value'), '', 'Missing OOM text in textarea')
+ self.assertNotEqual(
+ textarea.get_attribute("value"), "", "Missing OOM text in textarea"
+ )
h3_summary = self.driver.find_element(By.XPATH, '//h3[text()="Summary"]')
- self.assertFalse(h3_summary.is_displayed(), "Analysis details incl. Summary
should be not displayed")
+ self.assertFalse(
+ h3_summary.is_displayed(),
+ "Analysis details incl. Summary
should be not displayed",
+ )
self.click_analyse()
@@ -166,89 +173,144 @@ class TestInBrowser(TestBase):
"""Check the results of the analysis of the RHEL7 example"""
self.assert_on_warn_error()
h3_summary = self.driver.find_element(By.XPATH, '//h3[text()="Summary"]')
- self.assertTrue(h3_summary.is_displayed(), "Analysis details incl. Summary
should be displayed")
+ self.assertTrue(
+ h3_summary.is_displayed(),
+ "Analysis details incl. Summary
should be displayed",
+ )
- trigger_proc_name = self.driver.find_element(By.CLASS_NAME, 'trigger_proc_name')
- self.assertEqual(trigger_proc_name.text, 'sed', 'Unexpected trigger process name')
- trigger_proc_pid = self.driver.find_element(By.CLASS_NAME, 'trigger_proc_pid')
- self.assertEqual(trigger_proc_pid.text, '29481', 'Unexpected trigger process pid')
- trigger_proc_gfp_mask = self.driver.find_element(By.CLASS_NAME, 'trigger_proc_gfp_mask')
- self.assertEqual(trigger_proc_gfp_mask.text,
- '0x201da (GFP_KERNEL | GFP_USER | GFP_HIGHUSER | '
- 'GFP_HIGHUSER_MOVABLE | __GFP_RECLAIMABLE | __GFP_COLD)',
- 'Unexpected GFP Mask')
+ trigger_proc_name = self.driver.find_element(By.CLASS_NAME, "trigger_proc_name")
+ self.assertEqual(
+ trigger_proc_name.text, "sed", "Unexpected trigger process name"
+ )
+ trigger_proc_pid = self.driver.find_element(By.CLASS_NAME, "trigger_proc_pid")
+ self.assertEqual(
+ trigger_proc_pid.text, "29481", "Unexpected trigger process pid"
+ )
+ trigger_proc_gfp_mask = self.driver.find_element(
+ By.CLASS_NAME, "trigger_proc_gfp_mask"
+ )
+ self.assertEqual(
+ trigger_proc_gfp_mask.text,
+ "0x201da (GFP_KERNEL | GFP_USER | GFP_HIGHUSER | "
+ "GFP_HIGHUSER_MOVABLE | __GFP_RECLAIMABLE | __GFP_COLD)",
+ "Unexpected GFP Mask",
+ )
- killed_proc_score = self.driver.find_element(By.CLASS_NAME, 'killed_proc_score')
- self.assertEqual(killed_proc_score.text, '651', 'Unexpected OOM score of killed process')
+ killed_proc_score = self.driver.find_element(By.CLASS_NAME, "killed_proc_score")
+ self.assertEqual(
+ killed_proc_score.text, "651", "Unexpected OOM score of killed process"
+ )
- swap_cache_kb = self.driver.find_element(By.CLASS_NAME, 'swap_cache_kb')
- self.assertEqual(swap_cache_kb.text, '45368 kBytes')
- swap_used_kb = self.driver.find_element(By.CLASS_NAME, 'swap_used_kb')
- self.assertEqual(swap_used_kb.text, '8343236 kBytes')
- swap_free_kb = self.driver.find_element(By.CLASS_NAME, 'swap_free_kb')
- self.assertEqual(swap_free_kb.text, '0 kBytes')
- swap_total_kb = self.driver.find_element(By.CLASS_NAME, 'swap_total_kb')
- self.assertEqual(swap_total_kb.text, '8388604 kBytes')
+ swap_cache_kb = self.driver.find_element(By.CLASS_NAME, "swap_cache_kb")
+ self.assertEqual(swap_cache_kb.text, "45368 kBytes")
+ swap_used_kb = self.driver.find_element(By.CLASS_NAME, "swap_used_kb")
+ self.assertEqual(swap_used_kb.text, "8343236 kBytes")
+ swap_free_kb = self.driver.find_element(By.CLASS_NAME, "swap_free_kb")
+ self.assertEqual(swap_free_kb.text, "0 kBytes")
+ swap_total_kb = self.driver.find_element(By.CLASS_NAME, "swap_total_kb")
+ self.assertEqual(swap_total_kb.text, "8388604 kBytes")
- explanation = self.driver.find_element(By.ID, 'explanation')
- self.assertTrue('OOM killer was automatically triggered' in explanation.text,
- 'Missing text "OOM killer was automatically triggered"')
+ explanation = self.driver.find_element(By.ID, "explanation")
+ self.assertTrue(
+ "OOM killer was automatically triggered" in explanation.text,
+ 'Missing text "OOM killer was automatically triggered"',
+ )
- explanation = self.driver.find_element(By.ID, 'explanation')
- self.assertTrue("system has 33519336 kBytes physical memory and 8388604 kBytes swap space." in explanation.text,
- "Physical and swap memory in summary not found")
- self.assertTrue("That's 41907940 kBytes total." in explanation.text,
- "Total memory in summary not found")
- self.assertTrue("94% (31705788 kBytes out of 33519336 kBytes) physical memory" in explanation.text,
- "Used physical memory in summary not found")
- self.assertTrue("99% (8343236 kBytes out of 8388604 kBytes) swap space" in explanation.text,
- "Used swap space in summary not found")
+ explanation = self.driver.find_element(By.ID, "explanation")
+ self.assertTrue(
+ "system has 33519336 kBytes physical memory and 8388604 kBytes swap space."
+ in explanation.text,
+ "Physical and swap memory in summary not found",
+ )
+ self.assertTrue(
+ "That's 41907940 kBytes total." in explanation.text,
+ "Total memory in summary not found",
+ )
+ self.assertTrue(
+ "94% (31705788 kBytes out of 33519336 kBytes) physical memory"
+ in explanation.text,
+ "Used physical memory in summary not found",
+ )
+ self.assertTrue(
+ "99% (8343236 kBytes out of 8388604 kBytes) swap space" in explanation.text,
+ "Used swap space in summary not found",
+ )
- head = self.driver.find_element(By.ID, 'pstable_header')
- self.assertTrue('Page Table Entries' in head.text, 'Missing column head line "Page Table Entries"')
+ 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):
"""Check the results of the analysis of the Ubuntu example"""
- trigger_proc_gfp_mask = self.driver.find_element(By.CLASS_NAME, 'trigger_proc_gfp_mask')
- self.assertEqual(trigger_proc_gfp_mask.text, '0xcc0 (GFP_KERNEL)', 'Unexpected GFP Mask')
+ trigger_proc_gfp_mask = self.driver.find_element(
+ By.CLASS_NAME, "trigger_proc_gfp_mask"
+ )
+ self.assertEqual(
+ trigger_proc_gfp_mask.text, "0xcc0 (GFP_KERNEL)", "Unexpected GFP Mask"
+ )
- dirty_pages = self.driver.find_element(By.CLASS_NAME, 'dirty_pages')
- self.assertEqual(dirty_pages.text, '633 pages', 'Unexpected number of dirty pages')
+ dirty_pages = self.driver.find_element(By.CLASS_NAME, "dirty_pages")
+ self.assertEqual(
+ dirty_pages.text, "633 pages", "Unexpected number of dirty pages"
+ )
- ram_pages = self.driver.find_element(By.CLASS_NAME, 'ram_pages')
- self.assertEqual(ram_pages.text, '524158 pages', 'Unexpected number of RAM pages')
+ ram_pages = self.driver.find_element(By.CLASS_NAME, "ram_pages")
+ self.assertEqual(
+ ram_pages.text, "524158 pages", "Unexpected number of RAM pages"
+ )
- explanation = self.driver.find_element(By.ID, 'explanation')
- self.assertTrue('OOM killer was manually triggered' in explanation.text,
- 'Missing text "OOM killer was manually triggered"')
+ explanation = self.driver.find_element(By.ID, "explanation")
+ self.assertTrue(
+ "OOM killer was manually triggered" in explanation.text,
+ 'Missing text "OOM killer was manually triggered"',
+ )
- self.assertFalse('with an OOM score of' in explanation.text,
- 'No OOM score but text "with an OOM score of"')
+ self.assertFalse(
+ "with an OOM score of" in explanation.text,
+ 'No OOM score but text "with an OOM score of"',
+ )
- explanation = self.driver.find_element(By.ID, 'explanation')
- self.assertTrue("system has 2096632 kBytes physical memory and no swap space" in explanation.text,
- "Physical and swap memory in summary not found")
- self.assertTrue("9% (209520 kBytes out of 2096632 kBytes) physical memory" in explanation.text,
- "Used physical memory in summary not found")
+ explanation = self.driver.find_element(By.ID, "explanation")
+ self.assertTrue(
+ "system has 2096632 kBytes physical memory and no swap space"
+ in explanation.text,
+ "Physical and swap memory in summary not found",
+ )
+ self.assertTrue(
+ "9% (209520 kBytes out of 2096632 kBytes) physical memory"
+ in explanation.text,
+ "Used physical memory in summary not found",
+ )
- head = self.driver.find_element(By.ID, 'pstable_header')
- self.assertTrue('Page Table Bytes' in head.text, 'Missing column head line "Page Table Bytes"')
+ 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):
- explanation = self.driver.find_element(By.ID, 'explanation')
- self.assertTrue('physical memory and no swap space' in explanation.text,
- 'Missing text "physical memory and no swap space"')
- self.assertFalse('swap space are in use' in explanation.text,
- 'No swap space but text "swap space are in use"')
+ explanation = self.driver.find_element(By.ID, "explanation")
+ self.assertTrue(
+ "physical memory and no swap space" in explanation.text,
+ 'Missing text "physical memory and no swap space"',
+ )
+ self.assertFalse(
+ "swap space are in use" in explanation.text,
+ 'No swap space but text "swap space are in use"',
+ )
def check_swap_active(self):
- explanation = self.driver.find_element(By.ID, 'explanation')
- self.assertTrue('swap space are in use' in explanation.text,
- 'Swap space active but no text "swap space are in use"')
+ explanation = self.driver.find_element(By.ID, "explanation")
+ self.assertTrue(
+ "swap space are in use" in explanation.text,
+ 'Swap space active but no text "swap space are in use"',
+ )
def test_010_load_page(self):
"""Test if the page is loading"""
@@ -261,45 +323,69 @@ class TestInBrowser(TestBase):
def test_030_insert_and_analyse_rhel7_example(self):
"""Test loading and analysing RHEL7 example"""
- textarea = self.driver.find_element(By.ID, 'textarea_oom')
- self.assertEqual(textarea.get_attribute('value'), '', 'Empty textarea expected')
- insert_example = self.driver.find_element(By.XPATH, '//button[contains(text(), "RHEL7" )]')
+ textarea = self.driver.find_element(By.ID, "textarea_oom")
+ self.assertEqual(textarea.get_attribute("value"), "", "Empty textarea expected")
+ insert_example = self.driver.find_element(
+ By.XPATH, '//button[contains(text(), "RHEL7" )]'
+ )
insert_example.click()
- self.assertNotEqual(textarea.get_attribute('value'), '', 'Missing OOM text in textarea')
+ self.assertNotEqual(
+ textarea.get_attribute("value"), "", "Missing OOM text in textarea"
+ )
h3_summary = self.driver.find_element(By.XPATH, '//h3[text()="Summary"]')
- self.assertFalse(h3_summary.is_displayed(), "Analysis details incl. Summary
should be not displayed")
+ self.assertFalse(
+ h3_summary.is_displayed(),
+ "Analysis details incl. Summary
should be not displayed",
+ )
self.click_analyse()
self.check_results_rhel7()
def test_031_insert_and_analyse_ubuntu_example(self):
"""Test loading and analysing Ubuntu 21.10 example"""
- textarea = self.driver.find_element(By.ID, 'textarea_oom')
- self.assertEqual(textarea.get_attribute('value'), '', 'Empty textarea expected')
- insert_example = self.driver.find_element(By.XPATH, '//button[contains(text(), "Ubuntu" )]')
+ textarea = self.driver.find_element(By.ID, "textarea_oom")
+ self.assertEqual(textarea.get_attribute("value"), "", "Empty textarea expected")
+ insert_example = self.driver.find_element(
+ By.XPATH, '//button[contains(text(), "Ubuntu" )]'
+ )
insert_example.click()
- self.assertNotEqual(textarea.get_attribute('value'), '', 'Missing OOM text in textarea')
+ self.assertNotEqual(
+ textarea.get_attribute("value"), "", "Missing OOM text in textarea"
+ )
h3_summary = self.driver.find_element(By.XPATH, '//h3[text()="Summary"]')
- self.assertFalse(h3_summary.is_displayed(), "Analysis details incl. Summary
should be not displayed")
+ self.assertFalse(
+ h3_summary.is_displayed(),
+ "Analysis details incl. Summary
should be not displayed",
+ )
self.click_analyse()
self.check_results_ubuntu2110()
def test_032_empty_textarea(self):
"""Test "Analyse" with empty textarea"""
- textarea = self.driver.find_element(By.ID, 'textarea_oom')
- self.assertEqual(textarea.get_attribute('value'), '', 'Empty textarea expected')
+ textarea = self.driver.find_element(By.ID, "textarea_oom")
+ self.assertEqual(textarea.get_attribute("value"), "", "Empty textarea expected")
# textarea.send_keys(text)
- self.assertEqual(textarea.get_attribute('value'), '', 'Expected empty text area, but text found')
+ self.assertEqual(
+ textarea.get_attribute("value"),
+ "",
+ "Expected empty text area, but text found",
+ )
h3_summary = self.driver.find_element(By.XPATH, '//h3[text()="Summary"]')
- self.assertFalse(h3_summary.is_displayed(), "Analysis details incl. Summary
should be not displayed")
+ self.assertFalse(
+ h3_summary.is_displayed(),
+ "Analysis details incl. Summary
should be not displayed",
+ )
self.click_analyse()
- self.assertEqual(self.get_error_text(), "ERROR: Empty OOM text. Please insert an OOM message block.")
+ self.assertEqual(
+ self.get_error_text(),
+ "ERROR: Empty OOM text. Please insert an OOM message block.",
+ )
self.click_reset()
def test_033_begin_but_no_end(self):
@@ -310,8 +396,11 @@ sed cpuset=/ mems_allowed=0-1
CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1
"""
self.analyse_oom(example)
- self.assertEqual(self.get_error_text(), "ERROR: The inserted OOM is incomplete! The initial pattern was "
- "found but not the final.")
+ self.assertEqual(
+ self.get_error_text(),
+ "ERROR: The inserted OOM is incomplete! The initial pattern was "
+ "found but not the final.",
+ )
self.click_reset()
def test_034_no_begin_but_end(self):
@@ -321,26 +410,29 @@ 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
"""
self.analyse_oom(example)
- self.assertEqual(self.get_error_text(), "ERROR: Failed to extract kernel version from OOM text")
+ self.assertEqual(
+ self.get_error_text(),
+ "ERROR: Failed to extract kernel version from OOM text",
+ )
self.click_reset()
def test_035_leading_journalctl_input(self):
- """Test loading input from journalctl """
+ """Test loading input from journalctl"""
# prepare example
- example_lines = OOMAnalyser.OOMDisplay.example_rhel7.split('\n')
+ example_lines = OOMAnalyser.OOMDisplay.example_rhel7.split("\n")
res = []
# unescape #012 - see OOMAnalyser.OOMEntity._rsyslog_unescape_lf()
for line in example_lines:
- if '#012' in line:
- res.extend(line.split('#012'))
+ if "#012" in line:
+ res.extend(line.split("#012"))
else:
res.append(line)
example_lines = res
res = []
# add date/time prefix except for "Mem-Info:" block
- pattern = r'^ (active_file|unevictable|slab_reclaimable|mapped|free):.+$'
+ pattern = r"^ (active_file|unevictable|slab_reclaimable|mapped|free):.+$"
rec = re.compile(pattern)
for line in example_lines:
match = rec.search(line)
@@ -358,37 +450,44 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
def test_040_trigger_proc_space(self):
"""Test trigger process name contains a space"""
example = OOMAnalyser.OOMDisplay.example_rhel7
- example = example.replace('sed', 'VM Monitoring Task')
+ example = example.replace("sed", "VM Monitoring Task")
self.analyse_oom(example)
self.assert_on_warn_error()
h3_summary = self.driver.find_element(By.XPATH, '//h3[text()="Summary"]')
- self.assertTrue(h3_summary.is_displayed(), "Analysis details incl. Summary
should be displayed")
+ self.assertTrue(
+ h3_summary.is_displayed(),
+ "Analysis details incl. Summary
should be displayed",
+ )
def test_050_kill_proc_space(self):
"""Test killed process name contains a space"""
example = OOMAnalyser.OOMDisplay.example_rhel7
- example = example.replace('mysqld', 'VM Monitoring Task')
+ example = example.replace("mysqld", "VM Monitoring Task")
self.analyse_oom(example)
self.assert_on_warn_error()
h3_summary = self.driver.find_element(By.XPATH, '//h3[text()="Summary"]')
- self.assertTrue(h3_summary.is_displayed(), "Analysis details incl. Summary
should be displayed")
+ self.assertTrue(
+ h3_summary.is_displayed(),
+ "Analysis details incl. Summary
should be displayed",
+ )
def test_060_removal_of_leading_but_useless_columns(self):
"""Test removal of leading but useless columns"""
self.analyse_oom(OOMAnalyser.OOMDisplay.example_rhel7)
self.check_results_rhel7()
self.click_reset()
- for prefix in ["[11686.888109] ",
- "Apr 01 14:13:32 mysrv: ",
- "Apr 01 14:13:32 mysrv kernel: ",
- "Apr 01 14:13:32 mysrv kernel: ",
- "Apr 01 14:13:32 mysrv kernel: [11686.888109] ",
- "kernel:",
- "Apr 01 14:13:32 mysrv kernel:",
- ]:
- lines = OOMAnalyser.OOMDisplay.example_rhel7.split('\n')
+ for prefix in [
+ "[11686.888109] ",
+ "Apr 01 14:13:32 mysrv: ",
+ "Apr 01 14:13:32 mysrv kernel: ",
+ "Apr 01 14:13:32 mysrv kernel: ",
+ "Apr 01 14:13:32 mysrv kernel: [11686.888109] ",
+ "kernel:",
+ "Apr 01 14:13:32 mysrv kernel:",
+ ]:
+ lines = OOMAnalyser.OOMDisplay.example_rhel7.split("\n")
lines = ["{}{}".format(prefix, line) for line in lines]
oom_text = "\n".join(lines)
self.analyse_oom(oom_text)
@@ -399,18 +498,20 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
def test_070_manually_triggered_OOM(self):
"""Test for manually triggered OOM"""
example = OOMAnalyser.OOMDisplay.example_rhel7
- example = example.replace('order=0', 'order=-1')
+ example = example.replace("order=0", "order=-1")
self.analyse_oom(example)
self.assert_on_warn_error()
- explanation = self.driver.find_element(By.ID, 'explanation')
- self.assertTrue('OOM killer was manually triggered' in explanation.text,
- 'Missing text "OOM killer was manually triggered"')
+ explanation = self.driver.find_element(By.ID, "explanation")
+ self.assertTrue(
+ "OOM killer was manually triggered" in explanation.text,
+ 'Missing text "OOM killer was manually triggered"',
+ )
def test_080_swap_deactivated(self):
"""Test w/o swap or with deactivated swap"""
example = OOMAnalyser.OOMDisplay.example_rhel7
- example = example.replace('Total swap = 8388604kB', 'Total swap = 0kB')
+ example = example.replace("Total swap = 8388604kB", "Total swap = 0kB")
self.analyse_oom(example)
self.assert_on_warn_error()
@@ -418,10 +519,10 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
self.click_reset()
example = OOMAnalyser.OOMDisplay.example_rhel7
- example = re.sub(r'\d+ pages in swap cac.*\n*', '', example, re.MULTILINE)
- example = re.sub(r'Swap cache stats.*\n*', '', example)
- example = re.sub(r'Free swap.*\n*', '', example)
- example = re.sub(r'Total swap.*\n*', '', example)
+ example = re.sub(r"\d+ pages in swap cac.*\n*", "", example, re.MULTILINE)
+ example = re.sub(r"Swap cache stats.*\n*", "", example)
+ example = re.sub(r"Free swap.*\n*", "", example)
+ example = re.sub(r"Total swap.*\n*", "", example)
self.analyse_oom(example)
self.assert_on_warn_error()
@@ -429,54 +530,82 @@ Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0k
class TestPython(TestBase):
-
def test_001_trigger_proc_space(self):
"""Test RE to find name of trigger process"""
first = self.get_first_line(OOMAnalyser.OOMDisplay.example_rhel7)
- pattern = OOMAnalyser.OOMAnalyser.oom_result.kconfig.EXTRACT_PATTERN['invoked oom-killer'][0]
+ pattern = OOMAnalyser.OOMAnalyser.oom_result.kconfig.EXTRACT_PATTERN[
+ "invoked oom-killer"
+ ][0]
rec = re.compile(pattern, re.MULTILINE)
match = rec.search(first)
- self.assertTrue(match, "Error: re.search('invoked oom-killer') failed for simple process name")
+ self.assertTrue(
+ match,
+ "Error: re.search('invoked oom-killer') failed for simple process name",
+ )
- first = first.replace('sed', 'VM Monitoring Task')
+ first = first.replace("sed", "VM Monitoring Task")
match = rec.search(first)
- self.assertTrue(match, "Error: re.search('invoked oom-killer') failed for process name with space")
+ self.assertTrue(
+ match,
+ "Error: re.search('invoked oom-killer') failed for process name with space",
+ )
def test_002_killed_proc_space(self):
"""Test RE to find name of killed process"""
text = self.get_lines(OOMAnalyser.OOMDisplay.example_rhel7, -2)
- pattern = OOMAnalyser.OOMAnalyser.oom_result.kconfig.EXTRACT_PATTERN['Process killed by OOM'][0]
+ pattern = OOMAnalyser.OOMAnalyser.oom_result.kconfig.EXTRACT_PATTERN[
+ "Process killed by OOM"
+ ][0]
rec = re.compile(pattern, re.MULTILINE)
match = rec.search(text)
- self.assertTrue(match, "Error: re.search('Process killed by OOM') failed for simple process name")
+ self.assertTrue(
+ match,
+ "Error: re.search('Process killed by OOM') failed for simple process name",
+ )
- text = text.replace('sed', 'VM Monitoring Task')
+ text = text.replace("sed", "VM Monitoring Task")
match = rec.search(text)
- self.assertTrue(match, "Error: re.search('Process killed by OOM') failed for process name with space")
+ self.assertTrue(
+ match,
+ "Error: re.search('Process killed by OOM') failed for process name with space",
+ )
def test_003_OOMEntity_number_of_columns_to_strip(self):
"""Test stripping useless / leading columns"""
oom_entity = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7)
for pos, line in [
- (1, '[11686.888109] CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1'),
- (5, 'Apr 01 14:13:32 mysrv kernel: CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1'),
- (6, 'Apr 01 14:13:32 mysrv kernel: [11686.888109] CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1'),
+ (
+ 1,
+ "[11686.888109] CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1",
+ ),
+ (
+ 5,
+ "Apr 01 14:13:32 mysrv kernel: CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1",
+ ),
+ (
+ 6,
+ "Apr 01 14:13:32 mysrv kernel: [11686.888109] CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1",
+ ),
]:
to_strip = oom_entity._number_of_columns_to_strip(line)
- self.assertEqual(to_strip, pos, 'Calc wrong number of columns to strip for "%s": got: %d, expect: %d' % (
- line, to_strip, pos))
+ self.assertEqual(
+ to_strip,
+ pos,
+ 'Calc wrong number of columns to strip for "%s": got: %d, expect: %d'
+ % (line, to_strip, pos),
+ )
def test_004_extract_block_from_next_pos(self):
"""Test extracting a single block (all lines till the next line with a colon)"""
oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7)
analyser = OOMAnalyser.OOMAnalyser(oom)
- text = analyser._extract_block_from_next_pos('Hardware name:')
- expected = '''\
+ text = analyser._extract_block_from_next_pos("Hardware name:")
+ expected = """\
Hardware name: HP ProLiant DL385 G7, BIOS A18 12/08/2012
ffff880182272f10 00000000021dcb0a ffff880418207938 ffffffff816861ac
ffff8804182079c8 ffffffff81681157 ffffffff810eab9c ffff8804182fe910
ffff8804182fe928 0000000000000202 ffff880182272f10 ffff8804182079b8
-'''
+"""
self.assertEqual(text, expected)
def test_005_extract_kernel_version(self):
@@ -484,25 +613,57 @@ Hardware name: HP ProLiant DL385 G7, BIOS A18 12/08/2012
oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7)
analyser = OOMAnalyser.OOMAnalyser(oom)
for text, kversion in [
- ('CPU: 0 PID: 19163 Comm: kworker/0:0 Tainted: G OE 5.4.0-80-lowlatency #90~18.04.1-Ubuntu', '5.4.0-80-lowlatency'),
- ('CPU: 4 PID: 1 Comm: systemd Not tainted 3.10.0-1062.9.1.el7.x86_64 #1', '3.10.0-1062.9.1.el7.x86_64'),
+ (
+ "CPU: 0 PID: 19163 Comm: kworker/0:0 Tainted: G OE 5.4.0-80-lowlatency #90~18.04.1-Ubuntu",
+ "5.4.0-80-lowlatency",
+ ),
+ (
+ "CPU: 4 PID: 1 Comm: systemd Not tainted 3.10.0-1062.9.1.el7.x86_64 #1",
+ "3.10.0-1062.9.1.el7.x86_64",
+ ),
]:
analyser.oom_entity.text = text
success = analyser._identify_kernel_version()
- self.assertTrue(analyser._identify_kernel_version(), analyser.oom_result.error_msg)
+ self.assertTrue(
+ analyser._identify_kernel_version(), analyser.oom_result.error_msg
+ )
self.assertEqual(analyser.oom_result.kversion, kversion)
def test_006_choosing_kernel_config(self):
"""Test choosing the right kernel configuration"""
for kcfg, kversion in [
- (OOMAnalyser.KernelConfig_5_8(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 5.13.0-514 #1'),
- (OOMAnalyser.KernelConfig_5_8(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 5.8.0-514 #1'),
- (OOMAnalyser.KernelConfig_4_6(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 4.6.0-514 #1'),
- (OOMAnalyser.KernelConfigRhel7(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-1062.9.1.el7.x86_64 #1'),
- (OOMAnalyser.KernelConfig_5_0(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 5.5.1 #1'),
- (OOMAnalyser.KernelConfig_5_8(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 5.23.0 #1'),
- (OOMAnalyser.KernelConfig_5_8(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 6.12.0 #1'),
- (OOMAnalyser.BaseKernelConfig(), 'CPU: 4 PID: 29481 Comm: sed Not tainted 2.33.0 #1'),
+ (
+ OOMAnalyser.KernelConfig_5_8(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 5.13.0-514 #1",
+ ),
+ (
+ OOMAnalyser.KernelConfig_5_8(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 5.8.0-514 #1",
+ ),
+ (
+ OOMAnalyser.KernelConfig_4_6(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 4.6.0-514 #1",
+ ),
+ (
+ OOMAnalyser.KernelConfigRhel7(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-1062.9.1.el7.x86_64 #1",
+ ),
+ (
+ OOMAnalyser.KernelConfig_5_0(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 5.5.1 #1",
+ ),
+ (
+ OOMAnalyser.KernelConfig_5_8(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 5.23.0 #1",
+ ),
+ (
+ OOMAnalyser.KernelConfig_5_8(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 6.12.0 #1",
+ ),
+ (
+ OOMAnalyser.BaseKernelConfig(),
+ "CPU: 4 PID: 29481 Comm: sed Not tainted 2.33.0 #1",
+ ),
]:
oom = OOMAnalyser.OOMEntity(kversion)
analyser = OOMAnalyser.OOMAnalyser(oom)
@@ -510,10 +671,10 @@ Hardware name: HP ProLiant DL385 G7, BIOS A18 12/08/2012
analyser._choose_kernel_config()
result = analyser.oom_result.kconfig
self.assertEqual(
- type(result), type(kcfg),
- 'Mismatch between expected kernel config "%s" and chosen config "%s" for kernel version "%s"' % (
- type(kcfg), type(result), kversion
- )
+ type(result),
+ type(kcfg),
+ 'Mismatch between expected kernel config "%s" and chosen config "%s" for kernel version "%s"'
+ % (type(kcfg), type(result), kversion),
)