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 == '<not found>': + if content == "<not found>": 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 += '<li><a href="#{}">{}</a></li>'.format(element.id, element.textContent) + new_toc += '<li><a href="#{}">{}</a></li>'.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: <td>{}</td> <td>{}</td> </tr> - """.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. <h3>Summary</h3> should be not displayed") + self.assertFalse( + h3_summary.is_displayed(), + "Analysis details incl. <h3>Summary</h3> 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. <h3>Summary</h3> should be displayed") + self.assertTrue( + h3_summary.is_displayed(), + "Analysis details incl. <h3>Summary</h3> 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. <h3>Summary</h3> should be not displayed") + self.assertFalse( + h3_summary.is_displayed(), + "Analysis details incl. <h3>Summary</h3> 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. <h3>Summary</h3> should be not displayed") + self.assertFalse( + h3_summary.is_displayed(), + "Analysis details incl. <h3>Summary</h3> 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. <h3>Summary</h3> should be not displayed") + self.assertFalse( + h3_summary.is_displayed(), + "Analysis details incl. <h3>Summary</h3> 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. <h3>Summary</h3> should be displayed") + self.assertTrue( + h3_summary.is_displayed(), + "Analysis details incl. <h3>Summary</h3> 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. <h3>Summary</h3> should be displayed") + self.assertTrue( + h3_summary.is_displayed(), + "Analysis details incl. <h3>Summary</h3> 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 <kern.warning> kernel: ", - "Apr 01 14:13:32 mysrv kernel: [11686.888109] ", - "kernel:", - "Apr 01 14:13:32 mysrv <kern.warning> 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 <kern.warning> kernel: ", + "Apr 01 14:13:32 mysrv kernel: [11686.888109] ", + "kernel:", + "Apr 01 14:13:32 mysrv <kern.warning> 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), )