Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:infrastructure
OOMAnalyser
oom-o-o-0.1.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File oom-o-o-0.1.obscpio of Package OOMAnalyser
07070100000000000081A4000000000000000000000001643C162900000430000000000000000000000000000000000000001700000000oom-o-o-0.1/.build.yml# Build file for OOMAnalyser # # Copyright (c) 2020-2023 Carsten Grohmann # License: MIT (see LICENSE.txt) # THIS PROGRAM COMES WITH NO WARRANTY image: archlinux arch: x86_64 packages: - python3 - python-black - python-virtualenv - git - xorg-server-xvfb sources: - https://git.sr.ht/~carstengrohmann/OOMAnalyser shell: null tasks: - setup_pkgs: | yay -S --noconfirm --noeditmenu --noupgrademenu rollup chromedriver google-chrome git clone https://aur.archlinux.org/python37.git cd python37 # disable optimizations to speedup build process by 8 minutes sed -i '/enable-optimizations/d' PKGBUILD makepkg --noconfirm -si --skippgpcheck cd .. - setup_venv: | cd OOMAnalyser make venv - check_code_with_black: | cd OOMAnalyser make black-check - build: | cd OOMAnalyser make build ls -l OOMAnalyser.html OOMAnalyser.js - test: | cd OOMAnalyser make test triggers: - action: email condition: failure to: Carsten Grohmann <mail@carstengrohmann.de> 07070100000001000081A4000000000000000000000001643C1629000001B8000000000000000000000000000000000000001700000000oom-o-o-0.1/.gitignore# Files to be ignored by Git # # Copyright (c) 2017-2023 Carsten Grohmann # License: MIT (see LICENSE.txt) # THIS PROGRAM COMES WITH NO WARRANTY # Byte-compiled / optimized / DLL files *.py[cod] # Virtual environment env/ # IntelliJ project files .idea # Webdriver Manager: cache for selenium drivers .wdm # Generated (compiled) JavaScript code __target__/ OOMAnalyser.js # Release files release/ OOMAnalyser*.zip OOMAnalyser*.tar.gz 07070100000002000081A4000000000000000000000001643C1629000001DA000000000000000000000000000000000000002400000000oom-o-o-0.1/.pre-commit-config.yamlrepos: - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black language_version: python3.7 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: check-shebang-scripts-are-executable - id: check-toml - id: check-yaml - id: debug-statements - id: detect-private-key - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace 07070100000003000081A4000000000000000000000001643C16290000044F000000000000000000000000000000000000001800000000oom-o-o-0.1/LICENSE.txtCopyright (c) 2017-2023 Carsten Grohmann mail <add at here> carstengrohmann.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 07070100000004000081A4000000000000000000000001643C162900001078000000000000000000000000000000000000001500000000oom-o-o-0.1/Makefile# Makefile for OOMAnalyser # # Copyright (c) 2017-2023 Carsten Grohmann # License: MIT (see LICENSE.txt) # THIS PROGRAM COMES WITH NO WARRANTY .PHONY: help clean distclean venv venv-clean venv-freeze build websrv test # Makefile defaults SHELL = /bin/sh BASE_DIR = . PYTHON3_BIN = /usr/bin/python3.7 TARGET_DIR = $(BASE_DIR)/__target__ VIRTUAL_ENV_DIR = env HTML_FILE = $(BASE_DIR)/OOMAnalyser.html JS_OUT_FILE = $(BASE_DIR)/OOMAnalyser.js JS_TEMP_FILE = $(TARGET_DIR)/OOMAnalyser.js PY_SOURCE = $(BASE_DIR)/OOMAnalyser.py TEST_FILE = $(BASE_DIR)/test.py # e.g. 0.6.0 or 0.6.0_devel VERSION = 0.6.0_devel RELEASE_DIR = $(BASE_DIR)/release RELEASE_FILES = $(HTML_FILE) $(JS_OUT_FILE) $(PY_SOURCE) $(TEST_FILE) rollup.config.js Makefile requirements.txt \ LICENSE.txt README.md RELEASE_INST_DIR = $(RELEASE_DIR)/OOMAnalyser-$(VERSION) RELEASE_TARGZ = OOMAnalyser-$(VERSION).tar.gz RELEASE_ZIP = OOMAnalyser-$(VERSION).zip BLACK_BIN = black BLACK_OPTS = --verbose ROLLUP_BIN = rollup ROLLUP_OPTS = --config rollup.config.mjs TRANSCRYPT_BIN = transcrypt TRANSCRYPT_OPTS = --build --map --nomin --sform --esv 6 export VIRTUAL_ENV := $(abspath ${VIRTUAL_ENV_DIR}) export PATH := ${VIRTUAL_ENV_DIR}/bin:${PATH} HELP= @grep -B1 '^[a-zA-Z\-]*:' Makefile |\ awk 'function p(h,t){printf"%-12s=%s\n",h,t;};\ /\#+/{t=$$0;};\ /:/{gsub(":.*","");h=$$0};\ /^--/{p(h,t);t=h="";};\ END{p(h,t)}' |\ sed -n 's/=.*\#+/:/gp' #+ Show this text help: $(HELP) #+ Run source code formatter black black: $(BLACK_BIN) $(BLACK_OPTS) $(PY_SOURCE) $(TEST_FILE) #+ Run source code formatter black in check-only mode black-check: $(BLACK_BIN) --check $(BLACK_OPTS) $(PY_SOURCE) $(TEST_FILE) #+ Clean python compiler files and automatically generated files clean: @echo "Remove all automatically generated files ..." @find $(BASE_DIR) -depth -type f -name "*.pyc" -exec rm -f {} \; @find $(BASE_DIR) -depth -type f -name "*.pyo" -exec rm -f {} \; @find $(BASE_DIR) -depth -type f -name "*.orig" -exec rm -f {} \; @find $(BASE_DIR) -depth -type f -name "*~" -exec rm -f {} \; @$(RM) --force --recursive .wdm @$(RM) --force --recursive ${RELEASE_DIR} ${TARGET_DIR} ${RELEASE_TARGZ} ${RELEASE_ZIP} #+ Remove all automatically generated and Git repository data distclean: clean venv-clean @echo "Remove Git repository data (.git*) ..." @(RM) --force .git .gitignore $(VIRTUAL_ENV_DIR)/bin/activate: requirements.txt test -d $(VIRTUAL_ENV_DIR) || virtualenv -p $(PYTHON3_BIN) $(VIRTUAL_ENV_DIR) . $(VIRTUAL_ENV_DIR)/bin/activate $(VIRTUAL_ENV_DIR)/bin/pip install -Ur requirements.txt touch $(VIRTUAL_ENV_DIR)/bin/activate #+ Setup the virtual environment from scratch venv: $(VIRTUAL_ENV_DIR)/bin/activate #+ Freeze the current virtual environment by update requirements.txt venv-freeze: source $(VIRTUAL_ENV_DIR)/bin/activate && $(VIRTUAL_ENV_DIR)/bin/pip freeze > requirements.txt #+ Remove the virtual environment venv-clean: rm -rf $(VIRTUAL_ENV_DIR) ${JS_TEMP_FILE}: $(VIRTUAL_ENV_DIR)/bin/activate ${PY_SOURCE} . $(VIRTUAL_ENV_DIR)/bin/activate $(TRANSCRYPT_BIN) $(TRANSCRYPT_OPTS) ${PY_SOURCE} ${JS_OUT_FILE}: $(VIRTUAL_ENV_DIR)/bin/activate ${JS_TEMP_FILE} . $(VIRTUAL_ENV_DIR)/bin/activate $(ROLLUP_BIN) $(ROLLUP_OPTS) ${RELEASE_TARGZ} ${RELEASE_ZIP}: mkdir -p $(RELEASE_INST_DIR) && \ cp -p $(RELEASE_FILES) $(RELEASE_INST_DIR) && \ cd $(RELEASE_DIR) && \ tar cvzf $(RELEASE_TARGZ) OOMAnalyser-$(VERSION) && \ zip -vr $(RELEASE_ZIP) OOMAnalyser-$(VERSION) && \ mv $(RELEASE_TARGZ) $(RELEASE_ZIP) .. #+ Compile Python to JavaScript build: $(VIRTUAL_ENV_DIR)/bin/activate ${JS_OUT_FILE} #+ Serve the current directory on http://127.0.0.1:8080 websrv: $(VIRTUAL_ENV_DIR)/bin/activate ${JS_OUT_FILE} $(PYTHON3_BIN) -m http.server 8080 --bind 127.0.0.1 #+ Run Selenium based web tests test: $(VIRTUAL_ENV_DIR)/bin/activate ${JS_OUT_FILE} . $(VIRTUAL_ENV_DIR)/bin/activate DISPLAY=:1 xvfb-run python $(TEST_FILE) #+ Build release packages release: ${JS_OUT_FILE} ${RELEASE_TARGZ} ${RELEASE_ZIP} 07070100000005000081A4000000000000000000000001643C162900015715000000000000000000000000000000000000001D00000000oom-o-o-0.1/OOMAnalyser.html<!DOCTYPE html> <!-- HTML for OOMAnalyser Copyright (c) 2017-2023 Carsten Grohmann License: MIT (see LICENSE.txt) THIS PROGRAM COMES WITH NO WARRANTY --> <html lang="en"> <head> <script defer="defer" src="OOMAnalyser.js"></script> <meta charset="UTF-8"> <title>OOMAnalyser</title> <link href="https://static.opensuse.org/favicon-32.png" rel="icon" sizes="32x32" type="image/png"><link href="https://static.opensuse.org/favicon-48.png" rel="icon" sizes="48x48" type="image/png"><link href="https://static.opensuse.org/favicon-64.png" rel="icon" sizes="64x64" type="image/png"><link href="https://static.opensuse.org/favicon-96.png" rel="icon" sizes="96x96" type="image/png"><link href="https://static.opensuse.org/favicon-144.png" rel="icon" sizes="144x144" type="image/png"><link href="https://static.opensuse.org/favicon-192.png" rel="icon" sizes="192x192" type="image/png"> <!-- Chameleon Style --> <link rel="stylesheet" href="https://static.opensuse.org/chameleon-3.0/dist/css/chameleon.css" /> <!-- jQuery Slim --> <script src="https://static.opensuse.org/chameleon-3.0/dist/js/jquery.slim.js" defer ></script> <!-- Bootstrap Script --> <script src="https://static.opensuse.org/chameleon-3.0/dist/js/bootstrap.bundle.js" defer ></script> <!-- Chameleon Script --> <script src="https://static.opensuse.org/chameleon-3.0/dist/js/chameleon.js" defer ></script> <style> /* Use BEM (Block__Element--Modifier) naming convention */ .text--align-right { text-align: right; } .text__superscript { vertical-align: super; font-size: 0.83em; } .js-text--default-hide { /* empty - used with JS to hide elements in the default view * * OOMDisplay.set_html_defaults() hides all elements by * adding "js-text--display-none" to classList. */ } .js-text--default-show { /* empty - used with JS to show elements in the default view * * OOMDisplay.set_html_defaults() shows all elements by * removing "js-text--display-none" from classList. */ } .js-text--display-none { /* hide elements * * The visibility of elements will be toggled with JS. * OOMAnalyser.show_elements(), OOMAnalyser.hide_elements() and * OOMAnalyser.toggle_elements() are used to control the * visibility. * * Set js-text--display-none to prevent flickering between loading * the page and setting default values with * OOMDisplay.set_html_defaults(). */ display: none; } .table__sub-section--bold { font-weight: bold; font-size: medium; padding: 5px; } a { text-decoration: none; } a:hover, a:active { text-decoration: underline; } .a--small { font-size: small; font-weight: unset; padding: unset; } .a__footnote { vertical-align: super; font-size: 0.83em; } .h2--no-newline { /* Prevent a linebreak after headline to a place a link on the same line */ /* Place such headlines within a div container if the next element can be invisible */ display: inline-block; } .js-mem-usage__svg { display: block; } .js-alloc-failure--show { /* empty - used to hide/show detailed allocation failure analysis */ } .js-alloc-failure-below-low-watermark--show { /* empty - used to hide/show details for failed memory allocations */ } .js-alloc-failure-no-free-chunks--show { /* empty - used to hide/show details for failed memory allocations */ } .js-alloc-failure-unknown-reason-show { /* empty - used to hide/show details for failed memory allocations */ } .js-killed-proc-score--show { /* empty - used to hide/show OOM score of killed process */ } .js-memory-fragmentation--show { /* empty - used to hide/show details for memory fragmentation */ } .js-memory-heavy-fragmentation--show { /* empty - used to hide/show details for memory fragmentation */ } .js-memory-no-heavy-fragmentation--show { /* empty - used to hide/show details for memory fragmentation */ } .js-memory-shortage-node--hide { /* empty - used to show the NUMA node with memory shortage */ } .js-oom-automatic--show { /* empty - used to show sections for automatically triggered OOMs */ } .js-oom-manual--show { /* empty - used to show sections for manually triggered OOMs */ } .js-pagesize-guessed--show { /* empty - used to show if the page size is guessed */ } .js-pagesize-determined--show { /* empty - used to show if the page size is determined */ } .js-pstable_sort_col0 { /* empty - used to sort process table */ } .js-pstable_sort_col1 { /* empty - used to sort process table */ } .js-pstable_sort_col2 { /* empty - used to sort process table */ } .js-pstable_sort_col3 { /* empty - used to sort process table */ } .js-pstable_sort_col4 { /* empty - used to sort process table */ } .js-pstable_sort_col5 { /* empty - used to sort process table */ } .js-pstable_sort_col6 { /* empty - used to sort process table */ } .js-pstable_sort_col7 { /* empty - used to sort process table */ } .js-pstable_sort_col8 { /* empty - used to sort process table */ } .js-pstable_sort_col9 { /* empty - used to sort process table */ } .js-swap-active--show { /* empty - used to show if the swap space is activated */ } .js-swap-inactive--show { /* empty - used to show if the swap space is not activated */ } .result__table { border-collapse: collapse; padding: 10px; table-layout: fixed; text-align: left; width: 100%; } .result__table--size-col-1 { width: 300px; } .result__table--border { border-collapse: collapse; padding: 10px; } .pstable__table--noborder { border: none; text-align: right; background-color: unset; table-layout: fixed; } .pstable__table--noborder thead * { font-weight: bold; padding-right: unset; padding-left: unset; white-space: nowrap; /* overwrite the generic th/td settings */ border: none; } .pstable__table--noborder tbody * { padding-left: 5px; padding-right: 18px; /* overwrite the generic th/td settings */ border: none; } /* Align last both columns to left in the process table */ .pstable__table--noborder td:nth-of-type(9) { text-align: left; } .pstable__table--noborder td:nth-of-type(10) { text-align: left; } .js-pstable__killedproc--bgcolor { background-color: #FFD2D2; } .js-pstable__triggerproc--bgcolor { background-color: #FEEFB3; } .pstable__row-numeric--width { width: 10ch; } .pstable__row-pages--width { width: 16ch; } .pstable__row-oom-score-adj--width { width: 16ch; } .pstable__row-notes--width { width: 20ch; } .pstable__row-sort--width { padding-left: unset; padding-right: unset; width: 10px; display: inline-block; } th { font-weight: bold; font-size: large; padding: 5px; } th, td { border: 1px solid black; word-wrap: break-word; } .js-notify_box__msg--debug { color: #000000; background-color: #e8ffb3; } .js-notify_box__msg--error { color: #D8000C; background-color: #FFD2D2; } .js-notify_box__msg--warning { color: #9F6000; background-color: #FEEFB3; } .license__text { font-size: small; } .notify_box { width: 100%; } .terminal { font-family: monospace; overflow: auto; } .table-of-contents { float: right; width: 40%; background: #eee; font-size: 0.8em; padding: 1em 2em; margin: 0 0 0.5em 0.5em; } .table-of-contents ul { padding: 0; } .table-of-contents li { margin: 0 0 0.25em 0; } </style> <script> function goBack() { window.history.back(); } // Add listener after the document has been loaded completely window.addEventListener('DOMContentLoaded', function() { let dropArea = document.getElementById('input'); dropArea.addEventListener('drop', file_dragged, false); }) // Event handler triggered if a file has been dragged function file_dragged(event) { let file = event.dataTransfer.files[0] event.preventDefault() read_and_display_file(file) return true; } // Read and display local file function read_and_display_file(file) { let reader = new FileReader(); reader.onload = function(e) { let textarea_oom = document.getElementById('textarea_oom') textarea_oom.value = reader.result; } reader.readAsText(file); } // Report uncaught errors to the user window.onerror = function (msg, url, lineNo, columnNo, errorObj) { let text = `INTERNAL ERROR:<br>${msg} at ${url}:${lineNo}:${columnNo}<br> ` if ('__args__' in errorObj && errorObj.__args__ !== undefined) { text += `Details: ${errorObj.__args__} <br>` } if ('stack' in errorObj && errorObj.stack !== undefined) { text += `Stack trace:</br>${errorObj.stack.replaceAll('\n', '<br>')}` } let notify_box = document.getElementById('notify_box') notify_box.classList.remove('js-text--display-none') let notification = document.createElement('div') notification.classList.add('js-notify_box__msg--error') notification.innerHTML = text notify_box.appendChild(notification) // true - don't propagate the event to the default handler return true }; </script> </head> <body class="d-flex flex-column"> <!-- START of openSUSE navbar --> <nav class="navbar noprint navbar-expand-md"> <a class="navbar-brand" href="/"> <img src="https://static.opensuse.org/favicon.svg" class="d-inline-block align-top" alt="openSUSE" title="openSUSE" width="30" height="30"> <span class="navbar-title">OOMAnalyser</span> </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-collapse"> <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M2.5 11.5A.5.5 0 0 1 3 11h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 3 3h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"> </path> </svg> </button> <div class="collapse navbar-collapse" id="navbar-collapse"> <ul class="nav navbar-nav mr-auto flex-md-shrink-0"> </div> <button class="navbar-toggler megamenu-toggler" type="button" data-toggle="collapse" data-target="#megamenu" aria-expanded="true"> <svg class="bi bi-grid" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"> </path> </svg> </button> <!--h1>Analyse and visualise Linux OOM output</h1> <nav class="table-of-contents" id="table_of_contents"> <h2>On this page</h2> <ul> </ul--> </nav> <div id="megamenu" class="megamenu collapse" style=""> <div class="container-fluid"> <div class="row"> <div class="col-6 col-md-4 col-lg-2"> <h5 class="megamenu-heading l10n" data-msg-id="main">Main</h5> <ul class="megamenu-list"> <li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M2 13.5V7h1v6.5a.5.5 0 0 0 .5.5h9a.5.5 0 0 0 .5-.5V7h1v6.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5zm11-11V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"></path> <path fill-rule="evenodd" d="M7.293 1.5a1 1 0 0 1 1.414 0l6.647 6.646a.5.5 0 0 1-.708.708L8 2.207 1.354 8.854a.5.5 0 1 1-.708-.708L7.293 1.5z"></path> </svg> <a class="l10n" href="https://www.opensuse.org/" data-msg-id="main-site" data-url-id="main-site-url">Main site</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2l-2.218-.887zm3.564 1.426L5.596 5 8 5.961 14.154 3.5l-2.404-.961zm3.25 1.7l-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923l6.5 2.6zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464L7.443.184z"></path></svg> <a class="l10n" href="https://software.opensuse.org/" data-msg-id="software" data-url-id="software-url">Software</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M14.5 3h-13a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"></path> <path fill-rule="evenodd" d="M3 5.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8zm0 2.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5z"></path> </svg> <a class="l10n" href="https://en.opensuse.org/" data-msg-id="wiki" data-url-id="wiki-url">Wiki</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M3.214 1.072C4.813.752 6.916.71 8.354 2.146A.5.5 0 0 1 8.5 2.5v11a.5.5 0 0 1-.854.354c-.843-.844-2.115-1.059-3.47-.92-1.344.14-2.66.617-3.452 1.013A.5.5 0 0 1 0 13.5v-11a.5.5 0 0 1 .276-.447L.5 2.5l-.224-.447.002-.001.004-.002.013-.006a5.017 5.017 0 0 1 .22-.103 12.958 12.958 0 0 1 2.7-.869zM1 2.82v9.908c.846-.343 1.944-.672 3.074-.788 1.143-.118 2.387-.023 3.426.56V2.718c-1.063-.929-2.631-.956-4.09-.664A11.958 11.958 0 0 0 1 2.82z"></path><path fill-rule="evenodd" d="M12.786 1.072C11.188.752 9.084.71 7.646 2.146A.5.5 0 0 0 7.5 2.5v11a.5.5 0 0 0 .854.354c.843-.844 2.115-1.059 3.47-.92 1.344.14 2.66.617 3.452 1.013A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.276-.447L15.5 2.5l.224-.447-.002-.001-.004-.002-.013-.006-.047-.023a12.582 12.582 0 0 0-.799-.34 12.96 12.96 0 0 0-2.073-.609zM15 2.82v9.908c-.846-.343-1.944-.672-3.074-.788-1.143-.118-2.387-.023-3.426.56V2.718c1.063-.929 2.631-.956 4.09-.664A11.956 11.956 0 0 1 15 2.82z"></path></svg> <a class="l10n" href="https://doc.opensuse.org/" data-msg-id="documentation" data-url-id="documentation-url">Documentation</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.5a2 2 0 0 1 1.6.8L8 14.333 9.9 11.8a2 2 0 0 1 1.6-.8H14a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"></path> <path d="M5 6a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path> </svg> <a class="l10n" href="https://forums.opensuse.org/" data-msg-id="forum" data-url-id="forum-url">Forum</a></li> </ul> </div> <div class="col-6 col-md-4 col-lg-2"> <h5 class="megamenu-heading l10n" data-msg-id="development">Development</h5> <ul class="megamenu-list"> <li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M8.837 1.626c-.246-.835-1.428-.835-1.674 0l-.094.319A1.873 1.873 0 0 1 4.377 3.06l-.292-.16c-.764-.415-1.6.42-1.184 1.185l.159.292a1.873 1.873 0 0 1-1.115 2.692l-.319.094c-.835.246-.835 1.428 0 1.674l.319.094a1.873 1.873 0 0 1 1.115 2.693l-.16.291c-.415.764.42 1.6 1.185 1.184l.292-.159a1.873 1.873 0 0 1 2.692 1.116l.094.318c.246.835 1.428.835 1.674 0l.094-.319a1.873 1.873 0 0 1 2.693-1.115l.291.16c.764.415 1.6-.42 1.184-1.185l-.159-.291a1.873 1.873 0 0 1 1.116-2.693l.318-.094c.835-.246.835-1.428 0-1.674l-.319-.094a1.873 1.873 0 0 1-1.115-2.692l.16-.292c.415-.764-.42-1.6-1.185-1.184l-.291.159A1.873 1.873 0 0 1 8.93 1.945l-.094-.319zm-2.633-.283c.527-1.79 3.065-1.79 3.592 0l.094.319a.873.873 0 0 0 1.255.52l.292-.16c1.64-.892 3.434.901 2.54 2.541l-.159.292a.873.873 0 0 0 .52 1.255l.319.094c1.79.527 1.79 3.065 0 3.592l-.319.094a.873.873 0 0 0-.52 1.255l.16.292c.893 1.64-.902 3.434-2.541 2.54l-.292-.159a.873.873 0 0 0-1.255.52l-.094.319c-.527 1.79-3.065 1.79-3.592 0l-.094-.319a.873.873 0 0 0-1.255-.52l-.292.16c-1.64.893-3.433-.902-2.54-2.541l.159-.292a.873.873 0 0 0-.52-1.255l-.319-.094c-1.79-.527-1.79-3.065 0-3.592l.319-.094a.873.873 0 0 0 .52-1.255l-.16-.292c-.892-1.64.902-3.433 2.541-2.54l.292.159a.873.873 0 0 0 1.255-.52l.094-.319z"></path> <path fill-rule="evenodd" d="M8 5.754a2.246 2.246 0 1 0 0 4.492 2.246 2.246 0 0 0 0-4.492zM4.754 8a3.246 3.246 0 1 1 6.492 0 3.246 3.246 0 0 1-6.492 0z"></path> </svg> <a class="l10n" href="https://build.opensuse.org/" data-msg-id="build-service" data-url-id="build-service-url">Build service</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M4.355.522a.5.5 0 0 1 .623.333l.291.956A4.979 4.979 0 0 1 8 1c1.007 0 1.946.298 2.731.811l.29-.956a.5.5 0 1 1 .957.29l-.41 1.352A4.985 4.985 0 0 1 13 6h.5a.5.5 0 0 0 .5-.5V5a.5.5 0 0 1 1 0v.5A1.5 1.5 0 0 1 13.5 7H13v1h1.5a.5.5 0 0 1 0 1H13v1h.5a1.5 1.5 0 0 1 1.5 1.5v.5a.5.5 0 1 1-1 0v-.5a.5.5 0 0 0-.5-.5H13a5 5 0 0 1-10 0h-.5a.5.5 0 0 0-.5.5v.5a.5.5 0 1 1-1 0v-.5A1.5 1.5 0 0 1 2.5 10H3V9H1.5a.5.5 0 0 1 0-1H3V7h-.5A1.5 1.5 0 0 1 1 5.5V5a.5.5 0 0 1 1 0v.5a.5.5 0 0 0 .5.5H3c0-1.364.547-2.601 1.432-3.503l-.41-1.352a.5.5 0 0 1 .333-.623zM4 7v4a4 4 0 0 0 3.5 3.97V7H4zm4.5 0v7.97A4 4 0 0 0 12 11V7H8.5zM12 6H4a3.99 3.99 0 0 1 1.333-2.982A3.983 3.983 0 0 1 8 2c1.025 0 1.959.385 2.666 1.018A3.989 3.989 0 0 1 12 6z"></path> </svg> <a class="l10n" href="https://bugzilla.opensuse.org/" data-msg-id="bugzilla" data-url-id="bugzilla-url">Bugzilla</a></li><li><svg width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path d="M256,32C132.3,32,32,134.9,32,261.7c0,101.5,64.2,187.5,153.2,217.9a17.56,17.56,0,0,0,3.8.4c8.3,0,11.5-6.1,11.5-11.4,0-5.5-.2-19.9-.3-39.1a102.4,102.4,0,0,1-22.6,2.7c-43.1,0-52.9-33.5-52.9-33.5-10.2-26.5-24.9-33.6-24.9-33.6-19.5-13.7-.1-14.1,1.4-14.1h.1c22.5,2,34.3,23.8,34.3,23.8,11.2,19.6,26.2,25.1,39.6,25.1a63,63,0,0,0,25.6-6c2-14.8,7.8-24.9,14.2-30.7-49.7-5.8-102-25.5-102-113.5,0-25.1,8.7-45.6,23-61.6-2.3-5.8-10-29.2,2.2-60.8a18.64,18.64,0,0,1,5-.5c8.1,0,26.4,3.1,56.6,24.1a208.21,208.21,0,0,1,112.2,0c30.2-21,48.5-24.1,56.6-24.1a18.64,18.64,0,0,1,5,.5c12.2,31.6,4.5,55,2.2,60.8,14.3,16.1,23,36.6,23,61.6,0,88.2-52.4,107.6-102.3,113.3,8,7.1,15.2,21.1,15.2,42.5,0,30.7-.3,55.5-.3,63,0,5.4,3.1,11.5,11.4,11.5a19.35,19.35,0,0,0,4-.4C415.9,449.2,480,363.1,480,261.7,480,134.9,379.7,32,256,32Z"></path> </svg> <a class="l10n" href="https://github.com/opensuse" data-msg-id="github" data-url-id="github-url">GitHub</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M9.669.864L8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68L9.669.864zm1.196 1.193l-1.51-.229L8 1.126l-1.355.702-1.51.229-.684 1.365-1.086 1.072L3.614 6l-.25 1.506 1.087 1.072.684 1.365 1.51.229L8 10.874l1.356-.702 1.509-.229.684-1.365 1.086-1.072L12.387 6l.248-1.506-1.086-1.072-.684-1.365z"></path><path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1 4 11.794z"></path></svg> <a class="l10n" href="https://openqa.opensuse.org/" data-msg-id="openaq" data-url-id="openaq-url">openQA</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M1.018 7.5h2.49c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5zM2.255 4H4.09a9.266 9.266 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.024 7.024 0 0 0 2.255 4zM8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm-.5 1.077c-.67.204-1.335.82-1.887 1.855-.173.324-.33.682-.468 1.068H7.5V1.077zM7.5 5H4.847a12.5 12.5 0 0 0-.338 2.5H7.5V5zm1 2.5V5h2.653c.187.765.306 1.608.338 2.5H8.5zm-1 1H4.51a12.5 12.5 0 0 0 .337 2.5H7.5V8.5zm1 2.5V8.5h2.99a12.495 12.495 0 0 1-.337 2.5H8.5zm-1 1H5.145c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12zm-2.173 2.472a6.695 6.695 0 0 1-.597-.933A9.267 9.267 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM1.674 11H3.82a13.651 13.651 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5zm8.999 3.472A7.024 7.024 0 0 0 13.745 12h-1.834a9.278 9.278 0 0 1-.641 1.539 6.688 6.688 0 0 1-.597.933zM10.855 12H8.5v2.923c.67-.204 1.335-.82 1.887-1.855A7.98 7.98 0 0 0 10.855 12zm1.325-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm.312-3.5h2.49a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5zM11.91 4a9.277 9.277 0 0 0-.64-1.539 6.692 6.692 0 0 0-.597-.933A7.024 7.024 0 0 1 13.745 4h-1.834zm-1.055 0H8.5V1.077c.67.204 1.335.82 1.887 1.855.173.324.33.682.468 1.068z"></path> </svg> <a class="l10n" href="https://l10n.opensuse.org/" data-msg-id="weblate" data-url-id="weblate-url">Weblate</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"></path><path fill-rule="evenodd" d="M6.854 4.646a.5.5 0 0 1 0 .708L4.207 8l2.647 2.646a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708 0zm2.292 0a.5.5 0 0 0 0 .708L11.793 8l-2.647 2.646a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708 0z"></path></svg> <a class="l10n" href="https://kernel.opensuse.org/" data-msg-id="kernel" data-url-id="kernel-url">Kernel</a></li> </ul> </div> <div class="col-6 col-md-4 col-lg-2"> <h5 class="megamenu-heading l10n" data-msg-id="information">Information</h5> <ul class="megamenu-list"> <li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M0 2A1.5 1.5 0 0 1 1.5.5h11A1.5 1.5 0 0 1 14 2v12a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 0 14V2zm1.5-.5A.5.5 0 0 0 1 2v12a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V2a.5.5 0 0 0-.5-.5h-11z"></path> <path fill-rule="evenodd" d="M15.5 3a.5.5 0 0 1 .5.5V14a1.5 1.5 0 0 1-1.5 1.5h-3v-1h3a.5.5 0 0 0 .5-.5V3.5a.5.5 0 0 1 .5-.5z"></path> <path d="M2 3h10v2H2V3zm0 3h4v3H2V6zm0 4h4v1H2v-1zm0 2h4v1H2v-1zm5-6h2v1H7V6zm3 0h2v1h-2V6zM7 8h2v1H7V8zm3 0h2v1h-2V8zm-3 2h2v1H7v-1zm3 0h2v1h-2v-1zm-3 2h2v1H7v-1zm3 0h2v1h-2v-1z"></path> </svg> <a class="l10n" href="https://news.opensuse.org/" data-msg-id="news" data-url-id="news-url">News</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path d="M6.445 11.688V6.354h-.633A12.6 12.6 0 0 0 4.5 7.16v.695c.375-.257.969-.62 1.258-.777h.012v4.61h.675zm1.188-1.305c.047.64.594 1.406 1.703 1.406 1.258 0 2-1.066 2-2.871 0-1.934-.781-2.668-1.953-2.668-.926 0-1.797.672-1.797 1.809 0 1.16.824 1.77 1.676 1.77.746 0 1.23-.376 1.383-.79h.027c-.004 1.316-.461 2.164-1.305 2.164-.664 0-1.008-.45-1.05-.82h-.684zm2.953-2.317c0 .696-.559 1.18-1.184 1.18-.601 0-1.144-.383-1.144-1.2 0-.823.582-1.21 1.168-1.21.633 0 1.16.398 1.16 1.23z"></path> <path fill-rule="evenodd" d="M1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1zm1-3a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H2z"></path> <path fill-rule="evenodd" d="M3.5 0a.5.5 0 0 1 .5.5V1a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 .5-.5zm9 0a.5.5 0 0 1 .5.5V1a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 .5-.5z"></path> </svg> <a class="l10n" href="https://events.opensuse.org/" data-msg-id="events" data-url-id="events-url">Events</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M1.018 7.5h2.49c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5zM2.255 4H4.09a9.266 9.266 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.024 7.024 0 0 0 2.255 4zM8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm-.5 1.077c-.67.204-1.335.82-1.887 1.855-.173.324-.33.682-.468 1.068H7.5V1.077zM7.5 5H4.847a12.5 12.5 0 0 0-.338 2.5H7.5V5zm1 2.5V5h2.653c.187.765.306 1.608.338 2.5H8.5zm-1 1H4.51a12.5 12.5 0 0 0 .337 2.5H7.5V8.5zm1 2.5V8.5h2.99a12.495 12.495 0 0 1-.337 2.5H8.5zm-1 1H5.145c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12zm-2.173 2.472a6.695 6.695 0 0 1-.597-.933A9.267 9.267 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM1.674 11H3.82a13.651 13.651 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5zm8.999 3.472A7.024 7.024 0 0 0 13.745 12h-1.834a9.278 9.278 0 0 1-.641 1.539 6.688 6.688 0 0 1-.597.933zM10.855 12H8.5v2.923c.67-.204 1.335-.82 1.887-1.855A7.98 7.98 0 0 0 10.855 12zm1.325-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm.312-3.5h2.49a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5zM11.91 4a9.277 9.277 0 0 0-.64-1.539 6.692 6.692 0 0 0-.597-.933A7.024 7.024 0 0 1 13.745 4h-1.834zm-1.055 0H8.5V1.077c.67.204 1.335.82 1.887 1.855.173.324.33.682.468 1.068z"></path></svg> <a class="l10n" href="https://planet.opensuse.org/" data-msg-id="planet" data-url-id="planet-url">Planet</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M2 6v8.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V6h1v8.5a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 14.5V6h1zm8-5a1.5 1.5 0 0 0-1.5 1.5c0 .098.033.16.12.227.103.081.272.15.49.2A3.44 3.44 0 0 0 9.96 3h.015L10 2.999l.025.002h.014A2.569 2.569 0 0 0 10.293 3c.17-.006.387-.026.598-.073.217-.048.386-.118.49-.199.086-.066.119-.13.119-.227A1.5 1.5 0 0 0 10 1zm0 3h-.006a3.535 3.535 0 0 1-.326 0 4.435 4.435 0 0 1-.777-.097c-.283-.063-.614-.175-.885-.385A1.255 1.255 0 0 1 7.5 2.5a2.5 2.5 0 0 1 5 0c0 .454-.217.793-.506 1.017-.27.21-.602.322-.885.385a4.434 4.434 0 0 1-1.104.099H10z"></path> <path fill-rule="evenodd" d="M6 1a1.5 1.5 0 0 0-1.5 1.5c0 .098.033.16.12.227.103.081.272.15.49.2A3.44 3.44 0 0 0 5.96 3h.015L6 2.999l.025.002h.014l.053.001a3.869 3.869 0 0 0 .799-.076c.217-.048.386-.118.49-.199.086-.066.119-.13.119-.227A1.5 1.5 0 0 0 6 1zm0 3h-.006a3.535 3.535 0 0 1-.326 0 4.435 4.435 0 0 1-.777-.097c-.283-.063-.614-.175-.885-.385A1.255 1.255 0 0 1 3.5 2.5a2.5 2.5 0 0 1 5 0c0 .454-.217.793-.506 1.017-.27.21-.602.322-.885.385a4.435 4.435 0 0 1-1.103.099H6zm1.5 12V6h1v10h-1z"></path> <path fill-rule="evenodd" d="M15 4H1v1h14V4zM1 3a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H1z"></path> </svg> <a class="l10n" href="https://shop.opensuse.org/" data-msg-id="shop" data-url-id="shop-url">Shop</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M7.938 2.016a.146.146 0 0 0-.054.057L1.027 13.74a.176.176 0 0 0-.002.183c.016.03.037.05.054.06.015.01.034.017.066.017h13.713a.12.12 0 0 0 .066-.017.163.163 0 0 0 .055-.06.176.176 0 0 0-.003-.183L8.12 2.073a.146.146 0 0 0-.054-.057A.13.13 0 0 0 8.002 2a.13.13 0 0 0-.064.016zm1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566z"></path> <path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995z"></path> </svg> <a class="l10n" href="https://status.opensuse.org/" data-msg-id="status" data-url-id="status-url">Status</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"></path> <path fill-rule="evenodd" d="M9.5 1h-3a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3zm4.354 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"></path> </svg> <a class="l10n" href="https://survey.opensuse.org/" data-msg-id="survey" data-url-id="survey-url">Survey</a></li> </ul> </div> <div class="col-6 col-md-4 col-lg-2"> <h5 class="megamenu-heading l10n" data-msg-id="community">Community</h5> <ul class="megamenu-list"> <li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M2.678 11.894a1 1 0 0 1 .287.801 10.97 10.97 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8.06 8.06 0 0 0 8 14c3.996 0 7-2.807 7-6 0-3.192-3.004-6-7-6S1 4.808 1 8c0 1.468.617 2.83 1.678 3.894zm-.493 3.905a21.682 21.682 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a9.68 9.68 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105z"></path> <path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path> </svg> <a class="l10n" href="https://en.opensuse.org/openSUSE:IRC_list" data-msg-id="irc-channels" data-url-id="irc-channels-url">IRC channels</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M14 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zM2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H2z"></path> <path d="M.05 3.555C.017 3.698 0 3.847 0 4v.697l5.803 3.546L0 11.801V12c0 .306.069.596.192.856l6.57-4.027L8 9.586l1.239-.757 6.57 4.027c.122-.26.191-.55.191-.856v-.2l-5.803-3.557L16 4.697V4c0-.153-.017-.302-.05-.445L8 8.414.05 3.555z"></path> </svg> <a class="l10n" href="https://en.opensuse.org/openSUSE:Mailing_lists_subscription" data-msg-id="mail-lists" data-url-id="mail-lists-url">Mail lists</a></li><li><svg width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path d="M455.27,32H56.73A24.74,24.74,0,0,0,32,56.73V455.27A24.74,24.74,0,0,0,56.73,480H256V304H202.45V240H256V189c0-57.86,40.13-89.36,91.82-89.36,24.73,0,51.33,1.86,57.51,2.68v60.43H364.15c-28.12,0-33.48,13.3-33.48,32.9V240h67l-8.75,64H330.67V480h124.6A24.74,24.74,0,0,0,480,455.27V56.73A24.74,24.74,0,0,0,455.27,32Z"></path></svg> <a class="l10n" href="https://www.facebook.com/groups/opensuseproject" data-msg-id="facebook-group" data-url-id="facebook-group-url">Facebook group</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path d="m 15.135036,0.00147914 c -0.01565,2.1e-5 -0.03132,4.44e-4 -0.04702,0.0013 -0.08366,0.0047 -0.167707,0.02177 -0.249133,0.05288 -7.1e-5,2.7e-5 -1.43e-4,4.3e-5 -2.14e-4,7.2e-5 L 0.55255496,5.4627331 a 0.57157718,0.57157718 0 0 0 -0.0024,9.07e-4 c -0.348213,0.133597 -0.557341,0.492923 -0.54996299977,0.819746 0.0074,0.326824 0.23249399977,0.676327 0.58638099977,0.794073 l -0.03433,-0.01263 5.93279904,2.405379 a 0.57157718,0.57157718 0 0 0 0.0337,0.01249 0.57157718,0.57157718 0 0 0 0.01256,0.03391 l 2.407542,5.9327289 -0.01074,-0.0286 c 0.120467,0.349349 0.46672,0.570238 0.791072,0.57759 0.324353,0.0074 0.680155,-0.197641 0.816328,-0.541172 a 0.57157718,0.57157718 0 0 0 0.0032,-0.0083 l 5.406976,-14.2861169 7.1e-5,-2.11e-4 c 0.124551,-0.32591496 0.02418,-0.69350296 -0.194576,-0.91225496 -0.153843,-0.153844 -0.381304,-0.249097 -0.6161,-0.248785 z M 13.352873,1.8402221 6.820368,8.3727271 1.641178,6.2728511 Z M 14.161178,2.6485271 9.729456,14.357781 7.628673,9.1810321 Z"></path></svg> <a class="l10n" href="https://t.me/opensuseusers" data-msg-id="telegram-group" data-url-id="telegram-group-url">Telegram group</a></li><li><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path d="M324,256a36,36,0,1,0,36,36A36,36,0,0,0,324,256Z"></path><circle cx="188" cy="292" r="36" transform="translate(-97.43 94.17) rotate(-22.5)"></circle><path d="M496,253.77c0-31.19-25.14-56.56-56-56.56a55.72,55.72,0,0,0-35.61,12.86c-35-23.77-80.78-38.32-129.65-41.27l22-79L363.15,103c1.9,26.48,24,47.49,50.65,47.49,28,0,50.78-23,50.78-51.21S441,48,413,48c-19.53,0-36.31,11.19-44.85,28.77l-90-17.89L247.05,168.4l-4.63.13c-50.63,2.21-98.34,16.93-134.77,41.53A55.38,55.38,0,0,0,72,197.21c-30.89,0-56,25.37-56,56.56a56.43,56.43,0,0,0,28.11,49.06,98.65,98.65,0,0,0-.89,13.34c.11,39.74,22.49,77,63,105C146.36,448.77,199.51,464,256,464s109.76-15.23,149.83-42.89c40.53-28,62.85-65.27,62.85-105.06a109.32,109.32,0,0,0-.84-13.3A56.32,56.32,0,0,0,496,253.77ZM414,75a24,24,0,1,1-24,24A24,24,0,0,1,414,75ZM42.72,253.77a29.6,29.6,0,0,1,29.42-29.71,29,29,0,0,1,13.62,3.43c-15.5,14.41-26.93,30.41-34.07,47.68A30.23,30.23,0,0,1,42.72,253.77ZM390.82,399c-35.74,24.59-83.6,38.14-134.77,38.14S157,423.61,121.29,399c-33-22.79-51.24-52.26-51.24-83A78.5,78.5,0,0,1,75,288.72c5.68-15.74,16.16-30.48,31.15-43.79a155.17,155.17,0,0,1,14.76-11.53l.3-.21,0,0,.24-.17c35.72-24.52,83.52-38,134.61-38s98.9,13.51,134.62,38l.23.17.34.25A156.57,156.57,0,0,1,406,244.92c15,13.32,25.48,28.05,31.16,43.81a85.44,85.44,0,0,1,4.31,17.67,77.29,77.29,0,0,1,.6,9.65C442.06,346.77,423.86,376.24,390.82,399Zm69.6-123.92c-7.13-17.28-18.56-33.29-34.07-47.72A29.09,29.09,0,0,1,440,224a29.59,29.59,0,0,1,29.41,29.71A30.07,30.07,0,0,1,460.42,275.1Z"></path><path d="M323.23,362.22c-.25.25-25.56,26.07-67.15,26.27-42-.2-66.28-25.23-67.31-26.27h0a4.14,4.14,0,0,0-5.83,0l-13.7,13.47a4.15,4.15,0,0,0,0,5.89h0c3.4,3.4,34.7,34.23,86.78,34.45,51.94-.22,83.38-31.05,86.78-34.45h0a4.16,4.16,0,0,0,0-5.9l-13.71-13.47a4.13,4.13,0,0,0-5.81,0Z"></path></svg> <a class="l10n" href="https://reddit.com/r/openSUSE" data-msg-id="reddit" data-url-id="reddit-url">Reddit</a></li> </ul> </div> <div class="col-6 col-md-4 col-lg-2"> <h5 class="megamenu-heading l10n" data-msg-id="social-media">Social Media</h5> <ul class="megamenu-list"> <li><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 94.023018 100.80365" height="1em" width="1em"><path d="M72.57077 49.00625c-3.9125 0-7.085-3.1825-7.085-7.095 0-3.91125 3.1725-7.09375 7.085-7.09375 3.92125 0 7.09375 3.1825 7.09375 7.09375 0 3.9125-3.1725 7.095-7.09375 7.095m-25.55875 0c-3.9225 0-7.095-3.1825-7.095-7.095 0-3.91125 3.1725-7.09375 7.095-7.09375 3.91125 0 7.09375 3.1825 7.09375 7.09375 0 3.9125-3.1825 7.095-7.09375 7.095m-25.57 0c-3.91125 0-7.08375-3.1825-7.08375-7.095 0-3.91125 3.1725-7.09375 7.08375-7.09375 3.92125 0 7.09375 3.1825 7.09375 7.09375 0 3.9125-3.1725 7.095-7.09375 7.095m72.5775-15.905c0-21.86625-14.32375-28.27375-14.32375-28.27375-7.23-3.31875-19.63-4.7125-32.5175-4.8275h-.3125c-12.88875.115-25.28875 1.50875-32.5075 4.8275 0 0-14.32375 6.4075-14.32375 28.27375 0 5.00375-.105 10.995.05125 17.34C.60577 71.83 4.00702 92.905 23.78327 98.1375c9.1125 2.4125 16.945 2.9125 23.24875 2.56875 11.4225-.63375 17.84-4.07625 17.84-4.07625l-.37375-8.3025s-8.16625 2.58-17.34125 2.2675c-9.09125-.3125-18.6825-.9775-20.16-12.13875-.135-.97875-.1975-2.02875-.1975-3.13125 0 0 8.915 2.185 20.2325 2.69375 6.9075.3225 13.39875-.39375 19.98375-1.185 12.6275-1.50875 23.62375-9.29 25.0075-16.405 2.17375-11.1925 1.99625-27.3275 1.99625-27.3275" fill="currentColor" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""></path></svg> <a class="l10n" href="https://fosstodon.org/@opensuse" data-msg-id="mastodon" data-url-id="mastodon-url">Mastodon</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path d="m 15.135036,0.00147914 c -0.01565,2.1e-5 -0.03132,4.44e-4 -0.04702,0.0013 -0.08366,0.0047 -0.167707,0.02177 -0.249133,0.05288 -7.1e-5,2.7e-5 -1.43e-4,4.3e-5 -2.14e-4,7.2e-5 L 0.55255496,5.4627331 a 0.57157718,0.57157718 0 0 0 -0.0024,9.07e-4 c -0.348213,0.133597 -0.557341,0.492923 -0.54996299977,0.819746 0.0074,0.326824 0.23249399977,0.676327 0.58638099977,0.794073 l -0.03433,-0.01263 5.93279904,2.405379 a 0.57157718,0.57157718 0 0 0 0.0337,0.01249 0.57157718,0.57157718 0 0 0 0.01256,0.03391 l 2.407542,5.9327289 -0.01074,-0.0286 c 0.120467,0.349349 0.46672,0.570238 0.791072,0.57759 0.324353,0.0074 0.680155,-0.197641 0.816328,-0.541172 a 0.57157718,0.57157718 0 0 0 0.0032,-0.0083 l 5.406976,-14.2861169 7.1e-5,-2.11e-4 c 0.124551,-0.32591496 0.02418,-0.69350296 -0.194576,-0.91225496 -0.153843,-0.153844 -0.381304,-0.249097 -0.6161,-0.248785 z M 13.352873,1.8402221 6.820368,8.3727271 1.641178,6.2728511 Z M 14.161178,2.6485271 9.729456,14.357781 7.628673,9.1810321 Z"></path></svg> <a class="l10n" href="https://t.me/opensusenews" data-msg-id="telegram" data-url-id="telegram-url">Telegram</a></li><li><svg width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path d="M455.27,32H56.73A24.74,24.74,0,0,0,32,56.73V455.27A24.74,24.74,0,0,0,56.73,480H256V304H202.45V240H256V189c0-57.86,40.13-89.36,91.82-89.36,24.73,0,51.33,1.86,57.51,2.68v60.43H364.15c-28.12,0-33.48,13.3-33.48,32.9V240h67l-8.75,64H330.67V480h124.6A24.74,24.74,0,0,0,480,455.27V56.73A24.74,24.74,0,0,0,455.27,32Z"></path></svg> <a class="l10n" href="https://www.facebook.com/en.openSUSE" data-msg-id="facebook" data-url-id="facebook-url">Facebook</a></li><li><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path d="M496,109.5a201.8,201.8,0,0,1-56.55,15.3,97.51,97.51,0,0,0,43.33-53.6,197.74,197.74,0,0,1-62.56,23.5A99.14,99.14,0,0,0,348.31,64c-54.42,0-98.46,43.4-98.46,96.9a93.21,93.21,0,0,0,2.54,22.1,280.7,280.7,0,0,1-203-101.3A95.69,95.69,0,0,0,36,130.4C36,164,53.53,193.7,80,211.1A97.5,97.5,0,0,1,35.22,199v1.2c0,47,34,86.1,79,95a100.76,100.76,0,0,1-25.94,3.4,94.38,94.38,0,0,1-18.51-1.8c12.51,38.5,48.92,66.5,92.05,67.3A199.59,199.59,0,0,1,39.5,405.6,203,203,0,0,1,16,404.2,278.68,278.68,0,0,0,166.74,448c181.36,0,280.44-147.7,280.44-275.8,0-4.2-.11-8.4-.31-12.5A198.48,198.48,0,0,0,496,109.5Z"></path></svg> <a class="l10n" href="https://twitter.com/opensuse" data-msg-id="twitter" data-url-id="twitter-url">Twitter</a></li><li><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path d="M508.64,148.79c0-45-33.1-81.2-74-81.2C379.24,65,322.74,64,265,64H247c-57.6,0-114.2,1-169.6,3.6-40.8,0-73.9,36.4-73.9,81.4C1,184.59-.06,220.19,0,255.79q-.15,53.4,3.4,106.9c0,45,33.1,81.5,73.9,81.5,58.2,2.7,117.9,3.9,178.6,3.8q91.2.3,178.6-3.8c40.9,0,74-36.5,74-81.5,2.4-35.7,3.5-71.3,3.4-107Q512.24,202.29,508.64,148.79ZM207,353.89V157.39l145,98.2Z"></path></svg> <a class="l10n" href="https://www.youtube.com/user/opensusetv" data-msg-id="youtube" data-url-id="youtube-url">YouTube</a></li> </ul> </div> <div class="col-6 col-md-4 col-lg-2"> <h5 class="megamenu-heading l10n" data-msg-id="undefined">Other</h5> <ul class="megamenu-list"> <li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2l-2.218-.887zm3.564 1.426L5.596 5 8 5.961 14.154 3.5l-2.404-.961zm3.25 1.7l-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923l6.5 2.6zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464L7.443.184z"></path></svg> <a class="l10n" href="http://packman.links2linux.org/" data-msg-id="packman" data-url-id="packman-url">Packman</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5 8 5.961 14.154 3.5 8.186 1.113zM15 4.239l-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923l6.5 2.6zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464L7.443.184z"></path></svg> <a class="l10n" href="https://kubic.opensuse.org/" data-msg-id="kubic" data-url-id="kubic-url">Kubic</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""><path fill-rule="evenodd" d="M3.214 1.072C4.813.752 6.916.71 8.354 2.146A.5.5 0 0 1 8.5 2.5v11a.5.5 0 0 1-.854.354c-.843-.844-2.115-1.059-3.47-.92-1.344.14-2.66.617-3.452 1.013A.5.5 0 0 1 0 13.5v-11a.5.5 0 0 1 .276-.447L.5 2.5l-.224-.447.002-.001.004-.002.013-.006a5.017 5.017 0 0 1 .22-.103 12.958 12.958 0 0 1 2.7-.869zM1 2.82v9.908c.846-.343 1.944-.672 3.074-.788 1.143-.118 2.387-.023 3.426.56V2.718c-1.063-.929-2.631-.956-4.09-.664A11.958 11.958 0 0 0 1 2.82z"></path><path fill-rule="evenodd" d="M12.786 1.072C11.188.752 9.084.71 7.646 2.146A.5.5 0 0 0 7.5 2.5v11a.5.5 0 0 0 .854.354c.843-.844 2.115-1.059 3.47-.92 1.344.14 2.66.617 3.452 1.013A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.276-.447L15.5 2.5l.224-.447-.002-.001-.004-.002-.013-.006-.047-.023a12.582 12.582 0 0 0-.799-.34 12.96 12.96 0 0 0-2.073-.609zM15 2.82v9.908c-.846-.343-1.944-.672-3.074-.788-1.143-.118-2.387-.023-3.426.56V2.718c1.063-.929 2.631-.956 4.09-.664A11.956 11.956 0 0 1 15 2.82z"></path></svg> <a class="l10n" href="https://opensuse-guide.org/" data-msg-id="guide-unofficial" data-url-id="guide-unofficial-url">Guide (unofficial)</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path d="M4.887 5.2l-.964-.165A2.5 2.5 0 1 0 3.5 10H6v1H3.5a3.5 3.5 0 1 1 .59-6.95 5.002 5.002 0 1 1 9.804 1.98A2.501 2.501 0 0 1 13.5 11H10v-1h3.5a1.5 1.5 0 0 0 .237-2.981L12.7 6.854l.216-1.028a4 4 0 1 0-7.843-1.587l-.185.96z"></path> <path fill-rule="evenodd" d="M5 12.5a.5.5 0 0 1 .707 0L8 14.793l2.293-2.293a.5.5 0 1 1 .707.707l-2.646 2.646a.5.5 0 0 1-.708 0L5 13.207a.5.5 0 0 1 0-.707z"></path> <path fill-rule="evenodd" d="M8 6a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0v-8A.5.5 0 0 1 8 6z"></path> </svg> <a class="l10n" href="https://mirrors.opensuse.org/" data-msg-id="mirrors" data-url-id="mirrors-url">Mirrors</a></li><li><svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="--darkreader-inline-fill: currentColor;" data-darkreader-inline-fill=""> <path fill-rule="evenodd" d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm5 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm-5 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"></path> <path d="M3.734 6.352a6.586 6.586 0 0 0-.445.275 1.94 1.94 0 0 0-.346.299 1.38 1.38 0 0 0-.252.369c-.058.129-.1.295-.123.498h.282c.242 0 .431.06.568.182.14.117.21.29.21.521a.697.697 0 0 1-.187.463c-.12.14-.289.21-.503.21-.336 0-.577-.108-.721-.327C2.072 8.619 2 8.328 2 7.969c0-.254.055-.485.164-.692.11-.21.242-.398.398-.562.16-.168.33-.31.51-.428.18-.117.33-.213.451-.287l.211.352zm2.168 0a6.588 6.588 0 0 0-.445.275 1.94 1.94 0 0 0-.346.299c-.113.12-.199.246-.257.375a1.75 1.75 0 0 0-.118.492h.282c.242 0 .431.06.568.182.14.117.21.29.21.521a.697.697 0 0 1-.187.463c-.12.14-.289.21-.504.21-.335 0-.576-.108-.72-.327-.145-.223-.217-.514-.217-.873 0-.254.055-.485.164-.692.11-.21.242-.398.398-.562.16-.168.33-.31.51-.428.18-.117.33-.213.451-.287l.211.352z"></path> </svg> <a class="l10n" href="https://lizards.opensuse.org/" data-msg-id="lizards" data-url-id="lizards-url">Lizards</a></li> </ul> </div> </div> </div> </div> <!-- END of openSUSE navbar --> <div class="flex-fill py-5"> <div class="container" style="max-width:80%"> <!-- override width for readability --> <div class="row"> <div class="col-lg-9 toc-scope"> <h1>Out of Memory Analyser</h1> <p> OOMAnalyser is a small project to transform the OOM message of a Linux kernel into a more user-friendly format. </p> <p> OOMAnalyser consists of a web page into whose input field the OOM message is copied. JavaScript code extracts the data from it and displays the details. All processing takes place in the browser. No data is transferred to external servers. </p> <p> <div class="terminal notify_box js-text--default-hide js-text--display-none" id="notify_box"></div> <div class="js-text--default-show" id="input"> <h2 id="step1">Step 1 - Enter your OOM message</h2> <textarea autocomplete="off" cols="80" id="textarea_oom" placeholder="<Paste your OOM log here, drag & drop a file or use the file dialog below>" rows="20" title="OOM input field"></textarea> <br/> <div> <input accept=".txt,.log" onchange="read_and_display_file(this.files[0])" type="file"> </div> <br/> <button onclick="OOMAnalyser.OOMDisplayInstance.analyse_and_show()" title="Analyse the OOM from the input area and show it" type="button">Analyse<br>OOM block</button> <button onclick="OOMAnalyser.OOMDisplayInstance.reset_form()" title="Clean the input area" type="reset">Reset<br>form</button> <button onclick="OOMAnalyser.OOMDisplayInstance.copy_example_tumbleweed_swap_to_form()" title="Copy an example OOM trace from Tumbleweed w/ swap into the input area" type="button"> Insert Tumbleweed example<br>swap enabled </button> <button onclick="OOMAnalyser.OOMDisplayInstance.copy_example_tumbleweed_noswap_to_form()" title="Copy an example OOM trace from Tumbleweed w/o swap into the input area" type="button"> Insert Tumbleweed example<br>swap disabled </button> </div> <div class="js-text--default-hide js-text--display-none" id="analysis"> <h2 id="step2">Step 2 - Results</h2> <p> Go back to <a href="javascript:void(0);" onclick="OOMAnalyser.OOMDisplayInstance.reset_form()" title="Run a new analysis">"Step 1 - Enter your OOM message"</a> to run a new analysis. </p> <h3>Summary</h3> <div id="explanation"> <div class="js-text--default-hide js-text--display-none js-oom-automatic--show"> <p> The OOM killer was automatically triggered to free memory, because the system couldn't satisfy the memory request. The OOM killer calculates a score for each process and terminates the process with the highest score to satisfy the original memory request. </p> </div> <div class="js-text--default-hide js-text--display-none js-oom-manual--show"> <p> The OOM killer was manually triggered (e.g. with "<code>echo f > /proc/sysrq-trigger</code>") by a user with root privileges. There is no demand to free memory but the OOM killer started nevertheless. The OOM killer calculates a score for each process and terminates the process with the highest score. </p> </div> <div class="js-text--default-hide js-text--display-none js-swap-active--show"> <p> The system has <span class="system_total_ram_kb"></span> physical memory and <span class="swap_total_kb"></span> swap space. That's <span class="system_total_ramswap_kb"></span> total. <span class="system_total_used_percent"></span> (<span class="system_total_ram_used_kb"></span> out of <span class="system_total_ram_kb"></span>) physical memory and <span class="system_swap_used_percent"></span> (<span class="swap_used_kb"></span> out of <span class="swap_total_kb"></span>) swap space are in use. </p> </div> <div class="js-text--default-hide js-text--display-none js-swap-inactive--show"> <p> The system has <span class="system_total_ram_kb"></span> physical memory and no swap space. <span class="system_total_used_percent"></span> (<span class="system_total_ram_used_kb"></span> out of <span class="system_total_ram_kb"></span>) physical memory are in use. </p> </div> <div class="js-text--default-hide js-text--display-none js-oom-automatic--show"> <p> The process "<span class="trigger_proc_name"></span>" (PID <span class="trigger_proc_pid"></span>) requested a memory chunk of order <span class="trigger_proc_order"></span> from the "<span class="trigger_proc_mem_zone"></span>" memory zone. This is 2<span class="text__superscript">order</span> pages == 2<span class="trigger_proc_order text__superscript"></span> pages == <span class="trigger_proc_requested_memory_pages"></span> a <span class="page_size_kb"></span> == <span class="trigger_proc_requested_memory_pages_kb"></span> memory. </p> </div> <div class="js-text--default-hide js-text--display-none js-alloc-failure--show"> <p> <span class="js-text--default-hide js-text--display-none js-alloc-failure-below-low-watermark--show"> The request failed because the free memory would be below the memory low watermark after its completion. </span> <span class="js-text--default-hide js-text--display-none js-alloc-failure-no-free-chunks--show"> If this requirement was satisfied, the free memory would still be above the low memory watermark. The request failed because there is no free chunk in the current or higher order. </span> <span class="js-text--default-hide js-text--display-none js-alloc-failure-unknown-reason-show"> The request failed, but the reason is unknown. </span> This memory shortage triggers the OOM process. </p> </div> <p> The process "<span class="killed_proc_name"></span>" (PID <span class="killed_proc_pid"></span>) <span class="js-text--default-hide js-text--display-none js-killed-proc-score--show"> with an OOM score of <span class="killed_proc_score"></span> </span> has been terminated. It uses <span class="killed_proc_rss_percent"></span> (<span class="killed_proc_total_rss_kb"></span>) of the resident memory. </p> <div class="js-text--default-hide js-text--display-none js-memory-fragmentation--show"> <p> Dynamic memory allocation is used by both the kernel and all applications. This leads to memory fragmentation and is a common behavior. <span class="js-text--default-hide js-text--display-none js-memory-heavy-fragmentation--show"> The system memory is heavily fragmented, because all chunks with an order ≥ <span class="kconfig.PAGE_ALLOC_COSTLY_ORDER"></span> are in use. Allocation of larger contiguous memory areas will fail. </span> <span class="js-text--default-hide js-text--display-none js-memory-no-heavy-fragmentation--show"> The system memory is not heavily fragmented, because chunks with an order ≥ <span class="kconfig.PAGE_ALLOC_COSTLY_ORDER"></span> are freely available. </span> </p> </div> </div> <h3>Details of analysis</h3> <p> The result of the analysis is displayed in three columns. The first column is used to name the property including the original OOM identifier in brackets. The extracted information is displayed in the second column. The last column contains further details and additional information. </p> <table class="result__table"> <colgroup> <col class="result__table--size-col-1"> <col> <col> </colgroup> <!-- Trigger process --> <tr> <th colspan="3" scope="row">Trigger Process</th> </tr> <tr class="js-oom-automatic--show"> <td></td> <td class="text--align-right"><span class="trigger_proc_name"></span> (PID <span class="trigger_proc_pid"></span>)</td> <td>This process requests memory and is triggering thereby the OOM situation.</td> </tr> <tr> <td>Memory allocation flags<br>(gfp_mask)</td> <td class="trigger_proc_gfp_mask text--align-right"></td> <td>These flags are also called GFP (get free pages) flags. They control the kernel-internal memory allocation.<br> Some OOM variants include the individual flags in addition to the hexadecimal representation. The flags are calculated if they are not specified. </td> </tr> <tr> <td>Node mask to show on which CPU Cores this process can run<br>(nodemask)</td> <td class="trigger_proc_nodemask text--align-right"></td> <td>Bit mask indicating the cores on which the process can run.</td> </tr> <tr class="js-oom-automatic--show"> <td>Requested memory: order</td> <td class="text--align-right"> <span class="trigger_proc_order"></span> </td> <td>The kernel specifies the requested number of pages as an exponent of a power of two.</td> </tr> <tr class="js-oom-automatic--show"> <td>Requested memory: number of pages</td> <td class="text--align-right"> <span class="trigger_proc_requested_memory_pages"></span> (2<span class="trigger_proc_order text__superscript"></span> pages) a <span class="page_size_kb"></span> per page </td> <td>Requested memory in number of pages.</td> </tr> <tr class="js-oom-automatic--show"> <td>Requested memory: size</td> <td class="text--align-right"><span class="trigger_proc_requested_memory_pages_kb"></span></td> <td>Requested memory in kBytes.</td> </tr> <tr class="js-oom-automatic--show"> <td>Requested memory: zone</td> <td class="trigger_proc_mem_zone text--align-right"></td> <td>Memory zone from which the requested storage chunk should come.</td> </tr> <tr class="js-text--default-hide js-oom-automatic--show js-memory-shortage-node--hide"> <td>Requested memory: node</td> <td class="trigger_proc_numa_node text--align-right"></td> <td> First NUMA node with memory shortage watermark "<code>free</code>" < "<code>min</code>" in memory watermark information. <br> Assumption that this is the node where the OOM was triggered. </td> </tr> <tr> <td>Adjust oom-killer score<br>(oom_score_adj)</td> <td class="trigger_proc_oomscore text--align-right"></td> <td> This value is added to the badness score before it's used to determine the process to be killed. </td> </tr> <!-- Killed Process --> <tr> <th scope="row" colspan="3">Killed Process</th> </tr> <tr> <td></td> <td class="text--align-right"> <span class="killed_proc_name"></span> (PID <span class="killed_proc_pid"></span>) </td> <td>Process killed by Linux kernel to satisfy the memory request.</td> </tr> <tr class="js-text--default-show js-killed-proc-score--show"> <td>OOM Score<br>(score)</td> <td class="killed_proc_score text--align-right"></td> <td>Programs with the highest OOM score are terminated first.</td> </tr> <tr> <td>Virtual Memory <br> (total_vm) </td> <td class="killed_proc_total_vm_kb text--align-right"></td> <td>Virtual memory used by this process.</td> </tr> <tr> <td>Total resident anonymous memory <br> (rss)</td> <td class="killed_proc_total_rss_kb text--align-right"></td> <td> All virtual process memory mapped into RAM. Normally, a process always requests more virtual memory than it uses. <br> <code>TotalRSS = anon-rss + file-rss + shmem-rss</code> <br> </td> </tr> <tr> <td>Resident anonymous memory <br> (anon-rss)</td> <td class="killed_proc_anon_rss_kb text--align-right"></td> <td>Resident anonymous pages <br> Part of the virtual process memory mapped into RAM.</td> </tr> <tr> <td>Resident file mapping memory <br> (file-rss)</td> <td class="killed_proc_file_rss_kb text--align-right"></td> <td> Resident file mapping pages <br> Files which have been mapped into RAM (with <a href="https://manpages.opensuse.org/Tumbleweed/man-pages/mmap.2.en.html">mmap(2).</a>) </td> </tr> <tr> <td>Resident shared memory <br> (shmem-rss)</td> <td class="killed_proc_shmem_rss_kb text--align-right"></td> <td> Resident shared memory pages <br> This may include System V shared memory and shared anonymous memory. </td> </tr> <!-- Memory Usage --> <tr> <th colspan="3" scope="row">Memory Usage</th> </tr> <!-- Graphs --> <tr> <th class="table__sub-section--bold" colspan="3" scope="row">Graphs</th> </tr> <tr> <td>RAM Summary</td> <td class="result__table--border" colspan="2"><div id="svg_ram"></div></td> </tr> <tr class="js-text--default-show js-swap-active--show"> <td>Swap Summary</td> <td class="result__table--border" colspan="2"><div id="svg_swap"></div></td> </tr> <!-- Swap Usage --> <tr class="js-text--default-show"> <th class="table__sub-section--bold" colspan="3" scope="row">Swap Usage</th> </tr> <tr class="js-text--default-show js-swap-inactive--show"> <td colspan="3">No swap space available</td> </tr> <tr class="js-text--default-show js-swap-active--show"> <td>Swap Total</td> <td class="swap_total_kb text--align-right"></td> <td>Total amount of swap space available. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr class="js-text--default-show js-swap-active--show"> <td>Swap Free</td> <td class="swap_free_kb text--align-right"></td> <td>Amount of swap space that is currently unused. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr class="js-text--default-show js-swap-active--show"> <td>Swap Cached</td> <td class="swap_cache_kb text--align-right"></td> <td>Memory that once was swapped out, is swapped back in but still also is in the swap file. (If memory pressure is high, these pages don't need to be swapped out again because they are already in the swap file. This saves I/O). <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr class="js-text--default-show js-swap-active--show"> <td>Swap Used</td> <td class="swap_used_kb text--align-right"></td> <td>Amount of used swap space w/o cached swap <br> (<code>SwapUsed = SwapTotal - SwapFree -SwapCache</code>) </td> </tr> <!-- Page Usage --> <tr> <th class="table__sub-section--bold" colspan="3" scope="row">Memory Pages</th> </tr> <tr> <td>RAM pages</td> <td class="ram_pages text--align-right"></td> <td>Total number of RAM pages</td> </tr> <tr> <td>HighMem/MovableOnly</td> <td class="highmem_pages text--align-right"></td> <td>Number of pages in the High Memory Area or marked movable for Contiguous Memory Allocator (CMA). <br> HighMem pages are also counted in the total page number. </td> </tr> <tr> <td>Reserved pages</td> <td class="reserved_pages text--align-right"></td> <td>Number of reserved pages</td> </tr> <tr> <td>CMA reserved pages</td> <td class="cma_pages text--align-right">0</td> <td>Pages reserved for Contiguous Memory Allocator (CMA)</td> </tr> <tr> <td>Pagetable Cache</td> <td class="pagetablecache_pages text--align-right">0</td> <td>Number of pages in pagetable cache</td> </tr> <tr> <td>Number of pages with hardware errors</td> <td class="hwpoisoned_pages text--align-right">0</td> <td>Pages with uncorrectable memory errors</td> </tr> <!-- Memory Usage Details --> <tr> <th class="table__sub-section--bold" colspan="3" scope="row">Memory Usage Details</th> </tr> <tr> <td>Active anonymous memory <br> (active_anon)</td> <td class="active_anon_pages text--align-right"></td> <td>Recently used anonymous memory.<br> These memory pages will usually not swapped out. </td> </tr> <tr> <td>Inactive anonymous memory <br> (inactive_anon)</td> <td class="inactive_anon_pages text--align-right"></td> <td>Least recently used anonymous memory.<br> These memory pages can be swapped out. </td> </tr> <tr> <td>Isolated anonymous memory <br> (isolated_anon)</td> <td class="isolated_anon_pages text--align-right"></td> <td>Memory isolation is used to separate memory between different virtual machines.</td> </tr> <tr> <td>Active Pagecache <br> (active_file)</td> <td class="active_file_pages text--align-right"></td> <td>Pagecache that has been used more recently and usually not reclaimed unless absolutely necessary.</td> </tr> <tr> <td>Inactive Pagecache <br> (inactive_file)</td> <td class="inactive_file_pages text--align-right"></td> <td>Pagecache which has been less recently used. It can be reclaimed without huge performance impact.</td> </tr> <tr> <td>Isolated Pagecache <br> (isolated_file)</td> <td class="isolated_file_pages text--align-right"></td> <td>Memory isolation is used to separate memory between different virtual machines.</td> </tr> <tr> <td>Unevictable Pages <br> (unevictable)</td> <td class="unevictable_pages text--align-right"></td> <td>Unevictable memory. It can't be swapped out because the pages are owned by ramfs or protected by <a href="https://manpages.opensuse.org/Tumbleweed/man-pages/mlock.2.en.html" target="_blank">mlock(2)</a> / <a href="https://manpages.opensuse.org/Tumbleweed/man-pages/shmctl.2.en.html" target="_blank">shmctl(2)</a>. Unevictable pages are managed by kernels LRU framework. </td> </tr> <tr> <td>Dirty Pages <br> (dirty)</td> <td class="dirty_pages text--align-right"></td> <td>Memory which is waiting to get written back to the disk. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr> <td>Writeback <br> (writeback)</td> <td class="writeback_pages text--align-right"></td> <td> Memory which is actively being written back to the disk. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr> <td>Unstable <br> (unstable)</td> <td class="unstable_pages text--align-right"></td> <td>Not yet committed to stable storage.</td> </tr> <tr> <td>Slab Reclaimable <br> (slab_reclaimable)</td> <td class="slab_reclaimable_pages text--align-right"></td> <td> Slab is a in-kernel data structures cache. Part of Slab, that might be reclaimed, such as caches. <a class="a__footnote" href="#footnote-proc5">[1]</a> <br> Additional details are listed in <a href="https://manpages.opensuse.org/Tumbleweed/man-pages/slabinfo.5.en.html" target="_blank">slabinfo(5)</a> also. </td> </tr> <tr> <td>Slab Unreclaimable <br> (slab_unreclaimable)</td> <td class="slab_unreclaimable_pages text--align-right"></td> <td> Part of Slab, that cannot be reclaimed on memory pressure. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr> <td>Mapped <br> (mapped)</td> <td class="mapped_pages text--align-right"></td> <td> Files which have been mapped into memory (with <a href="https://manpages.opensuse.org/Tumbleweed/man-pages/mmap.2.en.html">mmap(2)</a>), such as libraries. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr> <td>Shared Memory <br> (shmem)</td> <td class="shmem_pages text--align-right"></td> <td> Amount of memory consumed in <a href="https://manpages.opensuse.org/Tumbleweed/man-pages/tmpfs.5.en.html">tmpfs(5)</a> filesystems. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr> <td>Pagetables <br> (pagetables)</td> <td class="pagetables_pages text--align-right"></td> <td> Amount of memory dedicated to the lowest level of pagetables. <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr> <td>Bounce <br> (bounce)</td> <td class="bounce_pages text--align-right"></td> <td> Memory used for block device "bounce buffers". <a class="a__footnote" href="#footnote-proc5">[1]</a> </td> </tr> <tr> <td>Free pages <br> (free)</td> <td class="free_pages text--align-right"></td> <td>Free pages</td> </tr> <tr> <td>Free per-CPU pages <br> (free_pcp)</td> <td class="free_pcp_pages text--align-right"></td> <td>Free number of pages per CPU</td> </tr> <tr> <td>Free CMA pages <br> (free_cma)</td> <td class="free_cma_pages text--align-right"></td> <td>Pages reserved but not used by Contiguous Memory Allocator (CMA)</td> </tr> <tr> <td>Total Pagecache</td> <td class="pagecache_total_pages text--align-right"></td> <td>Total number of pages in pagecache</td> </tr> <!-- Operating System --> <tr> <th scope="row" colspan="3">Operating System</th> </tr> <tr> <td>Kernel</td> <td class="kernel_version text--align-right text--align-right"></td> <td></td> </tr> <tr> <td>Distribution</td> <td class="dist text--align-right text--align-right"></td> <td></td> </tr> <tr> <td>Platform</td> <td class="platform text--align-right text--align-right"></td> <td>Guessed from the kernel version</td> </tr> <tr> <td>Page size</td> <td class="page_size_kb text--align-right"></td> <td> <span class="js-text--default-hide js-text--display-none js-pagesize-determined--show"> Extracted from DMA zone buddyinfo </span> <span class="js-text--default-hide js-text--display-none js-pagesize-guessed--show"> Guessed </span> </td> </tr> <!-- Memory Chunks --> <tr> <th colspan="3" scope="row">Available Memory Chunks</th> </tr> <tr> <td></td> <td colspan="2" class="terminal"> <pre class="mem_node_info"></pre> </td> </tr> <!-- Memory watermarks --> <tr> <th colspan="3" scope="row">Memory Watermarks </th> </tr> <tr> <td></td> <td class="terminal" colspan="2"> <pre class="mem_watermarks"></pre> </td> </tr> <tr> <th colspan="3" scope="row">Process Table</th> </tr> <tr> <td></td> <td class="terminal " colspan="2"> <table class="pstable__table--noborder"> <thead id="pstable_header"> <tr> <td class="pstable__row-numeric--width"><span>pid</span> <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col0" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(0)"></a> </td> <td class="pstable__row-numeric--width">uid <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col1" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(1)"></a> </td> <td class="pstable__row-numeric--width">tgid <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col2" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(2)"></a> </td> <td class="pstable__row-pages--width">total_vm <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col3" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(3)"></a> </td> <td class="pstable__row-pages--width">rss <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col4" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(4)"></a> </td> <td class="pstable__row-pages--width">nr_ptes <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col5" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(5)"></a> </td> <td class="pstable__row-pages--width">swapents <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col6" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(6)"></a> </td> <td class="pstable__row-oom-score-adj--width">oom_score_adj <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col7" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(7)"></a> </td> <td class="pstable__row-notes--width">name <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col8" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(8)"></a> </td> <td class="pstable__row-notes--width">notes <a class="pstable__row-sort--width" href="javascript:void(0);" id="js-pstable_sort_col9" onclick="OOMAnalyser.OOMDisplayInstance.sort_pstable(9)"></a> </td> </tr> </thead> <tbody id="pstable_content"> </tbody> </table> </td> </tr> <!-- Hardware Details --> <tr> <th colspan="3" scope="row">Hardware Details</th> </tr> <tr> <td></td> <td colspan="2" class="terminal"> <pre class="hardware_info"></pre> </td> </tr> <!-- Kernel Call Tree --> <tr> <th colspan="3" scope="row">Kernel Call Trace</th> </tr> <tr> <td></td> <td colspan="2" class="terminal"> <pre class="call_trace"></pre> </td> </tr> <!-- Initial OOM --> <tr> <th colspan="3" scope="row">Entire OOM Message <a class="a--small" href="javascript:void(0);" id="oom_toogle_msg" onclick="OOMAnalyser.OOMDisplayInstance.toggle_oom()" title="Click to show/hide full OOM message">(click to hide)</a> </th> </tr> <tr> <td></td> <td colspan="2" class="terminal"> <pre id="oom"></pre> </td> </tr> </tbody> </table> <p> Go back to <a href="javascript:void(0);" onclick="OOMAnalyser.OOMDisplayInstance.reset_form()" title="Run a new analysis">"Step 1 - Enter your OOM message"</a> to run a new analysis. </p> <h2 id="h2-footnotes">Footnotes</h2> <ol> <li id="footnote-proc5"><cite><a href="https://manpages.opensuse.org/Tumbleweed/man-pages/proc.5.en.html" target="_blank"> proc(5) - process information pseudo-filesystem</a></cite> (<a href="#" onclick="goBack()">Go Back</a>) </li> </ol> </div> <h2 id="infos">Further Information</h2> <ol> <li> <a href="https://man.opensuse.org/">openSUSE man pages online</a> </li> <li> <a href="https://utcc.utoronto.ca/~cks/space/blog/linux/DecodingPageAllocFailures"> Decoding the Linux kernel's page allocation failure messages </a> </li> <li> <a href="https://github.com/openSUSE/kernel">Kernel Source code</a> with <a href="https://github.com/openSUSE/kernel/blob/master/mm/oom_kill.c"><code>mm/omm_kill.c</code></a> </li> </ol> </div> </div> </div> </div> <div class="js-text--default-hide js-text--display-none" id="changelog"> <h3>Version 0.6.0 - 2023-XX-XX:</h3> <h4>General</h4> <ol> <li>Fix memory calculation in summary section</li> <li>Fix and rework calculation of GFP flags</li> <li>Add GFP flags for more kernel releases</li> <li>Display missing memory chunks (buddyinfo) again</li> <li>Add analysis why the memory request failed</li> <li>Add check for heavy memory fragmentation</li> <li>Summary of the analysis revised</li> <li>Show memory watermarks together will all details</li> <li>Add support for kernel 6.0 and newer</li> <li>...</li> </ol> <h4>Note</h4> See the <a href="https://github.com/CarstenGrohmann/OOMAnalyser/compare/v0.5.0...master" title="commit history">commit history</a> of the repository for a full list of changes. <h3>Version 0.5.0 - 2022-01-12:</h3> <h4>General</h4> <ol> <li>Improve SVG chart colour palette</li> <li>Add Selenium based unit tests</li> <li>Fix to allow process names with spaces</li> <li>Rework removal of unused information</li> <li>Report uncaught errors to the user</li> <li>Add support for manually triggered OOM (suggested by Mikko Rantalainen)</li> <li>Add support for systems w/o swap (suggested by Mikko Rantalainen)</li> <li>Add support for newer kernels (suggested by Mikko Rantalainen)</li> <li>Add support for journalctl output (suggested by Mikko Rantalainen)</li> <li>Add support for newer process table format</li> <li>Rework memory charts to show all items in legend</li> </ol> <h4>Note</h4> See the <a href="https://github.com/CarstenGrohmann/OOMAnalyser/compare/v0.4.0...v0.5.0" title="commit history">commit history</a> of the repository for a full list of changes. <h3>Version 0.4.0 - 2020-12-10:</h3> <h4>General</h4> <ol> <li>Add a textual summary of the analysis</li> <li>Fix calculation of requested memory in kBytes</li> <li>Fix issue that prevents units from being copied</li> <li>Show additional information in process table</li> <li>Add sorting process table</li> <li>Fix: Trigger process isn't part of process table</li> <li>Update to Transcrypt 3.7</li> <li>Line "Killed process" can contain the process UID</li> <li>Add drag-and-drop support for files</li> <li>Add missing explanations</li> <li>Allow more characters in program name</li> <li>Bug fixes</li> </ol> <h4>Note</h4> See the <a href="https://github.com/CarstenGrohmann/OOMAnalyser/compare/v0.3.0...v0.4.0" title="commit history">commit history</a> of the repository for a full list of changes. <h3>Version 0.3.0 - 2019-11-24:</h3> <h4>General</h4> <ol> <li>Improve presentation</li> <li>Separate analysis and visualisation code</li> <li>Use CSS classes to control the visibility</li> <li>Strip columns left to the message automatically</li> <li>Lot if internal improvements and restructuring</li> <li>Bug fixes</li> </ol> <!-- <h4>Known issues</h4> <ol> <li>none</li> </ol> --> <h4>Note</h4> See the <a href="https://github.com/CarstenGrohmann/OOMAnalyser/compare/v0.2.0...v0.3.0" title="commit history">commit history</a> of the repository for a full list of changes. </div> <div class="js-text--default-hide js-text--display-none" id="installation"> Installing OOMAnalyser is quite easy since OOMAnalyser consists only of two files, a HTML file and a JavaScript file. Both can be stored locally to use OOMAnalyser without an Internet connection. <h3>Installation steps</h3> <ol> <li>Download the <a download="OOMAnalyser.html" href="OOMAnalyser.html">HTML file</a> and the <a download="OOMAnalyser.js" href="OOMAnalyser.js">JavaScript file</a> to the main directory </li> <li>Open the file <code>OOMAnalyser.html</code> in your favourite browser.</li> </ol> </div> <hr/> <div class="footer"> OOMAnalyser <span id="version"></span> | Copyright (C) 2017-2023 Carsten Grohmann | <a href="javascript:void(0);" onclick="OOMAnalyser.toggle('license')" title="Show / hide license text">License: MIT</a> | <a href="https://sr.ht/~carstengrohmann/OOMAnalyser/" title="Source code on Sourcehut">Source Code on Sourcehut</a> </div> <div class="license__text js-text--display-none" id="license"> <p> Copyright (c) 2017-2023 Carsten Grohmann mail <add at here> carstengrohmann.de </p> <p> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: </p> <p> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. </p> <p> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </p> </div> <svg style="display:none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <symbol id="svg_array_updown" viewBox="0 0 8 11"> <polygon points="0,5 8,5 4,0"/> <polygon points="0,6 8,6 4,11"/> </symbol> <symbol id="svg_array_up" viewBox="0 0 8 11"> <polygon points="0,5 8,5 4,0"/> </symbol> <symbol id="svg_array_down" viewBox="0 0 8 11"> <polygon points="0,6 8,6 4,11"/> </symbol> </svg> </body> </html> 07070100000006000081A4000000000000000000000001643C16290002F0B2000000000000000000000000000000000000001B00000000oom-o-o-0.1/OOMAnalyser.py# -*- coding: Latin-1 -*- # # Linux OOMAnalyser # # Copyright (c) 2017-2023 Carsten Grohmann # License: MIT (see LICENSE.txt) # THIS PROGRAM COMES WITH NO WARRANTY import math import re DEBUG = False """Show additional information during the development cycle""" VERSION = "0.6.0 (devel)" """Version number""" # __pragma__ ('skip') # MOC objects to satisfy statical checker and imports in unit tests js_undefined = 0 class classList: def add(self, *args, **kwargs): pass def remove(self, *args, **kwargs): pass def toggle(self, *args, **kwargs): pass class document: def querySelectorAll( self, *args, ): return [Node()] @staticmethod def getElementsByClassName(names): """ Returns an array-like object of all child elements which have all the given class name(s). @param names: A string representing the class name(s) to match; multiple class names are separated by whitespace. @type names: List(str) @rtype: List(Node) """ return [Node()] @staticmethod def getElementById(_id): """ Returns an object representing the element whose id property matches @type _id: str @rtype: Node """ return Node() @staticmethod def createElementNS(namespaceURI, qualifiedName, *arg): """ Creates an element with the specified namespace URI and qualified name. @param str namespaceURI: Namespace URI to associate with the element @param str qualifiedName: Type of element to be created @rtype: Node """ return Node() @staticmethod def createElement(tagName, *args): """ Creates the HTML element specified by tagName. @param str tagName: Type of element to be created @rtype: Node """ return Node() class Node: classList = classList() id = None offsetWidth = 0 textContent = "" def __init__(self, nr_children=1): self.nr_children = nr_children @property def firstChild(self): if self.nr_children: self.nr_children -= 1 return Node(self.nr_children) else: return None def removeChild(self, *args, **kwargs): return def appendChild(self, *args, **kwargs): return def setAttribute(self, *args, **kwargs): return @property def parentNode(self): return super().__new__(self) # __pragma__ ('noskip') class OOMEntityState: """Enum for completeness of the OOM block""" unknown = 0 empty = 1 invalid = 2 started = 3 complete = 4 class OOMEntityType: """Enum for the type of the OOM""" unknown = 0 automatic = 1 manual = 2 class OOMMemoryAllocFailureType: """Enum to store the results why the memory allocation could have failed""" not_started = 0 """Analysis not started""" missing_data = 1 """Missing data to start analysis""" failed_below_low_watermark = 2 """Failed, because after satisfying this request, the free memory will be below the low memory watermark""" failed_no_free_chunks = 3 """Failed, because no suitable chunk is free in the current or any higher order.""" failed_unknown_reason = 4 """Failed, but the reason is unknown""" skipped_high_order_dont_trigger_oom = 5 """"high order" requests don't trigger OOM""" def is_visible(element): return element.offsetWidth > 0 and element.offsetHeight > 0 def hide_element(element_id): """Hide the given HTML element""" element = document.getElementById(element_id) 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") 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") 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") def toggle(element_id): """Toggle the visibility of the given HTML element""" element = document.getElementById(element_id) element.classList.toggle("js-text--display-none") def escape_html(unsafe): """ Escape unsafe HTML entities @type unsafe: str @rtype: str """ return ( unsafe.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace('"', """) .replace("'", "'") ) def debug(msg): """Add debug message to the notification box""" add_to_notifybox("DEBUG", msg) def error(msg): """Show the notification box and add the error message""" add_to_notifybox("ERROR", msg) def internal_error(msg): """Show the notification box and add the internal error message""" add_to_notifybox("INTERNAL ERROR", msg) def warning(msg): """Show the notification box and add the warning message""" add_to_notifybox("WARNING", msg) def add_to_notifybox(prefix, msg): """ Escaped and add message to the notification box If the message has a prefix "ERROR" or "WARNING" the notification box will be shown. """ if prefix == "DEBUG": css_class = "js-notify_box__msg--debug" elif prefix == "WARNING": css_class = "js-notify_box__msg--warning" else: css_class = "js-notify_box__msg--error" if prefix != "DEBUG": show_element("notify_box") notify_box = document.getElementById("notify_box") notification = document.createElement("div") notification.classList.add(css_class) notification.innerHTML = "{}: {}<br>".format(prefix, escape_html(msg)) notify_box.appendChild(notification) class BaseKernelConfig: """Base class for all kernel specific configuration""" name = "Base configuration for all kernels based on vanilla kernel 3.10" """Name/description of this kernel configuration""" EXTRACT_PATTERN = None """ Instance specific dictionary of RE pattern to analyse a OOM block for a specific kernel version This dict will be filled from EXTRACT_PATTERN_BASE and EXTRACT_PATTERN_OVERLAY during class constructor is executed. :type: None|Dict :see: EXTRACT_PATTERN_BASE and EXTRACT_PATTERN_OVERLAY """ EXTRACT_PATTERN_BASE = { "invoked oom-killer": ( r"^(?P<trigger_proc_name>[\S ]+) invoked oom-killer: " r"gfp_mask=(?P<trigger_proc_gfp_mask>0x[a-z0-9]+)(\((?P<trigger_proc_gfp_flags>[A-Z_|]+)\))?, " r"(nodemask=(?P<trigger_proc_nodemask>([\d,-]+|\(null\))), )?" r"order=(?P<trigger_proc_order>-?\d+), " r"oom_score_adj=(?P<trigger_proc_oomscore>\d+)", True, ), "Trigger process and kernel version": ( r"^CPU: \d+ PID: (?P<trigger_proc_pid>\d+) " r"Comm: .* (Not tainted|Tainted:.*) " r"(?P<kernel_version>\d[\w.-]+) (?:#\d) (?P<distribution>\w+ \w+).+", True, ), # split caused by a limited number of iterations during converting PY regex into JS regex # Source: mm/page_alloc.c:__show_free_areas() "Overall Mem-Info (part 1)": ( r"^Mem-Info:.*" r"(?:\n)" # first line (starting w/o a space) r"^active_anon:(?P<active_anon_pages>\d+) inactive_anon:(?P<inactive_anon_pages>\d+) " r"isolated_anon:(?P<isolated_anon_pages>\d+)" r"(?:\n)" # remaining lines (w/ leading space) r"^ active_file:(?P<active_file_pages>\d+) inactive_file:(?P<inactive_file_pages>\d+) " r"isolated_file:(?P<isolated_file_pages>\d+)" r"(?:\n)" r"^ unevictable:(?P<unevictable_pages>\d+) dirty:(?P<dirty_pages>\d+) writeback:(?P<writeback_pages>\d+) " r"unstable:(?P<unstable_pages>\d+)", True, ), "Overall Mem-Info (part 2)": ( r"slab_reclaimable:(?P<slab_reclaimable_pages>\d+) slab_unreclaimable:(?P<slab_unreclaimable_pages>\d+)" r"(?:\n)" r" +mapped:(?P<mapped_pages>\d+) shmem:(?P<shmem_pages>\d+) pagetables:(?P<pagetables_pages>\d+) bounce:(?P<bounce_pages>\d+)" r"(?:\n)" r" +kernel_misc_reclaimable:(?P<kernel_misc_reclaimable>\d+)" r"(?:\n)" r" +free:(?P<free_pages>\d+) free_pcp:(?P<free_pcp_pages>\d+) free_cma:(?P<free_cma_pages>\d+)", True, ), "Available memory chunks": ( r"(?P<mem_node_info>(^Node \d+ ((DMA|DMA32|Normal):|(hugepages)).+(\n|$))+)", False, ), "Memory watermarks": ( r"(?P<mem_watermarks>(^(Node \d+ (DMA|DMA32|Normal) free:|lowmem_reserve\[\]:).+(\n|$))+)", False, ), "Page cache": ( r"^(?P<pagecache_total_pages>\d+) total pagecache pages.*$", True, ), # Source:mm/swap_state.c:show_swap_cache_info() "Swap usage information": ( r"^(?P<swap_cache_pages>\d+) pages in swap cache" r"(?:\n)" r"^Swap cache stats: add \d+, delete \d+, find \d+\/\d+" r"(?:\n)" r"^Free swap = (?P<swap_free_kb>\d+)kB" r"(?:\n)" r"^Total swap = (?P<swap_total_kb>\d+)kB", False, ), "Page information": ( r"^(?P<ram_pages>\d+) pages RAM" r"(" r"(?:\n)" r"^(?P<highmem_pages>\d+) pages HighMem/MovableOnly" r")?" r"(?:\n)" r"^(?P<reserved_pages>\d+) pages reserved" r"(" r"(?:\n)" r"^(?P<cma_pages>\d+) pages cma reserved" r")?" r"(" r"(?:\n)" r"^(?P<pagetablecache_pages>\d+) pages in pagetable cache" r")?" r"(" r"(?:\n)" r"^(?P<hwpoisoned_pages>\d+) pages hwpoisoned" r")?", True, ), "Process killed by OOM": ( r"^Out of memory: Kill process (?P<killed_proc_pid>\d+) \((?P<killed_proc_name>[\S ]+)\) " r"score (?P<killed_proc_score>\d+) or sacrifice child", True, ), "Details of process killed by OOM": ( r"^Killed process \d+ \(.*\)" r"(, UID \d+,)?" r" total-vm:(?P<killed_proc_total_vm_kb>\d+)kB, anon-rss:(?P<killed_proc_anon_rss_kb>\d+)kB, " r"file-rss:(?P<killed_proc_file_rss_kb>\d+)kB, shmem-rss:(?P<killed_proc_shmem_rss_kb>\d+)kB.*", True, ), } """ RE pattern to extract information from OOM. The first item is the RE pattern and the second is whether it is mandatory to find this pattern. This dictionary will be copied to EXTRACT_PATTERN during class constructor is executed. :type: dict(tuple(str, bool)) :see: EXTRACT_PATTERN """ EXTRACT_PATTERN_OVERLAY = {} """ To extend / overwrite parts of EXTRACT_PATTERN in kernel configuration. :type: dict(tuple(str, bool)) :see: EXTRACT_PATTERN """ # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH"}, "GFP_HIGHUSER": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM" }, "GFP_HIGHUSER_MOVABLE": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_MOVABLE" }, "GFP_IOFS": {"value": "__GFP_IO | __GFP_FS"}, "GFP_KERNEL": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS"}, "GFP_NOFS": {"value": "__GFP_WAIT | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_WAIT"}, "GFP_NOWAIT": {"value": "GFP_ATOMIC & ~__GFP_HIGH"}, "GFP_TEMPORARY": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD" }, "GFP_USER": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KMEMCG": {"value": "___GFP_KMEMCG"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_NO_KSWAPD": {"value": "___GFP_NO_KSWAPD"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WAIT": {"value": "___GFP_WAIT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___GFP_DMA": {"value": 0x01}, "___GFP_HIGHMEM": {"value": 0x02}, "___GFP_DMA32": {"value": 0x04}, "___GFP_MOVABLE": {"value": 0x08}, "___GFP_WAIT": {"value": 0x10}, "___GFP_HIGH": {"value": 0x20}, "___GFP_IO": {"value": 0x40}, "___GFP_FS": {"value": 0x80}, "___GFP_COLD": {"value": 0x100}, "___GFP_NOWARN": {"value": 0x200}, "___GFP_REPEAT": {"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_RECLAIMABLE": {"value": 0x80000}, "___GFP_KMEMCG": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_NO_KSWAPD": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, } """ Definition of GFP flags The decimal value of a flag will be calculated by evaluating the entries from left to right. Grouping by parentheses is not supported. Source: include/linux/gpf.h @note : This list os probably a mixture of different kernel versions - be carefully """ gfp_reverse_lookup = [] """ Sorted list of flags used to do a reverse lookup. This list doesn't contain all flags. It contains the "useful flags" (GFP_*) as well as "modifier flags" (__GFP_*). "Plain flags" (___GFP_*) are not part of this list. @type: List(str) @see: _gfp_create_reverse_lookup() """ MAX_ORDER = -1 """ The kernel memory allocator divides physically contiguous memory blocks into "zones", where each zone is a power of two number of pages. This option selects the largest power of two that the kernel keeps in the memory allocator. This config option is actually maximum order plus one. For example, a value of 11 means that the largest free memory block is 2^10 pages. The value will be calculated dynamically based on the numbers of orders in OOMAnalyser._extract_buddyinfo(). @see: OOMAnalyser._extract_buddyinfo(). """ 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""" PAGE_ALLOC_COSTLY_ORDER = 3 """ Requests with order > PAGE_ALLOC_COSTLY_ORDER will never trigger the OOM-killer to satisfy the request. """ 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"] """Columns that are not converted to an integer""" pstable_start = "[ pid ]" """ Pattern to find the start of the process table :type: str """ release = (3, 10, "") """ Kernel release with this configuration The tuple contains major and minor version as well as a suffix like "-aws" or ".el7." The patch level isn't part of this version tuple, because I don't assume any changes in GFP flags within a patch release. @see: OOMAnalyser._choose_kernel_config() @type: (int, int, str) """ REC_FREE_MEMORY_CHUNKS = re.compile( "Node (?P<node>\d+) (?P<zone>DMA|DMA32|Normal): (?P<zone_usage>.*) = (?P<total_free_kb_per_node>\d+)kB" ) """RE to extract free memory chunks of a memory zone""" 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) """RE to match the last line of an OOM block""" REC_PAGE_SIZE = re.compile("Node 0 DMA: \d+\*(?P<page_size>\d+)kB") """RE to extract the page size from buddyinfo DMA zone""" REC_PROCESS_LINE = re.compile( r"^\[(?P<pid>[ \d]+)\]\s+(?P<uid>\d+)\s+(?P<tgid>\d+)\s+(?P<total_vm_pages>\d+)\s+(?P<rss_pages>\d+)\s+" r"(?P<nr_ptes_pages>\d+)\s+(?P<swapents_pages>\d+)\s+(?P<oom_score_adj>-?\d+)\s+(?P<name>.+)\s*" ) """Match content of process table""" REC_WATERMARK = re.compile( "Node (?P<node>\d+) (?P<zone>DMA|DMA32|Normal) " "free:(?P<free>\d+)kB " "min:(?P<min>\d+)kB " "low:(?P<low>\d+)kB " "high:(?P<high>\d+)kB " ".*" ) """ RE to extract watermark information in a memory zone Source: mm/page_alloc.c:__show_free_areas() """ watermark_start = "Node 0 DMA free:" """ Pattern to find the start of the memory watermark information :type: str """ zoneinfo_start = "Node 0 DMA: " """ Pattern to find the start of the memory chunk information (buddyinfo) :type: str """ ZONE_TYPES = ["DMA", "DMA32", "Normal", "HighMem", "Movable"] """ List of memory zones @type: List(str) """ def __init__(self): super().__init__() if self.EXTRACT_PATTERN is None: # Create a copy to prevent modifications on the class dictionary # TODO replace with self.EXTRACT_PATTERN = self.EXTRACT_PATTERN.copy() after # https://github.com/QQuick/Transcrypt/issues/716 "dict does not have a copy method" is fixed self.EXTRACT_PATTERN = {} self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_BASE) if self.EXTRACT_PATTERN_OVERLAY: self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_OVERLAY) self._gfp_calc_all_values() self.gfp_reverse_lookup = self._gfp_create_reverse_lookup() self._check_mandatory_gfp_flags() def _gfp_calc_all_values(self): """ Calculate decimal values for all GFP flags and store in in GFP_FLAGS[<flag>]["_value"] """ # __pragma__ ('jsiter') for flag in self.GFP_FLAGS: value = self._gfp_flag2decimal(flag) self.GFP_FLAGS[flag]["_value"] = value # __pragma__ ('nojsiter') def _gfp_flag2decimal(self, flag): """\ Convert a single flag into a decimal value. The flags can be concatenated with "|" or "~" and negated with "~". The flags will be processed from left to right. Parentheses are not supported. """ if flag not in self.GFP_FLAGS: error("No definition for flag {} found".format(flag)) return 0 value = self.GFP_FLAGS[flag]["value"] if isinstance(value, int): return value tokenlist = iter(re.split("([|&])", value)) operator = "|" # set to process first flag negate_rvalue = False lvalue = 0 while True: try: token = next(tokenlist) except StopIteration: break token = token.strip() if token in ["|", "&"]: operator = token continue if token.startswith("~"): token = token[1:] negate_rvalue = True if token.isdigit(): rvalue = int(token) 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 rvalue = self._gfp_flag2decimal(token) if negate_rvalue: rvalue = ~rvalue if operator == "|": lvalue |= rvalue elif operator == "&": lvalue &= rvalue operator = None negate_rvalue = False return lvalue def _gfp_create_reverse_lookup(self): """ Create a sorted list of flags used to do a reverse lookup from value to the flag. @rtype: List(str) """ # __pragma__ ('jsiter') useful = [ key for key in self.GFP_FLAGS if key.startswith("GFP") and self.GFP_FLAGS[key]["_value"] != 0 ] useful = sorted( useful, key=lambda key: self.GFP_FLAGS[key]["_value"], reverse=True ) modifier = [ key for key in self.GFP_FLAGS if key.startswith("__GFP") and self.GFP_FLAGS[key]["_value"] != 0 ] modifier = sorted( modifier, key=lambda key: self.GFP_FLAGS[key]["_value"], reverse=True ) # __pragma__ ('nojsiter') # useful + modifier produces a string with all values concatenated res = useful res.extend(modifier) return res def _check_mandatory_gfp_flags(self): """ Check existance of mandatory flags used in OOMAnalyser._calc_trigger_process_values() to calculate the memory zone """ if "__GFP_DMA" not in self.GFP_FLAGS: error( "Missing definition of GFP flag __GFP_DMA for kernel {}.{}.{}".format( *self.release ) ) if "__GFP_DMA32" not in self.GFP_FLAGS: error( "Missing definition of GFP flag __GFP_DMA for kernel {}.{}.{}".format( *self.release ) ) return class KernelConfig_3_10(BaseKernelConfig): name = "Configuration for Linux kernel 3.10 or later" release = (3, 10, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH"}, "GFP_HIGHUSER": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM" }, "GFP_HIGHUSER_MOVABLE": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_MOVABLE" }, "GFP_IOFS": {"value": "__GFP_IO | __GFP_FS"}, "GFP_KERNEL": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS"}, "GFP_NOFS": {"value": "__GFP_WAIT | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_WAIT"}, "GFP_NOWAIT": {"value": "GFP_ATOMIC & ~__GFP_HIGH"}, "GFP_TEMPORARY": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD" }, "GFP_USER": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KMEMCG": {"value": "___GFP_KMEMCG"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_NO_KSWAPD": {"value": "___GFP_NO_KSWAPD"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WAIT": {"value": "___GFP_WAIT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___GFP_DMA": {"value": 0x01}, "___GFP_HIGHMEM": {"value": 0x02}, "___GFP_DMA32": {"value": 0x04}, "___GFP_MOVABLE": {"value": 0x08}, "___GFP_WAIT": {"value": 0x10}, "___GFP_HIGH": {"value": 0x20}, "___GFP_IO": {"value": 0x40}, "___GFP_FS": {"value": 0x80}, "___GFP_COLD": {"value": 0x100}, "___GFP_NOWARN": {"value": 0x200}, "___GFP_REPEAT": {"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_RECLAIMABLE": {"value": 0x80000}, "___GFP_KMEMCG": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_NO_KSWAPD": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, } class KernelConfig_3_10_EL7(KernelConfig_3_10): # Supported changes: # * update GFP flags name = "Configuration for RHEL 7 / CentOS 7 specific Linux kernel (3.10)" release = (3, 10, ".el7.") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH"}, "GFP_HIGHUSER": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM" }, "GFP_HIGHUSER_MOVABLE": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_MOVABLE" }, "GFP_IOFS": {"value": "__GFP_IO | __GFP_FS"}, "GFP_KERNEL": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_WAIT | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_WAIT"}, "GFP_NOWAIT": {"value": "GFP_ATOMIC & ~__GFP_HIGH"}, "GFP_TEMPORARY": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD" }, "GFP_USER": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_NO_KSWAPD": {"value": "___GFP_NO_KSWAPD"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WAIT": {"value": "___GFP_WAIT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___GFP_DMA": {"value": 0x01}, "___GFP_HIGHMEM": {"value": 0x02}, "___GFP_DMA32": {"value": 0x04}, "___GFP_MOVABLE": {"value": 0x08}, "___GFP_WAIT": {"value": 0x10}, "___GFP_HIGH": {"value": 0x20}, "___GFP_IO": {"value": 0x40}, "___GFP_FS": {"value": 0x80}, "___GFP_COLD": {"value": 0x100}, "___GFP_NOWARN": {"value": 0x200}, "___GFP_REPEAT": {"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_RECLAIMABLE": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_NO_KSWAPD": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, } def __init__(self): super().__init__() class KernelConfig_3_16(KernelConfig_3_10): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 3.16 or later" release = (3, 16, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH"}, "GFP_HIGHUSER": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM" }, "GFP_HIGHUSER_MOVABLE": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_MOVABLE" }, "GFP_IOFS": {"value": "__GFP_IO | __GFP_FS"}, "GFP_KERNEL": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS"}, "GFP_NOFS": {"value": "__GFP_WAIT | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_WAIT"}, "GFP_NOWAIT": {"value": "GFP_ATOMIC & ~__GFP_HIGH"}, "GFP_TEMPORARY": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD" }, "GFP_USER": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_NO_KSWAPD": {"value": "___GFP_NO_KSWAPD"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WAIT": {"value": "___GFP_WAIT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___GFP_DMA": {"value": 0x01}, "___GFP_HIGHMEM": {"value": 0x02}, "___GFP_DMA32": {"value": 0x04}, "___GFP_MOVABLE": {"value": 0x08}, "___GFP_WAIT": {"value": 0x10}, "___GFP_HIGH": {"value": 0x20}, "___GFP_IO": {"value": 0x40}, "___GFP_FS": {"value": 0x80}, "___GFP_COLD": {"value": 0x100}, "___GFP_NOWARN": {"value": 0x200}, "___GFP_REPEAT": {"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_RECLAIMABLE": {"value": 0x80000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_NO_KSWAPD": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, } class KernelConfig_3_19(KernelConfig_3_16): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 3.19 or later" release = (3, 19, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_IOFS": {"value": "__GFP_IO | __GFP_FS"}, "GFP_KERNEL": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS"}, "GFP_NOFS": {"value": "__GFP_WAIT | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_WAIT"}, "GFP_NOWAIT": {"value": "GFP_ATOMIC & ~__GFP_HIGH"}, "GFP_TEMPORARY": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD" }, "GFP_USER": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_NO_KSWAPD": {"value": "___GFP_NO_KSWAPD"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WAIT": {"value": "___GFP_WAIT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___GFP_DMA": {"value": 0x01}, "___GFP_HIGHMEM": {"value": 0x02}, "___GFP_DMA32": {"value": 0x04}, "___GFP_MOVABLE": {"value": 0x08}, "___GFP_WAIT": {"value": 0x10}, "___GFP_HIGH": {"value": 0x20}, "___GFP_IO": {"value": 0x40}, "___GFP_FS": {"value": 0x80}, "___GFP_COLD": {"value": 0x100}, "___GFP_NOWARN": {"value": 0x200}, "___GFP_REPEAT": {"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_RECLAIMABLE": {"value": 0x80000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_NO_KSWAPD": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, } class KernelConfig_4_1(KernelConfig_3_19): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.1 or later" release = (4, 1, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_IOFS": {"value": "__GFP_IO | __GFP_FS"}, "GFP_KERNEL": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS"}, "GFP_NOFS": {"value": "__GFP_WAIT | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_WAIT"}, "GFP_NOWAIT": {"value": "GFP_ATOMIC & ~__GFP_HIGH"}, "GFP_TEMPORARY": { "value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD" }, "GFP_USER": {"value": "__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOACCOUNT": {"value": "___GFP_NOACCOUNT"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_NO_KSWAPD": {"value": "___GFP_NO_KSWAPD"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WAIT": {"value": "___GFP_WAIT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___GFP_DMA": {"value": 0x01}, "___GFP_HIGHMEM": {"value": 0x02}, "___GFP_DMA32": {"value": 0x04}, "___GFP_MOVABLE": {"value": 0x08}, "___GFP_WAIT": {"value": 0x10}, "___GFP_HIGH": {"value": 0x20}, "___GFP_IO": {"value": 0x40}, "___GFP_FS": {"value": 0x80}, "___GFP_COLD": {"value": 0x100}, "___GFP_NOWARN": {"value": 0x200}, "___GFP_REPEAT": {"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_RECLAIMABLE": {"value": 0x80000}, "___GFP_NOACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_NO_KSWAPD": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, } class KernelConfig_4_4(KernelConfig_4_1): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.4 or later" release = (4, 4, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TEMPORARY": { "value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN & ~__GFP_KSWAPD_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOACCOUNT": {"value": "___GFP_NOACCOUNT"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_REPEAT": {"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_ATOMIC": {"value": 0x80000}, "___GFP_NOACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x2000000}, } class KernelConfig_4_5(KernelConfig_4_4): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.5 or later" release = (4, 5, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TEMPORARY": { "value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN & ~__GFP_KSWAPD_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_REPEAT": {"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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x2000000}, } class KernelConfig_4_6(KernelConfig_4_5): # Supported changes: # * "mm, oom_reaper: report success/failure" (bc448e897b6d24aae32701763b8a1fe15d29fa26) # * update GFP flags name = "Configuration for Linux kernel 4.6 or later" release = (4, 6, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TEMPORARY": { "value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_REPEAT": {"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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x2000000}, } # 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, ) def __init__(self): super().__init__() class KernelConfig_4_8(KernelConfig_4_6): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.8 or later" release = (4, 8, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TEMPORARY": { "value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_OTHER_NODE": {"value": "___GFP_OTHER_NODE"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_REPEAT": {"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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_OTHER_NODE": {"value": 0x800000}, "___GFP_WRITE": {"value": 0x1000000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x2000000}, } class KernelConfig_4_9(KernelConfig_4_8): # Supported changes: # * "mm: oom: deduplicate victim selection code for memcg and global oom" (7c5f64f84483bd13886348edda8b3e7b799a7fdb) name = "Configuration for Linux kernel 4.9 or later" release = (4, 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<killed_proc_total_vm_kb>\d+)kB, anon-rss:(?P<killed_proc_anon_rss_kb>\d+)kB, " r"file-rss:(?P<killed_proc_file_rss_kb>\d+)kB, shmem-rss:(?P<killed_proc_shmem_rss_kb>\d+)kB.*", True, ), } def __init__(self): super().__init__() self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_OVERLAY_49) class KernelConfig_4_10(KernelConfig_4_9): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.10 or later" release = (4, 10, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TEMPORARY": { "value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_REPEAT": {"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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_WRITE": {"value": 0x800000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x1000000}, } class KernelConfig_4_12(KernelConfig_4_10): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.12 or later" release = (4, 12, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TEMPORARY": { "value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_REPEAT": {"value": "___GFP_REPEAT"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_REPEAT": {"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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_WRITE": {"value": 0x800000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x1000000}, "___GFP_NOLOCKDEP": {"value": 0x2000000}, } class KernelConfig_4_13(KernelConfig_4_12): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.13 or later" release = (4, 13, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TEMPORARY": { "value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE" }, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_WRITE": {"value": 0x800000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x1000000}, "___GFP_NOLOCKDEP": {"value": 0x2000000}, } class KernelConfig_4_14(KernelConfig_4_13): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.14 or later" release = (4, 14, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COLD": {"value": "___GFP_COLD"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOTRACK": {"value": "___GFP_NOTRACK"}, "__GFP_NOTRACK_FALSE_POSITIVE": {"value": "__GFP_NOTRACK"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_NOTRACK": {"value": 0x200000}, "___GFP_DIRECT_RECLAIM": {"value": 0x400000}, "___GFP_WRITE": {"value": 0x800000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x1000000}, "___GFP_NOLOCKDEP": {"value": 0x2000000}, } class KernelConfig_4_15(KernelConfig_4_14): # Supported changes: # * mm: consolidate page table accounting (af5b0f6a09e42c9f4fa87735f2a366748767b686) # * update GFP flags name = "Configuration for Linux kernel 4.15 or later" release = (4, 15, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_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_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}, } # nr_ptes -> pgtables_bytes # 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<pid>[ \d]+)\]\s+(?P<uid>\d+)\s+(?P<tgid>\d+)\s+(?P<total_vm_pages>\d+)\s+(?P<rss_pages>\d+)\s+" r"(?P<pgtables_bytes>\d+)\s+(?P<swapents_pages>\d+)\s+(?P<oom_score_adj>-?\d+)\s+(?P<name>.+)\s*" ) 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", ] class KernelConfig_4_18(KernelConfig_4_15): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 4.18 or later" release = (4, 18, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_WRITE": {"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_ATOMIC": {"value": 0x80000}, "___GFP_ACCOUNT": {"value": 0x100000}, "___GFP_DIRECT_RECLAIM": {"value": 0x200000}, "___GFP_KSWAPD_RECLAIM": {"value": 0x400000}, "___GFP_NOLOCKDEP": {"value": 0x800000}, } class KernelConfig_4_19(KernelConfig_4_18): # Supported changes: # * mm, oom: describe task memory unit, larger PID pad (c3b78b11efbb2865433abf9d22c004ffe4a73f5c) name = "Configuration for Linux kernel 4.19 or later" release = (4, 19, "") pstable_start = "[ pid ]" class KernelConfig_5_0(KernelConfig_4_19): # Supported changes: # * "mm, oom: reorganize the oom report in dump_header" (ef8444ea01d7442652f8e1b8a8b94278cb57eafd) name = "Configuration for Linux kernel 5.0 or later" release = (5, 0, "") 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<killed_proc_pid>\d+) \((?P<killed_proc_name>[\S ]+)\) " r"total-vm:(?P<killed_proc_total_vm_kb>\d+)kB, anon-rss:(?P<killed_proc_anon_rss_kb>\d+)kB, " r"file-rss:(?P<killed_proc_file_rss_kb>\d+)kB, shmem-rss:(?P<killed_proc_shmem_rss_kb>\d+)kB, " r"UID:\d+ pgtables:(?P<killed_proc_pgtables>\d+)kB oom_score_adj:(?P<killed_proc_oom_score_adj>\d+)", True, ), } def __init__(self): super().__init__() self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_OVERLAY_50) class KernelConfig_5_1(KernelConfig_5_0): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 5.1 or later" release = (5, 1, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": {"value": "GFP_HIGHUSER | __GFP_MOVABLE"}, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_ZERO": {"value": 0x100}, "___GFP_ATOMIC": {"value": 0x200}, "___GFP_DIRECT_RECLAIM": {"value": 0x400}, "___GFP_KSWAPD_RECLAIM": {"value": 0x800}, "___GFP_WRITE": {"value": 0x1000}, "___GFP_NOWARN": {"value": 0x2000}, "___GFP_RETRY_MAYFAIL": {"value": 0x4000}, "___GFP_NOFAIL": {"value": 0x8000}, "___GFP_NORETRY": {"value": 0x10000}, "___GFP_MEMALLOC": {"value": 0x20000}, "___GFP_COMP": {"value": 0x40000}, "___GFP_NOMEMALLOC": {"value": 0x80000}, "___GFP_HARDWALL": {"value": 0x100000}, "___GFP_ACCOUNT": {"value": 0x400000}, "___GFP_NOLOCKDEP": {"value": 0x800000}, } class KernelConfig_5_8(KernelConfig_5_1): # Supported changes: # * "mm/writeback: discard NR_UNSTABLE_NFS, use NR_WRITEBACK instead" (8d92890bd6b8502d6aee4b37430ae6444ade7a8c) name = "Configuration for Linux kernel 5.8 or later" release = (5, 8, "") EXTRACT_PATTERN_OVERLAY_58 = { "Overall Mem-Info (part 1)": ( r"^Mem-Info:.*" r"(?:\n)" # first line (starting w/o a space) r"^active_anon:(?P<active_anon_pages>\d+) inactive_anon:(?P<inactive_anon_pages>\d+) " r"isolated_anon:(?P<isolated_anon_pages>\d+)" r"(?:\n)" # remaining lines (w/ leading space) r"^ active_file:(?P<active_file_pages>\d+) inactive_file:(?P<inactive_file_pages>\d+) " r"isolated_file:(?P<isolated_file_pages>\d+)" r"(?:\n)" r"^ unevictable:(?P<unevictable_pages>\d+) dirty:(?P<dirty_pages>\d+) writeback:(?P<writeback_pages>\d+)", True, ), } def __init__(self): super().__init__() self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_OVERLAY_58) class KernelConfig_5_14(KernelConfig_5_8): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 5.14 or later" release = (5, 14, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": { "value": "GFP_HIGHUSER | __GFP_MOVABLE | __GFP_SKIP_KASAN_POISON" }, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_SKIP_KASAN_POISON": {"value": "___GFP_SKIP_KASAN_POISON"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, "__GFP_ZEROTAGS": {"value": "___GFP_ZEROTAGS"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_ZERO": {"value": 0x100}, "___GFP_ATOMIC": {"value": 0x200}, "___GFP_DIRECT_RECLAIM": {"value": 0x400}, "___GFP_KSWAPD_RECLAIM": {"value": 0x800}, "___GFP_WRITE": {"value": 0x1000}, "___GFP_NOWARN": {"value": 0x2000}, "___GFP_RETRY_MAYFAIL": {"value": 0x4000}, "___GFP_NOFAIL": {"value": 0x8000}, "___GFP_NORETRY": {"value": 0x10000}, "___GFP_MEMALLOC": {"value": 0x20000}, "___GFP_COMP": {"value": 0x40000}, "___GFP_NOMEMALLOC": {"value": 0x80000}, "___GFP_HARDWALL": {"value": 0x100000}, "___GFP_ACCOUNT": {"value": 0x400000}, "___GFP_ZEROTAGS": {"value": 0x800000}, "___GFP_SKIP_KASAN_POISON": {"value": 0x1000000}, "___GFP_NOLOCKDEP": {"value": 0x2000000}, } class KernelConfig_5_16(KernelConfig_5_14): # Supported changes: # * mm/page_alloc.c: show watermark_boost of zone in zoneinfo (a6ea8b5b9f1c) name = "Configuration for Linux kernel 5.16 or later" release = (5, 16, "") REC_WATERMARK = re.compile( "Node (?P<node>\d+) (?P<zone>DMA|DMA32|Normal) " "free:(?P<free>\d+)kB " "boost:(?P<boost>\d+)kB " "min:(?P<min>\d+)kB " "low:(?P<low>\d+)kB " "high:(?P<high>\d+)kB " ".*" ) class KernelConfig_5_18(KernelConfig_5_16): # Supported changes: # * update GFP flags name = "Configuration for Linux kernel 5.18 or later" release = (5, 18, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": { "value": "GFP_HIGHUSER | __GFP_MOVABLE | __GFP_SKIP_KASAN_POISON" }, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_SKIP_KASAN_POISON": {"value": "___GFP_SKIP_KASAN_POISON"}, "__GFP_SKIP_KASAN_UNPOISON": {"value": "___GFP_SKIP_KASAN_UNPOISON"}, "__GFP_SKIP_ZERO": {"value": "___GFP_SKIP_ZERO"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, "__GFP_ZEROTAGS": {"value": "___GFP_ZEROTAGS"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_ZERO": {"value": 0x100}, "___GFP_ATOMIC": {"value": 0x200}, "___GFP_DIRECT_RECLAIM": {"value": 0x400}, "___GFP_KSWAPD_RECLAIM": {"value": 0x800}, "___GFP_WRITE": {"value": 0x1000}, "___GFP_NOWARN": {"value": 0x2000}, "___GFP_RETRY_MAYFAIL": {"value": 0x4000}, "___GFP_NOFAIL": {"value": 0x8000}, "___GFP_NORETRY": {"value": 0x10000}, "___GFP_MEMALLOC": {"value": 0x20000}, "___GFP_COMP": {"value": 0x40000}, "___GFP_NOMEMALLOC": {"value": 0x80000}, "___GFP_HARDWALL": {"value": 0x100000}, "___GFP_ACCOUNT": {"value": 0x400000}, "___GFP_ZEROTAGS": {"value": 0x800000}, "___GFP_SKIP_ZERO": {"value": 0x1000000}, "___GFP_SKIP_KASAN_UNPOISON": {"value": 0x2000000}, "___GFP_SKIP_KASAN_POISON": {"value": 0x4000000}, "___GFP_NOLOCKDEP": {"value": 0x8000000}, } class KernelConfig_6_0(KernelConfig_5_18): # Supported changes: # * update GFP flags # * "mm/swap: remove swap_cache_info statistics" (442701e7058b) name = "Configuration for Linux kernel 6.0 or later" release = (6, 0, "") # NOTE: These flags are automatically extracted from a gfp.h file. # Please do not change them manually! GFP_FLAGS = { # # # Useful GFP flag combinations: "GFP_ATOMIC": {"value": "__GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM"}, "GFP_HIGHUSER": {"value": "GFP_USER | __GFP_HIGHMEM"}, "GFP_HIGHUSER_MOVABLE": { "value": "GFP_HIGHUSER | __GFP_MOVABLE | __GFP_SKIP_KASAN_POISON | __GFP_SKIP_KASAN_UNPOISON" }, "GFP_KERNEL": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS"}, "GFP_KERNEL_ACCOUNT": {"value": "GFP_KERNEL | __GFP_ACCOUNT"}, "GFP_NOFS": {"value": "__GFP_RECLAIM | __GFP_IO"}, "GFP_NOIO": {"value": "__GFP_RECLAIM"}, "GFP_NOWAIT": {"value": "__GFP_KSWAPD_RECLAIM"}, "GFP_TRANSHUGE": {"value": "GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM"}, "GFP_TRANSHUGE_LIGHT": { "value": "GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN & ~__GFP_RECLAIM" }, "GFP_USER": {"value": "__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL"}, # # # Modifier, mobility and placement hints: "__GFP_ACCOUNT": {"value": "___GFP_ACCOUNT"}, "__GFP_ATOMIC": {"value": "___GFP_ATOMIC"}, "__GFP_COMP": {"value": "___GFP_COMP"}, "__GFP_DIRECT_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM"}, "__GFP_DMA": {"value": "___GFP_DMA"}, "__GFP_DMA32": {"value": "___GFP_DMA32"}, "__GFP_FS": {"value": "___GFP_FS"}, "__GFP_HARDWALL": {"value": "___GFP_HARDWALL"}, "__GFP_HIGH": {"value": "___GFP_HIGH"}, "__GFP_HIGHMEM": {"value": "___GFP_HIGHMEM"}, "__GFP_IO": {"value": "___GFP_IO"}, "__GFP_KSWAPD_RECLAIM": {"value": "___GFP_KSWAPD_RECLAIM"}, "__GFP_MEMALLOC": {"value": "___GFP_MEMALLOC"}, "__GFP_MOVABLE": {"value": "___GFP_MOVABLE"}, "__GFP_NOFAIL": {"value": "___GFP_NOFAIL"}, "__GFP_NOLOCKDEP": {"value": "___GFP_NOLOCKDEP"}, "__GFP_NOMEMALLOC": {"value": "___GFP_NOMEMALLOC"}, "__GFP_NORETRY": {"value": "___GFP_NORETRY"}, "__GFP_NOWARN": {"value": "___GFP_NOWARN"}, "__GFP_RECLAIM": {"value": "___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM"}, "__GFP_RECLAIMABLE": {"value": "___GFP_RECLAIMABLE"}, "__GFP_RETRY_MAYFAIL": {"value": "___GFP_RETRY_MAYFAIL"}, "__GFP_SKIP_KASAN_POISON": {"value": "___GFP_SKIP_KASAN_POISON"}, "__GFP_SKIP_KASAN_UNPOISON": {"value": "___GFP_SKIP_KASAN_UNPOISON"}, "__GFP_SKIP_ZERO": {"value": "___GFP_SKIP_ZERO"}, "__GFP_WRITE": {"value": "___GFP_WRITE"}, "__GFP_ZERO": {"value": "___GFP_ZERO"}, "__GFP_ZEROTAGS": {"value": "___GFP_ZEROTAGS"}, # # # Plain integer GFP bitmasks (for internal use only): "___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_ZERO": {"value": 0x100}, "___GFP_ATOMIC": {"value": 0x200}, "___GFP_DIRECT_RECLAIM": {"value": 0x400}, "___GFP_KSWAPD_RECLAIM": {"value": 0x800}, "___GFP_WRITE": {"value": 0x1000}, "___GFP_NOWARN": {"value": 0x2000}, "___GFP_RETRY_MAYFAIL": {"value": 0x4000}, "___GFP_NOFAIL": {"value": 0x8000}, "___GFP_NORETRY": {"value": 0x10000}, "___GFP_MEMALLOC": {"value": 0x20000}, "___GFP_COMP": {"value": 0x40000}, "___GFP_NOMEMALLOC": {"value": 0x80000}, "___GFP_HARDWALL": {"value": 0x100000}, "___GFP_ACCOUNT": {"value": 0x400000}, "___GFP_ZEROTAGS": {"value": 0x800000}, "___GFP_SKIP_ZERO": {"value": 0x1000000}, "___GFP_SKIP_KASAN_UNPOISON": {"value": 0x2000000}, "___GFP_SKIP_KASAN_POISON": {"value": 0x4000000}, "___GFP_NOLOCKDEP": {"value": 0x8000000}, } EXTRACT_PATTERN_OVERLAY_60 = { "Swap usage information": ( r"^(?P<swap_cache_pages>\d+) pages in swap cache" r"(?:\n)" r"^Free swap = (?P<swap_free_kb>\d+)kB" r"(?:\n)" r"^Total swap = (?P<swap_total_kb>\d+)kB", False, ), } def __init__(self): super().__init__() self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_OVERLAY_60) class KernelConfig_6_1(KernelConfig_6_0): # Supported changes: # * "mm: add NR_SECONDARY_PAGETABLE to count secondary page table uses." (ebc97a52b5d6) name = "Configuration for Linux kernel 6.1 or later" release = (6, 1, "") EXTRACT_PATTERN_OVERLAY_61 = { "Overall Mem-Info (part 2)": ( r" +slab_reclaimable:(?P<slab_reclaimable_pages>\d+) slab_unreclaimable:(?P<slab_unreclaimable_pages>\d+)" r"(?:\n)" r" +mapped:(?P<mapped_pages>\d+) shmem:(?P<shmem_pages>\d+) pagetables:(?P<pagetables_pages>\d+) bounce:(?P<bounce_pages>\d+)" r"(?:\n)" r" +kernel_misc_reclaimable:(?P<kernel_misc_reclaimable>\d+)" r"(?:\n)" r" +free:(?P<free_pages>\d+) free_pcp:(?P<free_pcp_pages>\d+) free_cma:(?P<free_cma_pages>\d+)", True, ), } def __init__(self): super().__init__() self.EXTRACT_PATTERN.update(self.EXTRACT_PATTERN_OVERLAY_61) AllKernelConfigs = [ KernelConfig_6_1(), KernelConfig_6_0(), KernelConfig_5_18(), KernelConfig_5_16(), KernelConfig_5_14(), KernelConfig_5_8(), KernelConfig_5_1(), KernelConfig_5_0(), KernelConfig_4_15(), KernelConfig_4_19(), KernelConfig_4_18(), KernelConfig_4_15(), KernelConfig_4_14(), KernelConfig_4_13(), KernelConfig_4_12(), KernelConfig_4_10(), KernelConfig_4_9(), KernelConfig_4_8(), KernelConfig_4_6(), KernelConfig_4_5(), KernelConfig_4_4(), KernelConfig_4_1(), KernelConfig_3_19(), KernelConfig_3_16(), KernelConfig_3_10_EL7(), KernelConfig_3_10(), BaseKernelConfig(), ] """ Instances of all available kernel configurations. Manually sorted from newest to oldest and from specific to general. The last entry in this list is the base configuration as a fallback. """ class OOMEntity: """Hold whole OOM message block and provide access""" current_line = 0 """Zero based index of the current line in self.lines""" lines = [] """OOM text as list of lines""" state = OOMEntityState.unknown """State of the OOM after initial parsing""" text = "" """OOM as text""" def __init__(self, text): # use Unix LF only text = text.replace("\r\n", "\n") text = text.strip() oom_lines = text.split("\n") self.current_line = 0 self.lines = oom_lines self.text = text # don't do anything if the text is empty or does not contain the leading OOM message if not text: self.state = OOMEntityState.empty return 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 ) 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) if "Killed process" in text: self.state = OOMEntityState.complete else: self.state = OOMEntityState.started def _journalctl_add_leading_columns_to_meminfo(self, oom_lines, cols_to_add): """ Add leading columns to handle line breaks in journalctl output correctly. The output of the "Mem-Info:" block contains line breaks. journalctl breaks these lines accordingly, but inserts at the beginning spaces instead of date and time. As a result, removing the needless columns no longer works correctly. This function adds columns back in the affected rows so that the removal works cleanly over all rows. @see: _rsyslog_unescape_lf() """ pattern = r"^\s+ (active_file|unevictable|slab_reclaimable|mapped|sec_pagetables|kernel_misc_reclaimable|free):.+$" rec = re.compile(pattern) add_cols = "" for i in range(cols_to_add): add_cols += "Col{} ".format(i) expanded_lines = [] for line in oom_lines: match = rec.search(line) if match: line = "{} {}".format(add_cols, line.strip()) expanded_lines.append(line) return expanded_lines def _get_CPU_index(self, lines): """ Return the index of the first line with "CPU: " 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]: return i return 0 def _number_of_columns_to_strip(self, line): """ Determinate number of columns left to the OOM message to strip. Sometime timestamps, hostnames and or syslog tags are left to the OOM message. This columns will be count to strip later. """ to_strip = 0 columns = line.split(" ") # Examples: # [11686.888109] CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-514.6.1.el7.x86_64 #1 # 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 # 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: to_strip = columns.index("CPU:") except ValueError: pass return to_strip def _remove_non_oom_lines(self, oom_lines): """Remove all lines before and after OOM message block""" cleaned_lines = [] in_oom_lines = False killed_process = False for line in oom_lines: # first line of the oom message block if "invoked oom-killer:" in line: in_oom_lines = True if in_oom_lines: cleaned_lines.append(line) # 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: killed_process = True continue # next line after "Killed process \d+ ..." if killed_process: if "oom_reaper" in line: break else: # remove this line del cleaned_lines[-1] break return cleaned_lines def _rsyslog_unescape_lf(self, oom_lines): """ Split lines at '#012' (octal representation of LF). The output of the "Mem-Info:" block contains line breaks. Rsyslog replaces these line breaks with their octal representation #012. This breaks the removal of needless columns as well as the detection of the OOM values. Splitting the lines (again) solves this issue. This feature can be controlled inside the rsyslog configuration with the directives $EscapeControlCharactersOnReceive, $Escape8BitCharactersOnReceive and $ControlCharactersEscapePrefix. @see: _journalctl_add_leading_columns_to_meminfo() """ lines = [] for line in oom_lines: if "#012" in line: lines.extend(line.split("#012")) else: lines.append(line) return lines def _remove_kernel_colon(self, oom_lines): """ Remove the "kernel:" pattern w/o leading and tailing spaces. 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] return oom_lines def _strip_needless_columns(self, oom_lines, cols_to_strip=0): """ Remove needless columns at the start of every line. This function removes all leading items w/o any relation to the OOM message like, date and time, hostname, syslog priority/facility. """ stripped_lines = [] for line in oom_lines: # remove empty lines if not line.strip(): continue if cols_to_strip: # [-1] slicing needs Transcrypt operator overloading line = line.split(" ", cols_to_strip)[-1] # __:opov stripped_lines.append(line) return stripped_lines def goto_previous_line(self): """Set line pointer to previous line If using in front of an iterator: The line pointer in self.current_line points to the first line of a block. An iterator based loop starts with a next() call (as defined by the iterator protocol). This causes the current line to be skipped. Therefore, the line pointer is set to the previous line. """ if self.current_line > 0: self.current_line -= 1 return def current(self): """Return the current line""" return self.lines[self.current_line] def next(self): """Return the next line""" if self.current_line + 1 < len(self.lines): self.current_line += 1 return self.lines[self.current_line] raise StopIteration() def find_text(self, pattern): """ Search the pattern and set the position to the first found line. Otherwise the position pointer won't be changed. :param pattern: Text to find :type pattern: str :return: True if the marker has found. """ for line in self.lines: if pattern in line: self.current_line = self.lines.index(line) return True return False def __iter__(self): return self def __next__(self): return self.next() class OOMResult: """Results of an OOM analysis""" buddyinfo = {} """Information about free areas in all zones""" details = {} """Extracted result""" error_msg = "" """ Error message @type: str """ kconfig = BaseKernelConfig() """Kernel configuration""" kversion = None """ Kernel version @type: str """ mem_alloc_failure = OOMMemoryAllocFailureType.not_started """State/result of the memory allocation failure analysis @see: OOMAnalyser._analyse_alloc_failure() """ mem_fragmented = None """True if the memory is heavily fragmented. This means that the higher order has no free chunks. @see: BaseKernelConfig.PAGE_ALLOC_COSTLY_ORDER, OOMAnalyser._check_for_memory_fragmentation() @type: None | bool """ oom_entity = None """ State of this OOM (unknown, incomplete, ...) :type: OOMEntityState """ oom_text = None """ OOM text @type: str """ oom_type = OOMEntityType.unknown """ Type of this OOM (manually or automatically triggered) :type: OOMEntityType """ swap_active = False """ Swap space active or inactive @type: bool """ watermarks = {} """Memory watermark information""" class OOMAnalyser: """Analyse an OOM object and calculate additional values""" oom_entity = None """ State of this OOM (unknown, incomplete, ...) :type: OOMEntityState """ oom_result = OOMResult() """ Store details of OOM analysis :type: OOMResult """ REC_KERNEL_VERSION = re.compile( r"CPU: \d+ PID: \d+ Comm: .* (Not tainted|Tainted: [A-Z ]+) (?P<kernel_version>\d[\w.-]+) (?:#\d) (?P<distribution>\w+ \w+).+" ) """RE to match the OOM line with kernel version""" REC_SPLIT_KVERSION = re.compile( r"(?P<kernel_version>" r"(?P<major>\d+)\.(?P<minor>\d+)" # major . minor r"(\.\d+)?" # optional: patch level r"(-[\w.-]+)?" # optional: -rc6, -arch-1, -19-generic r")" ) """ RE for splitting the kernel version into parts Examples: - 5.19-rc6 - 4.14.288 - 5.18.6-arch1-1 - 5.13.0-19-generic #19-Ubuntu - 5.13.0-1028-aws #31~20.04.1-Ubuntu - 3.10.0-514.6.1.el7.x86_64 #1 """ def __init__(self, oom): self.oom_entity = oom self.oom_result = OOMResult() def _identify_kernel_version(self): """ Identify the used kernel version and @rtype: bool """ match = self.REC_KERNEL_VERSION.search(self.oom_entity.text) if not match: self.oom_result.error_msg = "Failed to extract kernel version from OOM text" return False self.oom_result.kversion = match.group("kernel_version") return True def _check_kversion_greater_equal(self, kversion, min_version): """ Returns True if the kernel version is greater or equal to the minimum version @param str kversion: Kernel version @param (int, int, str) min_version: Minimum version @rtype: bool """ match = self.REC_SPLIT_KVERSION.match(kversion) if not match: self.oom_result.error_msg = ( 'Failed to extract version details from version string "%s"' % kversion ) return False required_major = min_version[0] required_minor = min_version[1] suffix = min_version[2] current_major = int(match.group("major")) current_minor = int(match.group("minor")) if (required_major > current_major) or ( required_major == current_major and required_minor > current_minor ): return False if bool(suffix) and (suffix not in kversion): return False return True def _choose_kernel_config(self): """ Choose the first matching kernel configuration from AllKernelConfigs @see: _check_kversion_greater_equal(), AllKernelConfigs """ for kcfg in AllKernelConfigs: if self._check_kversion_greater_equal( self.oom_result.kversion, kcfg.release ): self.oom_result.kconfig = kcfg break if not self.oom_result.kconfig: warning( 'Failed to find a proper configuration for kernel "{}"'.format( self.oom_result.kversion ) ) self.oom_result.kconfig = BaseKernelConfig() return def _check_for_empty_oom(self): """ Check for an empty OOM text @rtype: bool """ if not self.oom_entity.text: self.state = OOMEntityState.empty self.oom_result.error_msg = ( "Empty OOM text. Please insert an OOM message block." ) return False return True def _check_for_complete_oom(self): """ Check if the OOM in self.oom_entity is complete and update self.oom_state accordingly @rtype: bool """ self.oom_state = OOMEntityState.unknown 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!" 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." ) return False self.state = OOMEntityState.complete self.oom_result.error_msg = None return True def _extract_block_from_next_pos(self, marker): """ Extract a block that starts with the marker and contains all lines up to the next line with ":". :rtype: str """ 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: self.oom_entity.goto_previous_line() break block += "{}\n".format(line) return block def _extract_gpf_mask(self): """Extract the GFP (Get Free Pages) mask""" if self.oom_result.details["trigger_proc_gfp_flags"] is not None: flags = self.oom_result.details["trigger_proc_gfp_flags"] else: flags, unknown = self._gfp_hex2flags( self.oom_result.details["trigger_proc_gfp_mask"], ) if unknown: flags.append("0x{0:x}".format(unknown)) flags = " | ".join(flags) self.oom_result.details["_trigger_proc_gfp_mask_decimal"] = int( self.oom_result.details["trigger_proc_gfp_mask"], 16 ) 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"] # TODO: Add check if given trigger_proc_gfp_flags is equal with calculated flags def _extract_from_oom_text(self): """Extract details from OOM message text""" self.oom_result.details = {} # __pragma__ ('jsiter') for k in self.oom_result.kconfig.EXTRACT_PATTERN: pattern, is_mandatory = self.oom_result.kconfig.EXTRACT_PATTERN[k] rec = re.compile(pattern, re.MULTILINE) match = rec.search(self.oom_entity.text) 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 ) ) # __pragma__ ('nojsiter') 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:" ) # 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"): continue call_trace += "{}\n".format(line.strip()) self.oom_result.details["call_trace"] = call_trace self._extract_page_size() self._extract_pstable() self._extract_gpf_mask() self._extract_buddyinfo() self._extract_watermarks() def _extract_page_size(self): """Extract page size from buddyinfo DMZ zone""" match = self.oom_result.kconfig.REC_PAGE_SIZE.search(self.oom_entity.text) if match: self.oom_result.details["page_size_kb"] = int(match.group("page_size")) self.oom_result.details["_page_size_guessed"] = False else: # educated guess self.oom_result.details["page_size_kb"] = 4 self.oom_result.details["_page_size_guessed"] = True def _extract_pstable(self): """Extract process table""" 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("["): 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) def _extract_buddyinfo(self): """Extract information about free areas in all zones The migration types "(UEM)" or similar are not evaluated. They are documented in mm/page_alloc.c:show_migration_types(). This function fills: * OOMResult.buddyinfo with [<zone>][<order>][<node>] = <number of free chunks> * OOMResult.buddyinfo with [zone]["total_free_kb_per_node"][node] = int(total_free_kb_per_node) """ self.oom_result.buddyinfo = {} buddy_info = self.oom_result.buddyinfo self.oom_entity.find_text(self.oom_result.kconfig.zoneinfo_start) self.oom_entity.goto_previous_line() for line in self.oom_entity: match = self.oom_result.kconfig.REC_FREE_MEMORY_CHUNKS.match(line) if not match: continue node = int(match.group("node")) zone = match.group("zone") if zone not in buddy_info: buddy_info[zone] = {} if "total_free_kb_per_node" not in buddy_info[zone]: buddy_info[zone]["total_free_kb_per_node"] = {} buddy_info[zone]["total_free_kb_per_node"][node] = int( int(match.group("total_free_kb_per_node")) ) order = -1 # to start with 0 after the first increment in for loop for element in match.group("zone_usage").split(" "): if element.startswith("("): # skip migration types continue order += 1 if order not in buddy_info[zone]: buddy_info[zone][order] = {} count = element.split("*")[0] count.strip() buddy_info[zone][order][node] = int(count) if "free_chunks_total" not in buddy_info[zone][order]: buddy_info[zone][order]["free_chunks_total"] = 0 buddy_info[zone][order]["free_chunks_total"] += buddy_info[zone][order][ node ] # MAX_ORDER is actually maximum order plus one. For example, # a value of 11 means that the largest free memory block is 2^10 pages. # __pragma__ ('jsiter') max_order = 0 for o in self.oom_result.buddyinfo["DMA"]: # JS: integer is sometimes a string :-/ if (isinstance(o, str) and o.isdigit()) or isinstance(o, int): max_order += 1 # __pragma__ ('nojsiter') self.oom_result.kconfig.MAX_ORDER = max_order def _extract_watermarks(self): """ Extract memory watermark information from all zones This function fills: * OOMResult.watermarks with [<zone>][<node>][(free|min|low|high)] = int * OOMResult.watermarks with [<zone>][<node>][(lowmem_reserve)] = List(int) """ self.oom_result.watermarks = {} watermark_info = self.oom_result.watermarks self.oom_entity.find_text(self.oom_result.kconfig.watermark_start) node = None zone = None self.oom_entity.goto_previous_line() for line in self.oom_entity: match = self.oom_result.kconfig.REC_WATERMARK.match(line) if not match: if line.startswith("lowmem_reserve[]:"): # zone and node are defined in the previous round watermark_info[zone][node]["lowmem_reserve"] = [ int(v) for v in line.split()[1:] ] continue node = int(match.group("node")) zone = match.group("zone") if zone not in watermark_info: watermark_info[zone] = {} if node not in watermark_info[zone]: watermark_info[zone][node] = {} for i in ["free", "min", "low", "high"]: watermark_info[zone][node][i] = int(match.group(i)) def _search_node_with_memory_shortage(self): """ Search NUMA node with memory shortage: watermark "free" < "min". This function fills: * OOMResult.details["trigger_proc_numa_node"] = <int(first node with memory shortage) | None> """ self.oom_result.details["trigger_proc_numa_node"] = None zone = self.oom_result.details["trigger_proc_mem_zone"] watermark_info = self.oom_result.watermarks if zone not in watermark_info: debug( "Missing watermark info for zone {} - skip memory analysis".format(zone) ) return # __pragma__ ('jsiter') for node in watermark_info[zone]: if watermark_info[zone][node]["free"] < watermark_info[zone][node]["min"]: self.oom_result.details["trigger_proc_numa_node"] = int(node) return # __pragma__ ('nojsiter') return def _gfp_hex2flags(self, hexvalue): """\ Convert the hexadecimal value into flags specified by definition @return: Unsorted list of flags and the sum of all unknown flags as integer @rtype: List(str), int """ remaining = int(hexvalue, 16) converted_flags = [] for flag in self.oom_result.kconfig.gfp_reverse_lookup: value = self.oom_result.kconfig.GFP_FLAGS[flag]["_value"] if (remaining & value) == value: # delete flag by "and" with a reverted mask remaining &= ~value converted_flags.append(flag) converted_flags.sort() return converted_flags, remaining def _convert_numeric_results_to_integer(self): """Convert all *_pages and *_kb to integer""" # __pragma__ ('jsiter') for item in self.oom_result.details: if self.oom_result.details[item] is None: self.oom_result.details[item] = "<not found>" 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"] ): 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] ) ) # __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_index = [] # TODO Check if transcrypt issue: pragma jsiter for the whole block "for pid_str in ps: ..." # sets item in "for item in ['uid',..." to 0 instead of 'uid' # jsiter is necessary to iterate over ps for pid_str in ps.keys(): converted = {} process = ps[pid_str] for item in self.oom_result.kconfig.pstable_items: if item in self.oom_result.kconfig.pstable_non_ints: continue try: converted[item] = int(process[item]) except: if item not in process: pitem = "<not in process table>" else: pitem = process[item] error( 'Converting process parameter "{}={}" to integer failed'.format( item, pitem ) ) 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 def _check_free_chunks(self, start_with_order, zone, node): """Check for at least one free chunk in the current or any higher order. Returns True, if at lease one suitable chunk is free. Returns None, if buddyinfo doesn't contain information for the requested node, order or zone @param int start_with_order: Start checking with this order @param str zone: Memory zone @param int node: Node number @rtype: None|bool """ if not self.oom_result.buddyinfo: return None buddyinfo = self.oom_result.buddyinfo if zone not in buddyinfo: return None for order in range(start_with_order, self.oom_result.kconfig.MAX_ORDER): if order not in buddyinfo[zone]: break if node not in buddyinfo[zone][order]: return None free_chunks = buddyinfo[zone][order][node] if free_chunks: return True return False def _check_for_memory_fragmentation(self): """Check for heavy memory fragmentation. This means that the higher order has no free chunks. Returns True, all high order chunk are in use. Returns False, if high order chunks are available. Returns None, if buddyinfo doesn't contain information for the requested node, order or zone @see: BaseKernelConfig.PAGE_ALLOC_COSTLY_ORDER, OOMResult.mem_fragmented @rtype: None|bool """ zone = self.oom_result.details["trigger_proc_mem_zone"] node = self.oom_result.details["trigger_proc_numa_node"] if zone not in self.oom_result.buddyinfo: return None self.oom_result.mem_fragmented = not self._check_free_chunks( self.oom_result.kconfig.PAGE_ALLOC_COSTLY_ORDER, zone, node ) self.oom_result.details[ "kconfig.PAGE_ALLOC_COSTLY_ORDER" ] = self.oom_result.kconfig.PAGE_ALLOC_COSTLY_ORDER def _analyse_alloc_failure(self): """ Analyse why the memory allocation could be failed. The code in this function is inspired by mm/page_alloc.c:__zone_watermark_ok() """ self.oom_result.mem_alloc_failure = OOMMemoryAllocFailureType.not_started if self.oom_result.oom_type == OOMEntityType.manual: debug("OOM triggered manually - skip memory analysis") return if not self.oom_result.buddyinfo: debug("Missing buddyinfo - skip memory analysis") return if ("trigger_proc_order" not in self.oom_result.details) or ( "trigger_proc_mem_zone" not in self.oom_result.details ): debug( "Missing trigger_proc_order and/or trigger_proc_mem_zone - skip memory analysis" ) return if not self.oom_result.watermarks: debug("Missing watermark information - skip memory analysis") return order = self.oom_result.details["trigger_proc_order"] zone = self.oom_result.details["trigger_proc_mem_zone"] watermark_info = self.oom_result.watermarks # "high order" requests don't trigger OOM if int(order) > self.oom_result.kconfig.PAGE_ALLOC_COSTLY_ORDER: debug("high order requests should not trigger OOM - skip memory analysis") self.oom_result.mem_alloc_failure = ( OOMMemoryAllocFailureType.skipped_high_order_dont_trigger_oom ) return # Node with memory shortage: watermark "free" < "min" node = self.oom_result.details["trigger_proc_numa_node"] if node is None: return # the remaining code is similar to mm/page_alloc.c:__zone_watermark_ok() # ======================================================================= # calculation in kB and not in pages free_kb = watermark_info[zone][node]["free"] highest_zoneidx = self.oom_result.kconfig.ZONE_TYPES.index(zone) lowmem_reserve = watermark_info[zone][node]["lowmem_reserve"] min_kb = watermark_info[zone][node]["low"] # reduce minimum watermark for high priority calls # ALLOC_HIGH == __GFP_HIGH gfp_mask_decimal = self.oom_result.details["_trigger_proc_gfp_mask_decimal"] gfp_flag_high = self.oom_result.kconfig.GFP_FLAGS["__GFP_DMA"]["_value"] if (gfp_mask_decimal & gfp_flag_high) == gfp_flag_high: min_kb -= int(min_kb / 2) # check watermarks, if these are not met, then a high-order request also # cannot go ahead even if a suitable page happened to be free. if free_kb <= ( min_kb + ( lowmem_reserve[highest_zoneidx] * self.oom_result.details["page_size_kb"] ) ): self.oom_result.mem_alloc_failure = ( OOMMemoryAllocFailureType.failed_below_low_watermark ) return # For a high-order request, check at least one suitable page is free if not self._check_free_chunks(order, zone, node): self.oom_result.mem_alloc_failure = ( OOMMemoryAllocFailureType.failed_no_free_chunks ) return self.oom_result.mem_alloc_failure = ( OOMMemoryAllocFailureType.failed_unknown_reason ) 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"] # 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" # 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" 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"] ) gfp_mask_decimal = self.oom_result.details["_trigger_proc_gfp_mask_decimal"] gfp_flag_dma = self.oom_result.kconfig.GFP_FLAGS["__GFP_DMA"]["_value"] gfp_flag_dma32 = self.oom_result.kconfig.GFP_FLAGS["__GFP_DMA32"]["_value"] if (gfp_mask_decimal & gfp_flag_dma) == gfp_flag_dma: zone = "DMA" elif (gfp_mask_decimal & gfp_flag_dma32) == gfp_flag_dma32: zone = "DMA32" else: zone = "Normal" self.oom_result.details["trigger_proc_mem_zone"] = zone 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_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""" if "swap_total_kb" in self.oom_result.details: self.oom_result.swap_active = self.oom_result.details["swap_total_kb"] > 0 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"] # 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"] ) 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"] ) 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"] ) else: self.oom_result.details[ "system_total_ramswap_kb" ] = self.oom_result.details["system_total_ram_kb"] # TODO: Current 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(): # convert to int to satisfy Python for unit tests total_rss_pages += int( 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"] ) def _determinate_platform_and_distribution(self): """Determinate platform and distribution""" kernel_version = self.oom_result.details.get("kernel_version", "") distribution = self.oom_result.details.get("distribution", None) if "x86_64" in kernel_version: self.oom_result.details["platform"] = "x86 64bit" else: self.oom_result.details["platform"] = "unknown" dist = "unknown" if distribution is not None: # this should work on openSUSE dist = distribution elif ".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 def _calc_from_oom_details(self): """ Calculate values from already extracted details @see: self.details """ self._convert_numeric_results_to_integer() self._convert_pstable_values_to_integer() self._calc_pstable_values() self._determinate_platform_and_distribution() self._calc_swap_values() self._calc_system_values() self._calc_trigger_process_values() self._calc_killed_process_values() self._search_node_with_memory_shortage() self._analyse_alloc_failure() self._check_for_memory_fragmentation() def analyse(self): """ Extract and calculate values from the given OOM object If the return value is False, the OOM is too incomplete to perform an analysis. @rtype: bool """ if not self._check_for_empty_oom(): error(self.oom_result.error_msg) return False if not self._identify_kernel_version(): error(self.oom_result.error_msg) return False self._choose_kernel_config() if not self._check_for_complete_oom(): error(self.oom_result.error_msg) return False self._extract_from_oom_text() self._calc_from_oom_details() self.oom_result.oom_text = self.oom_entity.text return True 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. """ cfg = dict( chart_height=150, chart_width=600, label_height=80, legend_entry_width=160, legend_margin=7, title_height=20, title_margin=10, 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", ] """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" 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_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["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 # __pragma__ ('kwargs') def create_element(self, tag, **kwargs): """ Create an SVG element of the given tag. @note: Underscores in the argument names will be replaced by minus @param str tag: Type of element to be created @rtype: Node """ element = document.createElementNS(self.namespace, tag) # __pragma__ ('jsiter') for k in kwargs: k2 = k.replace("_", "-") element.setAttribute(k2, kwargs[k]) # __pragma__ ('nojsiter') return element # __pragma__ ('nokwargs') # __pragma__ ('kwargs') def create_element_text(self, text, **kwargs): """ Create an SVG text element @note: Underscores in the argument names will be replaced by minus @param str text: Text @rtype: Node """ 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), ) if css_class: svg.setAttribute("class", css_class) return svg def create_rectangle(self, x, y, width, height, color=None, title=None): """ Return a rect-element in a group container If a title is given, the container also contains a <title> element. """ g = self.create_element("g") rect = self.create_element("rect", x=x, y=y, width=width, height=height) if color: rect.setAttribute("fill", color) if title: t = self.create_element("title") t.textContent = title g.appendChild(t) g.appendChild(rect) return g def create_legend_entry(self, color, desc, pos): """ Create a legend entry for the given position. Both elements of the entry are grouped within a g-element. @param str color: Colour of the entry @param str desc: Description @param int pos: Continuous position @rtype: Node """ 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.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)) return label_group def legend_max_row(self, pos): """ Returns the maximum number of rows in the legend @param int pos: Continuous position """ max_row = math.ceil(pos / self.max_entries_per_row) return max_row def legend_max_col(self, pos): """ Returns the maximum number of columns in the legend @param int pos: Continuous position @rtype: int """ if pos < self.max_entries_per_row: return pos return self.max_entries_per_row def legend_calc_x(self, column): """ Calculate the X-axis using the given column @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"]) return x def legend_calc_y(self, row): """ Calculate the Y-axis using the given row @type row: int @rtype: int """ y = self.cfg["bar_bottomleft_y"] + self.cfg["legend_margin"] y += row * 40 return y def legend_calc_xy(self, pos): """ Calculate the X-axis and Y-axis @param int pos: Continuous position @rtype: int, int """ if not pos: col = 0 row = 0 else: 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"]) y += row * 40 return x, y def generate_bar_area(self, elements): """ Generate colord stacked bars. All entries are group within a g-element. @rtype: Node """ 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"]) 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, ) current_x += rect_len bar_group.appendChild(rect) return bar_group def generate_legend(self, elements): """ Generate a legend for all elements. All entries are group within a g-element. @rtype: Node """ 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)] label_group = self.create_legend_entry(color, element_name, i) 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)) ) return legend_group def generate_chart(self, title, *elements): """ Return a SVG bar chart for all elements @param str title: Chart title @param elements: List of tuple with name and length of the entry (not normalized) @rtype: Node """ 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.appendChild(chart_title) svg.appendChild(bar_group) svg.appendChild(legend_group) return svg class OOMDisplay: """Display the OOM analysis""" # result ergibt an manchen stellen self.result.result :-/ oom_result = OOMResult() """ OOM analysis details @rtype: OOMResult """ example_tumbleweed_swap = """\ [ 5907.004253] MonsterApp invoked oom-killer: gfp_mask=0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_COMP|__GFP_ZERO), order=0, oom_score_adj=0 [ 5907.004262] CPU: 2 PID: 3271 Comm: MonsterApp Not tainted 6.0.3-1-default #1 openSUSE Tumbleweed 50a6ebc5cb1873d6b9c639843cdd1ed0089a1281 [ 5907.004266] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 [ 5907.004268] Call Trace: [ 5907.004272] <TASK> [ 5907.004275] dump_stack_lvl+0x44/0x5c [ 5907.004282] dump_header+0x4a/0x1ff [ 5907.004286] oom_kill_process.cold+0xb/0x10 [ 5907.004290] out_of_memory+0x1fd/0x4d0 [ 5907.004295] __alloc_pages_slowpath.constprop.0+0xcb0/0xe00 [ 5907.004303] __alloc_pages+0x218/0x240 [ 5907.004308] __folio_alloc+0x17/0x50 [ 5907.004311] ? policy_node+0x51/0x70 [ 5907.004314] vma_alloc_folio+0x88/0x300 [ 5907.004318] __handle_mm_fault+0x946/0xfa0 [ 5907.004324] handle_mm_fault+0xae/0x290 [ 5907.004327] do_user_addr_fault+0x1ba/0x690 [ 5907.004332] exc_page_fault+0x66/0x150 [ 5907.004337] asm_exc_page_fault+0x22/0x30 [ 5907.004340] RIP: 0033:0x4011c1 [ 5907.004347] Code: Unable to access opcode bytes at RIP 0x401197. [ 5907.004348] RSP: 002b:00007ffc77b3e4d0 EFLAGS: 00010206 [ 5907.004351] RAX: 00007f1d0508d000 RBX: 00007ffc77b3e608 RCX: 00007f2793922ca7 [ 5907.004353] RDX: 0000000115bfcff0 RSI: 0000000000000000 RDI: 0000000000000000 [ 5907.004354] RBP: 00007ffc77b3e4f0 R08: 00000000ffffffff R09: 0000000000000000 [ 5907.004356] R10: 00007ffc77b3e490 R11: 0000000000000202 R12: 0000000000000000 [ 5907.004357] R13: 00007ffc77b3e618 R14: 0000000000403de0 R15: 00007f2793a8b000 [ 5907.004362] </TASK> [ 5907.004363] Mem-Info: [ 5907.004366] active_anon:65916 inactive_anon:861964 isolated_anon:0 active_file:121 inactive_file:511 isolated_file:0 unevictable:2024 dirty:16 writeback:34 slab_reclaimable:6584 slab_unreclaimable:10454 mapped:28 shmem:2167 pagetables:2902 bounce:0 kernel_misc_reclaimable:0 free:26340 free_pcp:0 free_cma:0 [ 5907.004371] Node 0 active_anon:263664kB inactive_anon:3447856kB active_file:484kB inactive_file:2044kB unevictable:8096kB isolated(anon):0kB isolated(file):0kB mapped:112kB dirty:64kB writeback:136kB shmem:8668kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 909312kB writeback_tmp:0kB kernel_stack:3104kB pagetables:11608kB all_unreclaimable? yes [ 5907.004377] Node 0 DMA free:14336kB boost:0kB min:128kB low:160kB high:192kB reserved_highatomic:0KB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:15996kB managed:15360kB mlocked:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 5907.004383] lowmem_reserve[]: 0 1946 7904 7904 7904 [ 5907.004387] Node 0 DMA32 free:40428kB boost:0kB min:16608kB low:20760kB high:24912kB reserved_highatomic:0KB active_anon:316kB inactive_anon:1967132kB active_file:32kB inactive_file:36kB unevictable:0kB writepending:320kB present:2077504kB managed:2011712kB mlocked:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 5907.004393] lowmem_reserve[]: 0 0 5957 5957 5957 [ 5907.004397] Node 0 Normal free:50596kB boost:0kB min:50844kB low:63552kB high:76260kB reserved_highatomic:0KB active_anon:263100kB inactive_anon:1480748kB active_file:304kB inactive_file:1876kB unevictable:8096kB writepending:64kB present:6291456kB managed:1914680kB mlocked:80kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 5907.004413] lowmem_reserve[]: 0 0 0 0 0 [ 5907.004420] Node 0 DMA: 0*4kB 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 1*2048kB (M) 3*4096kB (M) = 14336kB [ 5907.004433] Node 0 DMA32: 82*4kB (UME) 32*8kB (UME) 40*16kB (UME) 12*32kB (UE) 19*64kB (U) 14*128kB (UME) 8*256kB (UE) 4*512kB (UE) 31*1024kB (UME) 0*2048kB 0*4096kB = 40456kB [ 5907.004472] Node 0 Normal: 847*4kB (UME) 652*8kB (UME) 285*16kB (UME) 66*32kB (UME) 53*64kB (UME) 23*128kB (UME) 9*256kB (UE) 5*512kB (UME) 24*1024kB (UM) 0*2048kB 0*4096kB = 51052kB [ 5907.004490] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB [ 5907.004492] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB [ 5907.004494] 293046 total pagecache pages [ 5907.004495] 290241 pages in swap cache [ 5907.004496] Free swap = 0kB [ 5907.004497] Total swap = 2098152kB [ 5907.004498] 2096239 pages RAM [ 5907.004499] 0 pages HighMem/MovableOnly [ 5907.004500] 1110801 pages reserved [ 5907.004500] 0 pages cma reserved [ 5907.004501] 0 pages hwpoisoned [ 5907.004502] Tasks state (memory values in pages): [ 5907.004503] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [ 5907.004514] [ 557] 0 557 14420 823 110592 7 -250 systemd-journal [ 5907.004518] [ 561] 0 561 8103 282 81920 274 -1000 systemd-udevd [ 5907.004522] [ 643] 0 643 2096 604 49152 360 0 haveged [ 5907.004525] [ 749] 0 749 2828 65 40960 3 -1000 auditd [ 5907.004528] [ 755] 483 755 2156 241 49152 16 -900 dbus-daemon [ 5907.004531] [ 757] 0 757 19909 80 53248 3 0 irqbalance [ 5907.004533] [ 777] 0 777 1542 119 53248 13 0 rngd [ 5907.004540] [ 782] 478 782 211645 561 151552 22 0 nscd [ 5907.004543] [ 810] 0 810 4331 280 73728 18 0 systemd-logind [ 5907.004546] [ 848] 0 848 60288 0 90112 704 0 pcscd [ 5907.004549] [ 849] 0 849 2384 237 61440 74 0 wickedd-auto4 [ 5907.004552] [ 851] 0 851 2385 108 57344 208 0 wickedd-dhcp4 [ 5907.004554] [ 854] 0 854 2422 63 53248 264 0 wickedd-dhcp6 [ 5907.004557] [ 863] 477 863 58964 99 102400 412 0 polkitd [ 5907.004559] [ 864] 0 864 2414 131 53248 225 0 wickedd [ 5907.004562] [ 874] 0 874 2419 62 53248 264 0 wickedd-nanny [ 5907.004564] [ 1085] 472 1085 3699 36 69632 176 0 vncmanager [ 5907.004567] [ 1089] 0 1089 803 38 45056 3 0 agetty [ 5907.004570] [ 1107] 0 1107 3438 49 61440 209 -1000 sshd [ 5907.004573] [ 1108] 476 1108 21312 176 65536 36 0 chronyd [ 5907.004576] [ 1146] 0 1146 76363 58 90112 703 0 lightdm [ 5907.004578] [ 1155] 0 1155 59117 732 94208 22 0 accounts-daemon [ 5907.004581] [ 1165] 0 1165 204230 4060 450560 3612 0 Xorg.bin [ 5907.004583] [ 1201] 0 1201 40196 132 90112 640 0 lightdm [ 5907.004586] [ 1213] 1000 1213 4860 432 77824 71 100 systemd [ 5907.004589] [ 1214] 1000 1214 26171 715 94208 248 100 (sd-pam) [ 5907.004591] [ 1221] 1000 1221 1846 0 49152 69 0 icewm-session [ 5907.004594] [ 1237] 1000 1237 2056 86 49152 11 200 dbus-daemon [ 5907.004597] [ 1293] 1000 1293 1598 123 45056 14 0 ssh-agent [ 5907.004603] [ 1294] 1000 1294 38735 595 65536 3 0 gpg-agent [ 5907.004606] [ 1295] 1000 1295 6143 545 90112 314 0 icewm [ 5907.004611] [ 1298] 1000 1298 1817 49 45056 37 0 startup [ 5907.004614] [ 1300] 1000 1300 1192 0 45056 94 0 xscreensaver [ 5907.004616] [ 1302] 1000 1302 1874 0 57344 103 0 xscreensaver-sy [ 5907.004619] [ 1584] 0 1584 4143 34 65536 340 0 sshd [ 5907.004622] [ 1588] 1000 1588 4208 26 65536 418 0 sshd [ 5907.004624] [ 1590] 1000 1590 2463 0 57344 721 0 bash [ 5907.004627] [ 1739] 0 1739 4144 271 69632 104 0 sshd [ 5907.004629] [ 1742] 1000 1742 4209 267 69632 172 0 sshd [ 5907.004632] [ 1743] 1000 1743 2529 592 57344 190 0 bash [ 5907.004635] [ 3271] 1000 3271 12207676 624136 9175040 513536 0 MonsterApp [ 5907.004638] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/user.slice/user-1000.slice/session-4.scope,task=MonsterApp,pid=3271,uid=1000 [ 5907.004654] Out of memory: Killed process 3271 (MonsterApp) total-vm:48830704kB, anon-rss:2496540kB, file-rss:4kB, shmem-rss:0kB, UID:1000 pgtables:8960kB oom_score_adj:0 """ example_tumbleweed_noswap = """\ [ 1400.080118] MonsterApp invoked oom-killer: gfp_mask=0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_COMP|__GFP_ZERO), order=0, oom_score_adj=0 [ 1400.080125] CPU: 0 PID: 1978 Comm: MonsterApp Not tainted 6.0.3-1-default #1 openSUSE Tumbleweed 50a6ebc5cb1873d6b9c639843cdd1ed0089a1281 [ 1400.080128] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 [ 1400.080129] Call Trace: [ 1400.080132] <TASK> [ 1400.080135] dump_stack_lvl+0x44/0x5c [ 1400.080141] dump_header+0x4a/0x1ff [ 1400.080152] oom_kill_process.cold+0xb/0x10 [ 1400.080154] out_of_memory+0x1fd/0x4d0 [ 1400.080159] __alloc_pages_slowpath.constprop.0+0xcb0/0xe00 [ 1400.080165] __alloc_pages+0x218/0x240 [ 1400.080168] __folio_alloc+0x17/0x50 [ 1400.080171] ? policy_node+0x51/0x70 [ 1400.080173] vma_alloc_folio+0x88/0x300 [ 1400.080176] __handle_mm_fault+0x946/0xfa0 [ 1400.080180] handle_mm_fault+0xae/0x290 [ 1400.080183] do_user_addr_fault+0x1ba/0x690 [ 1400.080188] exc_page_fault+0x66/0x150 [ 1400.080191] asm_exc_page_fault+0x22/0x30 [ 1400.080194] RIP: 0033:0x4011c1 [ 1400.080198] Code: Unable to access opcode bytes at RIP 0x401197. [ 1400.080199] RSP: 002b:00007ffccb028980 EFLAGS: 00010206 [ 1400.080201] RAX: 00007fd7c06c9000 RBX: 00007ffccb028ab8 RCX: 00007fe28a591ca7 [ 1400.080203] RDX: 00000000da5c9ff0 RSI: 0000000000000000 RDI: 0000000000000000 [ 1400.080206] RBP: 00007ffccb0289a0 R08: 00000000ffffffff R09: 0000000000000000 [ 1400.080207] R10: 00007ffccb028940 R11: 0000000000000202 R12: 0000000000000000 [ 1400.080208] R13: 00007ffccb028ac8 R14: 0000000000403de0 R15: 00007fe28a6fa000 [ 1400.080212] </TASK> [ 1400.080213] Mem-Info: [ 1400.080214] active_anon:970 inactive_anon:927725 isolated_anon:0 active_file:50 inactive_file:0 isolated_file:0 unevictable:2024 dirty:20 writeback:0 slab_reclaimable:6559 slab_unreclaimable:10354 mapped:0 shmem:2277 pagetables:2516 bounce:0 kernel_misc_reclaimable:0 free:26295 free_pcp:0 free_cma:0 [ 1400.080218] Node 0 active_anon:3880kB inactive_anon:3710900kB active_file:200kB inactive_file:0kB unevictable:8096kB isolated(anon):0kB isolated(file):0kB mapped:0kB dirty:80kB writeback:0kB shmem:9108kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 3178496kB writeback_tmp:0kB kernel_stack:3360kB pagetables:10064kB all_unreclaimable? yes [ 1400.080222] Node 0 DMA free:14336kB boost:0kB min:128kB low:160kB high:192kB reserved_highatomic:0KB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:15996kB managed:15360kB mlocked:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 1400.080226] lowmem_reserve[]: 0 1946 7904 7904 7904 [ 1400.080229] Node 0 DMA32 free:40272kB boost:0kB min:16608kB low:20760kB high:24912kB reserved_highatomic:0KB active_anon:140kB inactive_anon:1968716kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:2077504kB managed:2011712kB mlocked:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 1400.080233] lowmem_reserve[]: 0 0 5957 5957 5957 [ 1400.080236] Node 0 Normal free:50572kB boost:0kB min:50844kB low:63552kB high:76260kB reserved_highatomic:0KB active_anon:3740kB inactive_anon:1741752kB active_file:0kB inactive_file:356kB unevictable:8096kB writepending:80kB present:6291456kB managed:1914680kB mlocked:80kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 1400.080240] lowmem_reserve[]: 0 0 0 0 0 [ 1400.080243] Node 0 DMA: 0*4kB 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 1*2048kB (M) 3*4096kB (M) = 14336kB [ 1400.080259] Node 0 DMA32: 71*4kB (UME) 65*8kB (UME) 42*16kB (UE) 37*32kB (UE) 40*64kB (UME) 28*128kB (UME) 9*256kB (UME) 5*512kB (UME) 4*1024kB (UME) 1*2048kB (M) 5*4096kB (M) = 40292kB [ 1400.080294] Node 0 Normal: 730*4kB (UME) 110*8kB (UME) 339*16kB (UE) 213*32kB (UE) 91*64kB (UME) 43*128kB (UME) 13*256kB (UME) 5*512kB (UE) 17*1024kB (UM) 0*2048kB 0*4096kB = 50664kB [ 1400.080323] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB [ 1400.080324] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB [ 1400.080340] 2378 total pagecache pages [ 1400.080341] 0 pages in swap cache [ 1400.080341] Free swap = 0kB [ 1400.080342] Total swap = 0kB [ 1400.080343] 2096239 pages RAM [ 1400.080343] 0 pages HighMem/MovableOnly [ 1400.080344] 1110801 pages reserved [ 1400.080344] 0 pages cma reserved [ 1400.080345] 0 pages hwpoisoned [ 1400.080346] Tasks state (memory values in pages): [ 1400.080346] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [ 1400.080357] [ 557] 0 557 14420 828 110592 0 -250 systemd-journal [ 1400.080360] [ 561] 0 561 8103 556 81920 0 -1000 systemd-udevd [ 1400.080363] [ 643] 0 643 2096 964 49152 0 0 haveged [ 1400.080365] [ 749] 0 749 2828 60 40960 0 -1000 auditd [ 1400.080367] [ 755] 483 755 2156 256 49152 1 -900 dbus-daemon [ 1400.080369] [ 757] 0 757 19909 83 53248 0 0 irqbalance [ 1400.080371] [ 776] 0 776 20107 61 49152 0 0 qemu-ga [ 1400.080373] [ 777] 0 777 1542 132 53248 0 0 rngd [ 1400.080374] [ 782] 478 782 211645 580 151552 0 0 nscd [ 1400.080377] [ 810] 0 810 4331 298 73728 0 0 systemd-logind [ 1400.080393] [ 848] 0 848 60288 688 90112 0 0 pcscd [ 1400.080395] [ 849] 0 849 2384 311 61440 0 0 wickedd-auto4 [ 1400.080397] [ 851] 0 851 2385 316 57344 0 0 wickedd-dhcp4 [ 1400.080399] [ 854] 0 854 2422 312 53248 0 0 wickedd-dhcp6 [ 1400.080400] [ 863] 477 863 58964 509 102400 1 0 polkitd [ 1400.080402] [ 864] 0 864 2414 355 53248 0 0 wickedd [ 1400.080404] [ 874] 0 874 2419 326 53248 0 0 wickedd-nanny [ 1400.080406] [ 1080] 0 1080 7081 408 86016 0 0 cupsd [ 1400.080407] [ 1081] 458 1081 305359 7251 286720 0 0 matterbridge [ 1400.080409] [ 1082] 456 1082 179448 2603 110592 0 0 node_exporter [ 1400.080411] [ 1085] 472 1085 3699 212 69632 0 0 vncmanager [ 1400.080413] [ 1089] 0 1089 803 29 45056 0 0 agetty [ 1400.080414] [ 1107] 0 1107 3438 258 61440 0 -1000 sshd [ 1400.080416] [ 1108] 476 1108 21312 212 65536 0 0 chronyd [ 1400.080418] [ 1146] 0 1146 76363 761 90112 0 0 lightdm [ 1400.080420] [ 1155] 0 1155 59117 748 94208 1 0 accounts-daemon [ 1400.080422] [ 1165] 0 1165 204230 7672 450560 0 0 Xorg.bin [ 1400.080424] [ 1201] 0 1201 40196 772 90112 0 0 lightdm [ 1400.080425] [ 1213] 1000 1213 4860 503 77824 0 100 systemd [ 1400.080427] [ 1214] 1000 1214 26171 963 94208 0 100 (sd-pam) [ 1400.080429] [ 1221] 1000 1221 1846 64 49152 0 0 icewm-session [ 1400.080431] [ 1237] 1000 1237 2056 97 49152 0 200 dbus-daemon [ 1400.080433] [ 1293] 1000 1293 1598 137 45056 0 0 ssh-agent [ 1400.080434] [ 1294] 1000 1294 38735 92 65536 0 0 gpg-agent [ 1400.080436] [ 1295] 1000 1295 6143 859 90112 0 0 icewm [ 1400.080438] [ 1298] 1000 1298 1817 86 45056 0 0 startup [ 1400.080439] [ 1300] 1000 1300 1192 88 45056 0 0 xscreensaver [ 1400.080441] [ 1302] 1000 1302 1874 101 57344 0 0 xscreensaver-sy [ 1400.080443] [ 1584] 0 1584 4143 374 65536 0 0 sshd [ 1400.080447] [ 1588] 1000 1588 4208 444 65536 0 0 sshd [ 1400.080448] [ 1590] 1000 1590 2463 709 57344 0 0 bash [ 1400.080450] [ 1739] 0 1739 4144 375 69632 0 0 sshd [ 1400.080452] [ 1742] 1000 1742 4209 439 69632 0 0 sshd [ 1400.080453] [ 1743] 1000 1743 2463 718 53248 0 0 bash [ 1400.080455] [ 1978] 1000 1978 12207676 894428 7221248 0 0 MonsterApp [ 1400.080457] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/user.slice/user-1000.slice/session-4.scope,task=MonsterApp,pid=1978,uid=1000 [ 1400.080469] Out of memory: Killed process 1978 (MonsterApp) total-vm:48830704kB, anon-rss:3577708kB, file-rss:4kB, shmem-rss:0kB, UID:1000 pgtables:7052kB oom_score_adj:0 """ sorted_column_number = None """ Processes will sort by values in this column @type: int """ sort_order = None """Sort order for process values""" svg_array_updown = """ <svg width="8" height="11"> <use xlink:href="#svg_array_updown" /> </svg> """ """SVG graphics with two black triangles UP and DOWN for sorting""" svg_array_up = """ <svg width="8" height="11"> <use xlink:href="#svg_array_up" /> </svg> """ """SVG graphics with one black triangle UP for sorting""" svg_array_down = """ <svg width="8" height="11"> <use xlink:href="#svg_array_down" /> </svg> """ """SVG graphics with one black triangle DOWN for sorting""" def __init__(self): self.oom = None self.set_html_defaults() # self.update_toc() element = document.getElementById("version") element.textContent = "v{}".format(VERSION) def _set_item(self, item): """ Paste the content into HTML elements with the ID / Class that matches the item name. The content won't be formatted. Only suffixes for pages and kbytes are added in the singular or plural. """ elements = document.getElementsByClassName(item) for element in elements: content = self.oom_result.details.get(item, "") if isinstance(content, str): content = content.strip() if content == "<not found>": row = element.parentNode row.classList.add("js-text--display-none") 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 content == 1: content = "{} Byte".format(content) else: content = "{} Bytes".format(content) 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): content = "{} %".format(content) element.innerHTML = content if DEBUG: show_element("notify_box") # def update_toc(self): # """ # Update the TOC to show current headlines only # # There are two conditions to show a h2 headline in TOC: # * the headline is visible # * the id attribute is set # """ # new_toc = "" # # toc_content = document.querySelectorAll("nav > ul")[0] # # 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 # ) # # toc_content.innerHTML = new_toc def _show_pstable(self): """ Create and show 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", ) 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" 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"]: css_class = 'class="js-pstable__triggerproc--bgcolor"' 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" ] fmt_list.insert(0, css_class) fmt_list.insert(1, pid) line = """ <tr {}> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> </tr> """.format( *fmt_list ) new_table += line table_content.innerHTML = new_table def pstable_set_sort_triangle(self): """Set the sorting symbols for all columns in the process table""" for column_name in self.oom_result.kconfig.pstable_items: column_number = self.oom_result.kconfig.pstable_items.index(column_name) element_id = "js-pstable_sort_col{}".format(column_number) element = document.getElementById(element_id) if not element: internal_error('Missing id "{}" in process table.'.format(element_id)) continue if column_number == self.sorted_column_number: if self.sort_order == "descending": element.innerHTML = self.svg_array_down else: element.innerHTML = self.svg_array_up else: element.innerHTML = self.svg_array_updown def set_html_defaults(self): """Reset the HTML document but don't clean elements""" # show all hidden elements in the result table show_elements("table .js-text--display-none") # hide all elements marked to be hidden by default hide_elements(".js-text--default-hide") # show all elements marked to be shown by default show_elements(".js-text--default-show") # clear notification box element = document.getElementById("notify_box") while element.firstChild: element.removeChild(element.firstChild) # remove svg charts for element_id in ("svg_swap", "svg_ram"): element = document.getElementById(element_id) while element.firstChild: element.removeChild(element.firstChild) self._clear_pstable() def _clear_pstable(self): """Clear process table""" element = document.getElementById("pstable_content") while element.firstChild: element.removeChild(element.firstChild) # reset sort triangles self.sorted_column_number = None self.sort_order = None 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", ) element.firstChild.textContent = "col {}".format(i + 1) def copy_example_tumbleweed_swap_to_form(self): document.getElementById("textarea_oom").value = self.example_tumbleweed_swap def copy_example_tumbleweed_noswap_to_form(self): document.getElementById("textarea_oom").value = self.example_tumbleweed_noswap def reset_form(self): 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") row_with_oom = oom_element.parentNode.parentNode toggle_msg = document.getElementById("oom_toogle_msg") if show or row_with_oom.classList.contains("js-text--display-none"): row_with_oom.classList.remove("js-text--display-none") toggle_msg.text = "(click to hide)" else: row_with_oom.classList.add("js-text--display-none") toggle_msg.text = "(click to show)" def analyse_and_show(self): """Analyse the OOM text inserted into the form and show the results""" self.oom = OOMEntity(self.load_from_form()) # set defaults and clear notifications self.set_html_defaults() analyser = OOMAnalyser(self.oom) success = analyser.analyse() if success: self.oom_result = analyser.oom_result self.show_oom_details() scroll(0,0) # self.update_toc() def load_from_form(self): """ Return the OOM text from textarea element @rtype: str """ element = document.getElementById("textarea_oom") oom_text = element.value return oom_text def show_oom_details(self): """ Show all extracted details as well as additionally generated information """ self._show_items() self._show_swap_usage() self._show_ram_usage() self._show_alloc_failure() self._show_memory_fragmentation() self._show_page_size() # generate process table self._show_pstable() self.pstable_set_sort_triangle() element = document.getElementById("oom") element.textContent = self.oom_result.oom_text self.toggle_oom(show=False) def _show_alloc_failure(self): """Show details why the memory allocation failed""" if ( self.oom_result.mem_alloc_failure == OOMMemoryAllocFailureType.failed_below_low_watermark ): show_elements(".js-alloc-failure--show") show_elements(".js-alloc-failure-below-low-watermark--show") elif ( self.oom_result.mem_alloc_failure == OOMMemoryAllocFailureType.failed_no_free_chunks ): show_elements(".js-alloc-failure--show") show_elements(".js-alloc-failure-no-free-chunks--show") elif ( self.oom_result.mem_alloc_failure == OOMMemoryAllocFailureType.failed_unknown_reason ): show_elements(".js-alloc-failure--show") show_elements(".js-alloc-failure-unknown-reason-show") def _show_memory_fragmentation(self): """Show details about memory fragmentation""" if self.oom_result.mem_fragmented is None: return show_elements(".js-memory-fragmentation--show") if self.oom_result.mem_fragmented: show_elements(".js-memory-heavy-fragmentation--show") else: show_elements(".js-memory-no-heavy-fragmentation--show") if self.oom_result.details["trigger_proc_numa_node"] is None: hide_elements(".js-memory-shortage-node--hide") def _show_page_size(self): """Show page size""" if self.oom_result.details.get("_page_size_guessed", True): show_elements(".js-pagesize-guessed--show") else: show_elements(".js-pagesize-determined--show") def _show_ram_usage(self): """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"), ) 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") elem_svg_ram.appendChild(svg_ram) def _show_swap_usage(self): """Show/hide swap space and generate usage diagram""" 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") elem_svg_swap.appendChild(svg_swap) show_elements(".js-swap-active--show") else: show_elements(".js-swap-inactive--show") def _show_items(self): """Switch to output view and show most items""" hide_element("input") show_element("analysis") if self.oom_result.oom_type == OOMEntityType.manual: show_elements(".js-oom-manual--show") else: show_elements(".js-oom-automatic--show") for item in self.oom_result.details.keys(): # ignore internal items 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") def sort_pstable(self, column_number): """ Sort process table by values :param int column_number: Number of column to sort """ # TODO Check operator overloading # Operator overloading (Pragma opov) does not work in this context. # self.oom_result.kconfig.pstable_items + ['notes'] will compile to a string # "pid,uid,tgid,total_vm_pages,rss_pages,nr_ptes_pages,swapents_pages,oom_score_adjNotes" and not to an # array ps_table_and_notes = self.oom_result.kconfig.pstable_items[:] 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 ) ) return # reset sort order if the column has changes if column_number != self.sorted_column_number: self.sort_order = None self.sorted_column_number = column_number if not self.sort_order or self.sort_order == "descending": self.sort_order = "ascending" self.sort_psindex_by_column(column_name) else: self.sort_order = "descending" self.sort_psindex_by_column(column_name, True) self._show_pstable() self.pstable_set_sort_triangle() def sort_psindex_by_column(self, column_name, reverse=False): """ Sort the pid list '_pstable_index' based on the values in the process dict '_pstable'. 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"] def getvalue(column, pos): 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 ): value = int(value) return value # We set swapped to True so the loop looks runs at least once swapped = True while swapped: swapped = False for i in range(len(ps_index) - 1): v1 = getvalue(column_name, i) v2 = getvalue(column_name, i + 1) if (not reverse and v1 > v2) or (reverse and v1 < v2): # Swap the elements ps_index[i], ps_index[i + 1] = ps_index[i + 1], ps_index[i] # Set the flag to True so we'll loop again swapped = True OOMDisplayInstance = OOMDisplay() 07070100000007000081A4000000000000000000000001643C162900001378000000000000000000000000000000000000001600000000oom-o-o-0.1/README.md# openSUSE OOMAnalyser ## Introduction This is a forked version of [Carsten Grohmann's OOMAnalyser](https://git.sr.ht/~carstengrohmann/OOMAnalyser) targeting SUSE based operating systems. It is a small project to transform the OOM message of a Linux kernel into a more user-friendly format. OOMAnalyser consists of a web page into whose input field the OOM message is copied. JavaScript code extracts the data from it and displays the details. All processing takes place in the browser. No data is transferred to external servers. This makes it possible to use a locally stored copy of the website for analysis. This project is written in [Python](https://www.python.org) and uses [Transcrypt](https://www.transcrypt.org/) to translate Python code into JavaScript. The current online version is available at [https://www.carstengrohmann.de/oom/](https://www.carstengrohmann.de/oom/) . ## Installation Installing OOMAnalyser is quite easy since OOMAnalyser consists only of two files, an HTML file and a JavaScript file. Both can be stored locally to use OOMAnalyser without an Internet connection. ### Installation steps 1. Open [https://www.carstengrohmann.de/oom/](https://www.carstengrohmann.de/oom/) in a browser 2. Browse down to the paragraph "Local Installation" at the end of the document 3. Download the HTML file and the JavaScript file to the main directory 4. Open the file `OOMAnalyser.html` in your favourite browser ## Building OOMAnalyser ### Requirements * [Python](http://www.python.org) 3.7 * [Transcrypt](https://www.transcrypt.org/) 3.7 * [Rollup](https://rollupjs.org/) ### Prepare the build environment * Clone the repository: # git clone https://git.sr.ht/~carstengrohmann/OOMAnalyser * Set up the Python virtual environment: # virtualenv env # . env/bin/activate # env/bin/pip install -Ur requirements.txt or # make venv ### Build OOMAnalyser ``` # . env/bin/activate # transcrypt --build --map --nomin -e 6 OOMAnalyser.py # rollup --config rollup.config.mjs or # make build ``` ### Usage * Change into the source directory and start your own small web server. * Start Python built-in web server: # python3 -m http.server 8080 --bind 127.0.0.1 or # make websrv * Open the URL http://localhost:8080/OOMAnalyser.html in your favorite browser. ## Publish a new release ### Naming * `NEW_VERSION` - Version string of the new version e.g. `0.5.0` * `NEW_GIT_VERSION` - Git version string of the new version e.g. `v0.5.0` ### Steps 1. Commit all open changes 2. Updating the documentation in `README.md` and `OOMAnalyser.html` and commit changes # git commit -m "Update documentation" 3. Update version number in `OOMAnalyser.py` and `Makefile` # git commit -m "Bump version number to <NEW_VERSION>" 4. Create a new annotated git tag with shortened changelog # git tag -a <NEW_GIT_VERSION> 5. Push changes into public repositories # git push # git push origin --tags 6. Create release packages in zip and tar.gz format # make release 7. Create release on SourceHut & GitHub 8. Start a new development cycle by setting new version numbers ## Resources * [Transcrypt](https://www.transcrypt.org/) * [Linux man pages online](https://man7.org/) * [Decoding the Linux kernel's page allocation failure messages](https://utcc.utoronto.ca/~cks/space/blog/linux/DecodingPageAllocFailures) * [Linux Kernel OOM Log Analysis](http://elearningmedium.com/linux-kernel-oom-log-analysis/) ## Known Bugs/Issues Check the project [issue tracker](https://todo.sr.ht/~carstengrohmann/OOMAnalyser) for current open bugs. New bugs can be reported there also. ## License This project is licensed under the MIT license. > Copyright (c) 2017-2023 Carsten Grohmann, mail <add at here> carstengrohmann.de > > Permission is hereby granted, free of charge, to any person obtaining a copy of > this software and associated documentation files (the "Software"), to deal in > the Software without restriction, including without limitation the rights to > use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies > of the Software, and to permit persons to whom the Software is furnished to do > so, subject to the following conditions: > > The above copyright notice and this permission notice shall be included in all > copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > SOFTWARE. Enjoy! Carsten Grohmann 07070100000008000081A4000000000000000000000001643C162900000038000000000000000000000000000000000000001B00000000oom-o-o-0.1/pyproject.toml[tool.black] line-length = 88 target-version = ['py37'] 07070100000009000081A4000000000000000000000001643C1629000000EC000000000000000000000000000000000000001D00000000oom-o-o-0.1/requirements.txt# Requirements to setup virtual environment for OOMAnalyser # # Copyright (c) 2017-2023 Carsten Grohmann # License: MIT (see LICENSE.txt) # THIS PROGRAM COMES WITH NO WARRANTY Transcrypt == 3.7.16 selenium webdriver-manager pre-commit 0707010000000A000081A4000000000000000000000001643C162900000238000000000000000000000000000000000000001E00000000oom-o-o-0.1/rollup.config.mjs// Rollup.js configuration for OOMAnalyser // // Copyright (c) 2021-2023 Carsten Grohmann // License: MIT (see LICENSE.txt) // THIS PROGRAM COMES WITH NO WARRANTY export default { input: '__target__/OOMAnalyser.js', output: { file: 'OOMAnalyser.js', name: 'OOMAnalyser', banner: '// JavaScript for OOMAnalyser\n' + '//\n' + '// Copyright (c) 2017-2023 Carsten Grohmann\n' + '// License: MIT (see LICENSE.txt)\n' + '// THIS PROGRAM COMES WITH NO WARRANTY', format: 'umd' } }; 0707010000000B000081ED000000000000000000000001643C16290000AA56000000000000000000000000000000000000001400000000oom-o-o-0.1/test.py# Unit tests for OOMAnalyser # # Copyright (c) 2021-2023 Carsten Grohmann # License: MIT (see LICENSE.txt) # THIS PROGRAM COMES WITH NO WARRANTY import http.server import os import re import socketserver import threading import unittest from selenium import webdriver from selenium.common.exceptions import * from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from webdriver_manager.chrome import ChromeDriverManager import warnings 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) # suppress all HTTP request messages def log_message(self, format, *args): # super().log_message(format, *args) pass class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): pass class TestBase(unittest.TestCase): text_alloc_failed_below_low_watermark = ( "The request failed because the free memory would be below the memory low " "watermark after its completion." ) text_alloc_failed_no_free_chunks = ( "The request failed because there is no free chunk in the current or " "higher order." ) text_alloc_failed_unknown_reason = "The request failed, but the reason is unknown." text_mem_not_heavily_fragmented = "The system memory is not heavily fragmented" text_mem_heavily_fragmented = "The system memory is heavily fragmented" text_oom_triggered_manually = "OOM killer was manually triggered" text_oom_triggered_automatically = "OOM killer was automatically triggered" text_swap_space_not_in_use = "physical memory and no swap space" text_swap_space_are_in_use = "swap space are in use" text_with_an_oom_score_of = "with an OOM score of" def get_lines(self, text, count): """ Return the number of lines specified by count from given text @type text: str @type count: int """ lines = text.splitlines() if count < 0: lines.reverse() count = count * -1 lines = lines[:count] res = "\n".join(lines) return res def get_first_line(self, text): """ Return the first line of the given text @type text: str """ return self.get_lines(text, 1) def get_last_line(self, text): """ Return the last line of the given text @type text: str """ return self.get_lines(text, -1) class TestInBrowser(TestBase): """Test OOM web page in a browser""" def setUp(self): warnings.simplefilter("ignore", ResourceWarning) ThreadedTCPServer.allow_reuse_address = True 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" # store driver locally os.environ["WDM_LOCAL"] = "1" s = Service(ChromeDriverManager().install()) self.driver = webdriver.Chrome(service=s) self.driver.get("http://127.0.0.1:8000/OOMAnalyser.html") def tearDown(self): self.driver.close() self.httpd.shutdown() self.httpd.server_close() def assert_on_warn(self): notify_box = self.driver.find_element(By.ID, "notify_box") try: warning = notify_box.find_element( By.CLASS_NAME, "js-notify_box__msg--warning" ) except NoSuchElementException: pass else: self.fail('Unexpected warning message: "%s"' % warning.text) def assert_on_error(self): error = self.get_error_text() if error: self.fail('Unexpected error message: "%s"' % error) for event in self.driver.get_log("browser"): # ignore favicon.ico errors if "favicon.ico" in event["message"]: continue self.fail('Error on browser console reported: "%s"' % event) def assert_on_warn_error(self): self.assert_on_warn() self.assert_on_error() def click_analyse(self): analyse = self.driver.find_element(By.XPATH, '//button[text()="Analyse"]') analyse.click() def get_error_text(self): """ Return text from error notification box or an empty string if no error message exists @rtype: str """ notify_box = self.driver.find_element(By.ID, "notify_box") try: notify_box.find_element(By.CLASS_NAME, "js-notify_box__msg--error") except NoSuchElementException: return "" return notify_box.text def click_reset(self): reset = self.driver.find_element(By.XPATH, '//button[text()="Reset"]') 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.click() self.assert_on_warn_error() def analyse_oom(self, text): """ Insert text and run analysis :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.send_keys(text) 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.click_analyse() def check_results_rhel7(self): """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", ) 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" ) # 0x201da will split into # GFP_HIGHUSER_MOVABLE 0x200da # (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_MOVABLE) # __GFP_WAIT 0x10 # __GFP_IO 0x40 # __GFP_FS 0x80 # __GFP_HARDWALL 0x20000 # __GFP_HIGHMEM 0x02 # __GFP_MOVABLE 0x08 # __GFP_COLD 0x100 # sum: 0x201da self.assertEqual( trigger_proc_gfp_mask.text, "0x201da (GFP_HIGHUSER_MOVABLE | __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" ) 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") for expected in [ self.text_alloc_failed_below_low_watermark, self.text_mem_not_heavily_fragmented, self.text_oom_triggered_automatically, self.text_swap_space_are_in_use, self.text_with_an_oom_score_of, ]: self.assertTrue( expected in explanation.text, 'Missing statement "%s"' % expected, ) for unexpected in [ self.text_alloc_failed_no_free_chunks, self.text_alloc_failed_unknown_reason, self.text_mem_heavily_fragmented, self.text_oom_triggered_manually, self.text_swap_space_not_in_use, ]: self.assertTrue( unexpected not in explanation.text, 'Unexpected statement "%s"' % unexpected, ) 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", ) mem_node_info = self.driver.find_element(By.CLASS_NAME, "mem_node_info") self.assertEqual( mem_node_info.text[:44], "Node 0 DMA: 0*4kB 0*8kB 0*16kB 0*32kB 2*64kB", "Unexpected memory chunks", ) self.assertEqual( mem_node_info.text[-80:], "Node 1 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB", "Unexpected memory information about hugepages", ) mem_watermarks = self.driver.find_element(By.CLASS_NAME, "mem_watermarks") self.assertEqual( mem_watermarks.text[:51], "Node 0 DMA free:15872kB min:40kB low:48kB high:60kB", "Unexpected memory watermarks", ) self.assertEqual( mem_watermarks.text[-25:], "lowmem_reserve[]: 0 0 0 0", "Unexpected lowmem_reserve values", ) 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" ) # 0xcc0 will split into # GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS) # __GFP_RECLAIM (___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM) # ___GFP_DIRECT_RECLAIM 0x400 # ___GFP_KSWAPD_RECLAIM 0x800 # __GFP_IO 0x40 # __GFP_FS 0x80 # sum: 0xCC0 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" ) 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") for expected in [ self.text_oom_triggered_manually, self.text_swap_space_not_in_use, ]: self.assertTrue( expected in explanation.text, 'Missing statement "%s"' % expected, ) for unexpected in [ self.text_alloc_failed_below_low_watermark, self.text_alloc_failed_no_free_chunks, self.text_alloc_failed_unknown_reason, self.text_mem_heavily_fragmented, self.text_mem_not_heavily_fragmented, self.text_oom_triggered_automatically, self.text_with_an_oom_score_of, ]: self.assertTrue( unexpected not in explanation.text, 'Unexpected statement "%s"' % unexpected, ) self.assertTrue( "system has 2096632 kBytes physical memory" in explanation.text, "Physical 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", ) mem_node_info = self.driver.find_element(By.CLASS_NAME, "mem_node_info") self.assertEqual( mem_node_info.text[:49], "Node 0 DMA: 1*4kB (U) 1*8kB (U) 1*16kB (U) 1*32kB", "Unexpected memory chunks", ) self.assertEqual( mem_node_info.text[-80:], "Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB", "Unexpected memory information about hugepages", ) mem_watermarks = self.driver.find_element(By.CLASS_NAME, "mem_watermarks") self.assertEqual( mem_watermarks.text[:54], "Node 0 DMA free:15036kB min:352kB low:440kB high:528kB", "Unexpected memory watermarks", ) self.assertEqual( mem_watermarks.text[-27:], "lowmem_reserve[]: 0 0 0 0 0", "Unexpected lowmem_reserve values", ) 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( self.text_swap_space_not_in_use in explanation.text, 'Missing statement "%s"' % self.text_swap_space_not_in_use, ) self.assertTrue( self.text_swap_space_are_in_use not in explanation.text, 'Unexpected statement "%s"' % self.text_swap_space_are_in_use, ) def check_swap_active(self): explanation = self.driver.find_element(By.ID, "explanation") self.assertTrue( self.text_swap_space_are_in_use in explanation.text, 'Missing statement "%s"' % self.text_swap_space_are_in_use, ) def test_010_load_page(self): """Test if the page is loading""" assert "OOMAnalyser" in self.driver.title def test_020_load_js(self): """Test if JS is loaded""" elem = self.driver.find_element(By.ID, "version") self.assertIsNotNone(elem.text, "Version statement not set - JS not loaded") 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" )]' ) insert_example.click() 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.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" )]' ) insert_example.click() 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.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.send_keys(text) 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.click_analyse() 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): """Test incomplete OOM text - just the beginning""" example = """\ 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 """ 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.click_reset() def test_034_no_begin_but_end(self): """Test incomplete OOM text - just the end""" example = """\ 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.click_reset() def test_035_leading_journalctl_input(self): """Test loading input from journalctl""" # prepare example 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")) 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):.+$" rec = re.compile(pattern) for line in example_lines: match = rec.search(line) if match: line = " {}".format(line) else: line = "Apr 01 14:13:32 mysrv <kern.warning> kernel: {}".format(line) res.append(line) example = "\n".join(res) self.analyse_oom(example) self.check_results_rhel7() self.click_reset() 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") 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", ) 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") 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", ) 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") lines = ["{}{}".format(prefix, line) for line in lines] oom_text = "\n".join(lines) self.analyse_oom(oom_text) self.check_results_rhel7() self.click_reset() def test_070_manually_triggered_OOM(self): """Test for manually triggered OOM""" example = OOMAnalyser.OOMDisplay.example_rhel7 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( self.text_oom_triggered_manually in explanation.text, 'Missing statement "%s"' % self.text_oom_triggered_manually, ) self.assertTrue( self.text_oom_triggered_automatically not in explanation.text, 'Unexpected statement "%s"' % self.text_oom_triggered_automatically, ) 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") self.analyse_oom(example) self.assert_on_warn_error() self.check_swap_inactive() 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) self.analyse_oom(example) self.assert_on_warn_error() self.check_swap_inactive() 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] rec = re.compile(pattern, re.MULTILINE) match = rec.search(first) self.assertTrue( match, "Error: re.search('invoked oom-killer') failed for simple process name", ) 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", ) 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] 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", ) 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", ) 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", ), ]: 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), ) 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 = """\ 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): """Test extracting kernel version""" 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", ), ]: analyser.oom_entity.text = text success = analyser._identify_kernel_version() 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.KernelConfig_3_10_EL7(), "CPU: 4 PID: 29481 Comm: sed Not tainted 3.10.0-1062.9.1.el7.x86_64 #1", ), ( OOMAnalyser.KernelConfig_5_1(), "CPU: 4 PID: 29481 Comm: sed Not tainted 5.5.1 #1", ), ( OOMAnalyser.KernelConfig_5_18(), "CPU: 4 PID: 29481 Comm: sed Not tainted 5.23.0 #1", ), ( OOMAnalyser.KernelConfig_6_1(), "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) analyser._identify_kernel_version() 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), ) def test_007_gfp_processing(self): """Test processing GFP flags""" oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7) analyser = OOMAnalyser.OOMAnalyser(oom) success = analyser.analyse() self.assertTrue(success, "OOM analysis failed") self.assertEqual( analyser.oom_result.kconfig.release, (3, 10, ".el7."), "Wrong KernelConfig release", ) for flag, hex_value in [ ("__GFP_DMA", 0x01), ("__GFP_WAIT", 0x10), ("__GFP_IO", 0x40), ("__GFP_FS", 0x80), ("GFP_KERNEL", 0xD0), # __GFP_WAIT | __GFP_IO | __GFP_FS ("__GFP_UNKNOWN", 0x00), # unknown GFP flag ]: self.assertEqual( analyser.oom_result.kconfig._gfp_flag2decimal(flag), hex_value, "Invalid decimal value for %s" % flag, ) for hex_value, flags_expected, unknown_expected in [ ( "0x01", ["__GFP_DMA"], 0, ), ("0x05", ["__GFP_DMA", "__GFP_DMA32"], 0), ( "0x5000000", # 0x1000000 + 0x4000000 ["__GFP_WRITE"], 0x4000000, ), ("0x201da", ["GFP_HIGHUSER_MOVABLE", "__GFP_COLD"], 0), ]: flags_calculated, unknown_calculated = analyser._gfp_hex2flags(hex_value) self.assertEqual( flags_calculated, flags_expected, "Invalid flag(s) for hex value %s" % hex_value, ) self.assertEqual( unknown_calculated, unknown_expected, "Invalid remaining / not resolved decimal flag value", ) oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_ubuntu2110) analyser = OOMAnalyser.OOMAnalyser(oom) success = analyser.analyse() self.assertTrue(success, "OOM analysis failed") self.assertEqual( analyser.oom_result.kconfig.release, (5, 8, ""), "Wrong KernelConfig release", ) for flag, hex_value in [ ("__GFP_DMA", 0x01), ("__GFP_IO", 0x40), ("__GFP_FS", 0x80), ( "GFP_KERNEL", 0xCC0, ), # (__GFP_RECLAIM (___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM) | __GFP_IO | __GFP_FS) ("__GFP_UNKNOWN", 0x00), # unknown GFP flag ]: self.assertEqual( analyser.oom_result.kconfig._gfp_flag2decimal(flag), hex_value, "Invalid decimal value for %s" % flag, ) for hex_value, flags_expected, unknown_expected in [ ( "0x01", ["__GFP_DMA"], 0, ), ("0x05", ["__GFP_DMA", "__GFP_DMA32"], 0), ( "0x4001000", # 0x1000 + 0x4000000 ["__GFP_WRITE"], 0x4000000, ), ]: flags_calculated, unknown_calculated = analyser._gfp_hex2flags(hex_value) self.assertEqual( flags_calculated, flags_expected, "Invalid flag(s) for hex value %s" % hex_value, ) self.assertEqual( unknown_calculated, unknown_expected, "Invalid remaining / not resolved decimal flag value", ) def test_008_kversion_check(self): """Test check for minimum kernel version""" oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7) analyser = OOMAnalyser.OOMAnalyser(oom) for kversion, min_version, expected_result in ( ("5.19-rc6", (5, 16, ""), True), ("5.19-rc6", (5, 19, ""), True), ("5.19-rc6", (5, 20, ""), False), ("5.18.6-arch1-1", (5, 18, ""), True), ("5.18.6-arch1-1", (5, 1, ""), True), ("5.18.6-arch1-1", (5, 19, ""), False), ("5.13.0-1028-aws #31~20.04.1-Ubuntu", (5, 14, ""), False), ("5.13.0-1028-aws #31~20.04.1-Ubuntu", (5, 13, ""), True), ("5.13.0-1028-aws #31~20.04.1-Ubuntu", (5, 13, "-aws"), True), ("5.13.0-1028-aws #31~20.04.1-Ubuntu", (5, 13, "not_in_version"), False), ("5.13.0-1028-aws #31~20.04.1-Ubuntu", (5, 12, ""), True), ("4.14.288", (5, 0, ""), False), ("4.14.288", (4, 14, ""), True), ("3.10.0-514.6.1.el7.x86_64 #1", (3, 11, ""), False), ("3.10.0-514.6.1.el7.x86_64 #1", (3, 10, ".el7."), True), ("3.10.0-514.6.1.el7.x86_64 #1", (3, 10, ""), True), ("3.10.0-514.6.1.el7.x86_64 #1", (3, 9, ""), True), ): self.assertEqual( analyser._check_kversion_greater_equal(kversion, min_version), expected_result, 'Failed to compare kernel version "%s" with minimum version "%s"' % (kversion, min_version), ) def test_009_extract_zoneinfo(self): """Test extracting zone usage information""" oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7) analyser = OOMAnalyser.OOMAnalyser(oom) success = analyser.analyse() self.assertTrue(success, "OOM analysis failed") self.assertEqual( analyser.oom_result.kconfig.release, (3, 10, ".el7."), "Wrong KernelConfig release", ) buddyinfo = analyser.oom_result.buddyinfo for zone, order, node, except_count in [ ("Normal", 6, 0, 0), # order 6 - page size 256kB ("Normal", 6, 1, 2), # order 6 - page size 256kB ("Normal", 6, "free_chunks_total", 0 + 2), # order 6 - page size 256kB ("Normal", 0, 0, 1231), # order 0 - page size 4kB ("Normal", 0, 1, 2245), # order 0 - page size 4kB ("Normal", 0, "free_chunks_total", 1231 + 2245), # order 0 - page size 4kB ("DMA", 5, 0, 1), # order 5 - page size 128kB ("DMA", 5, "free_chunks_total", 1), # order 5 - page size 128kB ("DMA32", 4, 0, 157), # order 4 - page size 64k ("DMA32", 4, "free_chunks_total", 157), # order 4 - page size 64k ("Normal", "total_free_kb_per_node", 0, 38260), ("Normal", "total_free_kb_per_node", 1, 50836), ]: self.assertTrue( zone in buddyinfo, "Missing details for zone %s in buddy info" % zone ) self.assertTrue( order in buddyinfo[zone], 'Missing details for order "%s" in buddy info' % order, ) count = buddyinfo[zone][order][node] self.assertTrue( count == except_count, 'Wrong chunk count for order %s in zone "%s" for node "%s" (got: %d, expect %d)' % (order, zone, node, count, except_count), ) def test_010_extract_zoneinfo(self): """Test extracting watermark information""" oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7) analyser = OOMAnalyser.OOMAnalyser(oom) success = analyser.analyse() self.assertTrue(success, "OOM analysis failed") self.assertEqual( analyser.oom_result.kconfig.release, (3, 10, ".el7."), "Wrong KernelConfig release", ) watermarks = analyser.oom_result.watermarks for zone, node, level, except_level in [ ("Normal", 0, "free", 36692), ("Normal", 0, "min", 36784), ("Normal", 1, "low", 56804), ("Normal", 1, "high", 68164), ("DMA", 0, "free", 15872), ("DMA", 0, "high", 60), ("DMA32", 0, "free", 59728), ("DMA32", 0, "low", 9788), ]: self.assertTrue( zone in watermarks, "Missing details for zone %s in memory watermarks" % zone, ) self.assertTrue( node in watermarks[zone], 'Missing details for node "%s" in memory watermarks' % node, ) self.assertTrue( level in watermarks[zone][node], 'Missing details for level "%s" in memory watermarks' % level, ) level = watermarks[zone][node][level] self.assertTrue( level == except_level, 'Wrong watermark level for node %s in zone "%s" (got: %d, expect %d)' % (node, zone, level, except_level), ) node = analyser.oom_result.details["trigger_proc_numa_node"] self.assertTrue( node == 0, "Wrong node with memory shortage (got: %s, expect: 0)" % node ) self.assertEqual( analyser.oom_result.kconfig.MAX_ORDER, 11, # This is a hard coded value as extracted from kernel 6.2.0 "Unexpected number of chunk sizes (got: %s, expect: 11 (kernel 6.2.0))" % analyser.oom_result.kconfig.MAX_ORDER, ) def test_011_alloc_failure(self): """Test analysis why the memory allocation could be failed""" oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7) analyser = OOMAnalyser.OOMAnalyser(oom) success = analyser.analyse() self.assertTrue(success, "OOM analysis failed") self.assertEqual( analyser.oom_result.oom_type, OOMAnalyser.OOMEntityType.automatic, "OOM triggered manually", ) self.assertTrue(analyser.oom_result.buddyinfo, "Missing buddyinfo") self.assertTrue( "trigger_proc_order" in analyser.oom_result.details and "trigger_proc_mem_zone" in analyser.oom_result.details, "Missing trigger_proc_order and/or trigger_proc_mem_zone", ) self.assertTrue(analyser.oom_result.watermarks, "Missing watermark information") for zone, order, node, expected_result in [ ("DMA", 0, 0, True), ("DMA", 6, 0, True), ("DMA32", 0, 0, True), ("DMA32", 10, 0, False), ("Normal", 0, 0, True), ("Normal", 0, 1, True), ("Normal", 6, 0, False), ("Normal", 6, 1, True), ("Normal", 7, 0, False), ("Normal", 7, 1, True), ("Normal", 9, 0, False), ("Normal", 9, 1, False), ]: result = analyser._check_free_chunks(order, zone, node) self.assertEqual( result, expected_result, "Wrong result of the check for free chunks with the same or higher order for Node %d, " 'Zone "%s" and order %d (got: %s, expected %s)' % (node, zone, order, result, expected_result), ) # Search node with memory shortage: watermark "free" < "min" for zone, expected_node in [ ("DMA", None), ("DMA32", None), ("Normal", 0), ]: # override zone with test data and trigger extracting node analyser.oom_result.details["trigger_proc_mem_zone"] = zone analyser._search_node_with_memory_shortage() node = analyser.oom_result.details["trigger_proc_numa_node"] self.assertEqual( node, expected_node, 'Wrong result if a node has memory shortage in zone "%s" (got: %s, expected %s)' % (zone, node, expected_node), ) self.assertEqual( analyser.oom_result.mem_alloc_failure, OOMAnalyser.OOMMemoryAllocFailureType.failed_below_low_watermark, "Unexpected reason why the memory allocation has failed.", ) def test_012_fragmentation(self): """Test memory fragmentation""" oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7) analyser = OOMAnalyser.OOMAnalyser(oom) success = analyser.analyse() self.assertTrue(success, "OOM analysis failed") zone = analyser.oom_result.details["trigger_proc_mem_zone"] node = analyser.oom_result.details["trigger_proc_numa_node"] mem_fragmented = not analyser._check_free_chunks( analyser.oom_result.kconfig.PAGE_ALLOC_COSTLY_ORDER, zone, node ) self.assertFalse( mem_fragmented, 'Memory of Node %d, Zone "%s" is not fragmented, but reported as fragmented' % (node, zone), ) def test_013_page_size(self): """Test determination of the page size""" oom = OOMAnalyser.OOMEntity(OOMAnalyser.OOMDisplay.example_rhel7) analyser = OOMAnalyser.OOMAnalyser(oom) success = analyser.analyse() self.assertTrue(success, "OOM analysis failed") page_size_kb = analyser.oom_result.details["page_size_kb"] self.assertEqual( page_size_kb, 4, "Unexpected page size (got %s, expect: 4)" % page_size_kb, ) self.assertEqual( analyser.oom_result.details["_page_size_guessed"], False, "Page size guessed and not determinated", ) if __name__ == "__main__": unittest.main(verbosity=2) 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!663 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor