summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@vyos.io>2026-08-21 08:00:50 -0500
committerGitHub <noreply@github.com>2026-08-21 08:00:50 -0500
commit0712fac9dddb4dbfede4409867d2973ccbfb5aa2 (patch)
tree54b01fae4c493735918efb5229b613017521fe74 /src
parentac99e72affedc97c62d47d2391ade14fd6c2e884 (diff)
parent7b960007e817b722596b7e388577d73ad7a87ed7 (diff)
downloadvyos-1x-0712fac9dddb4dbfede4409867d2973ccbfb5aa2.tar.gz
vyos-1x-0712fac9dddb4dbfede4409867d2973ccbfb5aa2.zip
Merge pull request #5325 from alexandr-san4ez/T8868-rolling
kdump: T8868: Implement kernel crash dump support
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/system_option.py99
-rw-r--r--src/etc/systemd/system/kdump-tools.service.d/override.conf19
-rwxr-xr-xsrc/helpers/run-config-activation.py3
-rwxr-xr-xsrc/helpers/run-config-migration.py5
-rwxr-xr-xsrc/helpers/vyos-boot-config-loader.py7
-rwxr-xr-xsrc/op_mode/generate_tech-support_archive.py28
-rwxr-xr-xsrc/op_mode/image_installer.py18
-rw-r--r--src/op_mode/kdump.py256
-rwxr-xr-xsrc/op_mode/memory.py19
-rw-r--r--src/op_mode/show_techsupport_report.py5
10 files changed, 432 insertions, 27 deletions
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index 8951adcff..190db232d 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -26,22 +26,27 @@ from vyos.base import Warning
from vyos.config import Config
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
+from vyos.defaults import KDUMP_DEFAULT_MEMORY_AUTO
+from vyos.system import image
from vyos.system import grub_util
from vyos.template import render
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.convert import range_str_to_list
from vyos.utils.convert import list_to_range_str
+from vyos.utils.convert import human_to_bytes
from vyos.utils.cpu import get_cpus
from vyos.utils.cpu import get_available_cpus
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
-from vyos.utils.file import read_file
from vyos.utils.kernel import check_kmod
+from vyos.utils.kernel import get_kernel_boot_arg
+from vyos.utils.kernel import get_crash_kernel_size
from vyos.utils.process import cmdl
from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_addr_assigned
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.system import sysctl_write
+from vyos.utils.memory import get_memory_info
from vyos.configdep import set_dependents
from vyos.configdep import call_dependents
from vyos import ConfigError
@@ -73,6 +78,15 @@ tuned_profiles = {
'virtual-host': 'virtual-host',
}
+# Path to the kdump-tools configuration file controlling dump behaviour
+KDUMP_CONFIG_FILE = '/etc/default/kdump-tools'
+
+# initramfs-tools drop-in configuration applied during kdump initrd
+# generation. Named with 'zzzz-' prefix to ensure it is sourced last,
+# overriding any conflicting settings from other drop-ins (including the
+# `MODULES=dep` override written by the kdump-tools kernel postinst hook).
+KDUMP_INITRAMFS_HOOK = '/etc/initramfs-tools/conf.d/zzzz-kdump-vyos-overlay'
+
MANAGED_PARAMS = {
'hugepages1g': {
'parse': r'hugepagesz=1[Gg]\s+hugepages=(?P<hugepages1g>\d+)',
@@ -168,6 +182,11 @@ MANAGED_PARAMS = {
'clean': r'numa_balancing=\S+',
'type': str,
},
+ 'crashkernel': {
+ 'parse': r'crashkernel=(?P<crashkernel>\S+)',
+ 'clean': r'crashkernel=\S+',
+ 'type': str,
+ },
}
# Compiled regex pattern for parsing command line options
@@ -198,9 +217,18 @@ def get_config(config=None):
conf = Config()
base = ['system', 'option']
options = conf.get_config_dict(
- base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
+ base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True,
+ with_pki=True,
)
+ if 'kdump' not in options:
+ options['kdump'] = {}
+ options['kdump']['is_live_boot'] = image.is_live_boot()
+ options['kdump']['disabled'] = not conf.exists(base + ['kdump'])
+
if 'performance' in options:
# Update IPv4/IPv6 and sysctl options after tuned applied it's settings
set_dependents('ip_ipv6', conf)
@@ -314,6 +342,32 @@ def verify(options):
f'({reserved_gb} GB is reserved for system usage and services)'
)
+ if not options['kdump']['disabled']:
+ kdump = options['kdump']
+
+ if kdump.get('is_live_boot'):
+ raise ConfigError('kdump is not supported on live boot images')
+
+ # Check that the configured crash kernel memory does not exceed the
+ # total system RAM for the applicable range
+ memory = kdump.get('memory')
+ if memory and memory != 'auto':
+ memory_info = get_memory_info()
+ # Convert kilobytes to bytes
+ memory_total = memory_info['MemTotal'] * 1024
+
+ # 'memory_info' doesn't consider crash kernel reservation size
+ memory_total += get_crash_kernel_size()
+
+ # Convert scalar value from megabytes to bytes
+ memory_bytes = human_to_bytes(f'{memory}mb')
+
+ # Ensure that specified memory does not exceed total virtual memory
+ if memory_bytes >= memory_total:
+ raise ConfigError(
+ f'Specified kdump memory {memory!r} exceeds the available system RAM'
+ )
+
return None
@@ -401,6 +455,45 @@ def generate(options):
if count:
cmdline_options.append(f'hugepages={count}')
+ kdump = options['kdump']
+ if not kdump['disabled']:
+ # Crash kernel memory reservation
+ kdump_memory = kdump.get('memory')
+ if kdump_memory:
+ if kdump_memory == 'auto':
+ # Tiered auto-sizing based on total system RAM
+ kdump_memory = KDUMP_DEFAULT_MEMORY_AUTO
+ else:
+ # Append suffix (megabytes) to right format
+ kdump_memory = f'{kdump_memory}M'
+
+ # The `crashkernel=` parameter instructs the boot kernel to reserve a
+ # dedicated memory region for the capture kernel.
+ cmdline_options.append(f'crashkernel={kdump_memory}')
+
+ # Render the kdump-tools configuration file from the Jinja2 template
+ render(KDUMP_CONFIG_FILE, 'system/kdump_tools.conf.j2', kdump)
+
+ # VyOS uses an OverlayFS root filesystem. When mkinitramfs runs with
+ # `MODULES=dep`, it attempts to determine which kernel modules are needed
+ # by walking up the device tree from /. On a real block device
+ # this works correctly, but OverlayFS has no backing block device to walk from,
+ # causing mkinitramfs to fail with:
+ # mkinitramfs: failed to determine device for /
+ write_file(KDUMP_INITRAMFS_HOOK, 'MODULES=most')
+ else:
+ # kdump has been disabled - render the
+ # kdump-tools configuration with the disabled flag so the service
+ # starts in a no-op state rather than attempting to load a capture
+ # kernel with stale or missing parameters.
+ render(KDUMP_CONFIG_FILE, 'system/kdump_tools.conf.j2', kdump)
+
+ # Remove ephemeral files after the feature is disabled
+ kdump_delete_files = (KDUMP_INITRAMFS_HOOK,)
+ for delete_file in kdump_delete_files:
+ if os.path.exists(delete_file):
+ os.unlink(delete_file)
+
cmdline_options_str = ' '.join(cmdline_options)
grub_util.update_kernel_cmdline_options(cmdline_options_str)
@@ -464,7 +557,7 @@ def generate_cmdline_for_kexec(options):
- new_cmdline (str): The updated kernel command line string.
"""
# Read current cmdline and parse it
- current_cmdline = read_file('/proc/cmdline').strip()
+ current_cmdline = get_kernel_boot_arg().strip()
current_parsed = parse_cmdline(current_cmdline)
# Parse desired options from options['cmdline_options']
diff --git a/src/etc/systemd/system/kdump-tools.service.d/override.conf b/src/etc/systemd/system/kdump-tools.service.d/override.conf
new file mode 100644
index 000000000..67419e10b
--- /dev/null
+++ b/src/etc/systemd/system/kdump-tools.service.d/override.conf
@@ -0,0 +1,19 @@
+[Unit]
+Description=Kernel crash dump capture service
+DefaultDependencies=no
+Before=
+After=
+
+[Service]
+Type=oneshot
+StandardOutput=journal+console
+EnvironmentFile=/etc/default/kdump-tools
+ExecStart=
+ExecStart=/etc/init.d/kdump-tools start
+ExecStop=
+ExecStop=/etc/init.d/kdump-tools stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=
+WantedBy=vyos.target
diff --git a/src/helpers/run-config-activation.py b/src/helpers/run-config-activation.py
index 7e7a6571e..9e048e193 100755
--- a/src/helpers/run-config-activation.py
+++ b/src/helpers/run-config-activation.py
@@ -30,6 +30,7 @@ from vyos.utils.activate import set_activation
from vyos.utils.activate import is_active
from vyos.utils.system import load_as_module
from vyos.utils.func import FalseCallable
+from vyos.utils.kernel import get_kernel_boot_arg
from vyos.defaults import directories
from vyos.defaults import activation_list
@@ -54,7 +55,7 @@ fh.setFormatter(formatter)
logger.addHandler(fh)
-if 'vyos-activate-debug' in Path('/proc/cmdline').read_text():
+if get_kernel_boot_arg('vyos-activate-debug') is not None:
print(f'\nactivate-debug enabled: file {checkpoint_file}_* on error')
debug = checkpoint_file
logger.setLevel(logging.DEBUG)
diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py
index 6329d2979..f538c1e86 100755
--- a/src/helpers/run-config-migration.py
+++ b/src/helpers/run-config-migration.py
@@ -19,7 +19,7 @@ import sys
import time
from argparse import ArgumentParser
from shutil import copyfile
-from vyos.utils.file import read_file
+from vyos.utils.kernel import get_kernel_boot_arg
from vyos.migrate import ConfigMigrate
from vyos.migrate import ConfigMigrateError
@@ -80,6 +80,5 @@ if backup is not None and not config_migrate.config_modified:
# T1771: add knob on Kernel command-line to simulate failed config migrator run
# used to test if the automatic image reboot works.
-kernel_cmdline = read_file('/proc/cmdline')
-if 'vyos-fail-migration' in kernel_cmdline.split():
+if get_kernel_boot_arg('vyos-fail-migration') is not None:
sys.exit(1)
diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py
index a3a66eedc..e01b5bff6 100755
--- a/src/helpers/vyos-boot-config-loader.py
+++ b/src/helpers/vyos-boot-config-loader.py
@@ -28,6 +28,7 @@ from vyos.configsession import ConfigSessionError
from vyos.configtree import ConfigTree
from vyos.utils.process import cmdl
from vyos.utils.file import write_file
+from vyos.utils.kernel import get_kernel_boot_arg
STATUS_FILE = config_status
TRACE_FILE = '/tmp/boot-config-trace'
@@ -44,11 +45,9 @@ else:
LOG_FILE = LOG_DIR + '/vyos-boot-config-loader.log'
try:
- with open('/proc/cmdline', 'r') as f:
- cmdline = f.read()
- if 'vyos-debug' in cmdline:
+ if get_kernel_boot_arg('vyos-debug') is not None:
os.environ['VYOS_DEBUG'] = 'yes'
- if 'vyos-config-debug' in cmdline:
+ if get_kernel_boot_arg('vyos-config-debug') is not None:
os.environ['VYOS_DEBUG'] = 'yes'
trace_config = True
except Exception as e:
diff --git a/src/op_mode/generate_tech-support_archive.py b/src/op_mode/generate_tech-support_archive.py
index 2719d231c..7b539473c 100755
--- a/src/op_mode/generate_tech-support_archive.py
+++ b/src/op_mode/generate_tech-support_archive.py
@@ -17,6 +17,7 @@
import os
import argparse
import glob
+import json
from datetime import datetime
from pathlib import Path
from shutil import rmtree
@@ -24,7 +25,9 @@ from socket import gethostname
from sys import exit
from tarfile import open as tar_open
+from vyos.base import Warning
from vyos.defaults import directories
+from vyos.utils.process import rc_cmd
from vyos.utils.process import call
from vyos.utils.process import cmdl
from vyos.utils.file import get_name_from_path
@@ -38,6 +41,8 @@ ARCHIVE_TMP_DIR_PATTERN = 'drops-debug_'
DEFAULT_TMP_DIR = '/tmp'
EXCLUDED_ARCHIVE_EXT = ('.iso', '.gz', '.tar', '.zip')
+vyos_op_scripts_dir = directories['op_mode']
+
def __rotate_logs(path: str, log_pattern:str):
files_list = glob.glob(f'{path}/{log_pattern}')
@@ -53,7 +58,6 @@ def __save_show_report_files(reports_dir: Path):
:type reports_dir: pathlib.Path
"""
- vyos_op_scripts_dir = directories['op_mode']
script_path = f'{vyos_op_scripts_dir}/show_techsupport_report.py'
arguments = [
'--launched-from-generate-archive',
@@ -111,6 +115,28 @@ def __generate_archived_files(location_path: str) -> None:
'run': '/run',
}
+ # Get the directory of the last crash dump and add the crash directory to archive dictionary
+ err_code, out = rc_cmd([f'{vyos_op_scripts_dir}/kdump.py', 'show_dumps', '--raw'])
+ if not err_code:
+ try:
+ dumps = json.loads(out)
+ except json.decoder.JSONDecodeError as e:
+ Warning(f'Unable to parse kdump dump list. {e}')
+ dumps = []
+
+ if dumps:
+ last_dump = dumps[-1]
+ # Before enabling the dump, consider the size as
+ # it can range from hundreds of MB to several GB
+ max_dump_archive_bytes = 1 * 1024**3 # 1 GB cap
+ if last_dump['size_bytes'] > max_dump_archive_bytes:
+ size_human = last_dump['size_human']
+ Warning(
+ f"Skipping crash dump archival: {size_human} exceeds limit 1 GB"
+ )
+ else:
+ archive_dict['last-crash-dump'] = last_dump['directory']
+
for archive_name, path in archive_dict.items():
if not os.path.exists(path):
continue
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 5174ff3d2..fb28f182e 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -47,6 +47,7 @@ from vyos.config_mgmt import unsaved_commits
from vyos.defaults import base_dir
from vyos.defaults import directories
from vyos.defaults import activation_hint
+from vyos.defaults import KDUMP_DEFAULT_MEMORY_AUTO
from vyos.flavor import get_image_serial_console
from vyos.remote import download
from vyos.system import disk
@@ -525,9 +526,11 @@ def get_cli_kernel_options(config_file: str) -> list:
config = ConfigTree(read_file(config_file))
config_dict = loads(config.to_json())
cmdline_options = []
- kernel_options = dict_search('system.option.kernel', config_dict)
- if kernel_options is None:
+
+ options = dict_search('system.option', config_dict)
+ if options is None:
return cmdline_options
+ kernel_options = options.get('kernel', {})
k_cpu_opts = kernel_options.get('cpu', {})
k_memory_opts = kernel_options.get('memory', {})
@@ -591,6 +594,17 @@ def get_cli_kernel_options(config_file: str) -> list:
if count:
cmdline_options.append(f'hugepages={count}')
+ # Crash kernel memory reservation for the capture kernel
+ kdump_memory = options.get('kdump', {}).get('memory')
+ if kdump_memory:
+ if kdump_memory == 'auto':
+ # Tiered auto-sizing based on total system RAM
+ kdump_memory = KDUMP_DEFAULT_MEMORY_AUTO
+ else:
+ # Append suffix (megabytes) to right format
+ kdump_memory = f'{kdump_memory}M'
+ cmdline_options.append(f'crashkernel={kdump_memory}')
+
return cmdline_options
def configure_authentication(config_file: str, password: str) -> None:
diff --git a/src/op_mode/kdump.py b/src/op_mode/kdump.py
new file mode 100644
index 000000000..560650bdf
--- /dev/null
+++ b/src/op_mode/kdump.py
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) VyOS Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+from datetime import datetime
+
+import vyos.opmode
+from tabulate import tabulate
+from vyos.configquery import ConfigTreeQuery
+from vyos.defaults import systemd_services
+from vyos.utils.convert import bytes_to_human
+from vyos.utils.kernel import get_kernel_boot_arg
+from vyos.utils.kernel import is_crash_kernel_loaded
+from vyos.utils.kernel import get_crash_kernel_size
+from vyos.utils.process import is_systemd_service_active
+
+# systemd unit managed by kdump-tools that loads the capture kernel
+# and handles vmcore saving after a panic
+KDUMP_SERVICE = systemd_services['kdump']
+
+# Base path to boot files related to kdump
+KDUMP_LIB_PATH = '/var/lib/kdump'
+
+DEFAULT_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
+
+conf = ConfigTreeQuery()
+base = ['system', 'option', 'kdump']
+
+
+def _get_config() -> dict:
+ """Return the effective kdump config dict, or {} when unconfigured"""
+
+ if not conf.exists(base):
+ return {}
+
+ return conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
+ )
+
+
+def _read_link(symlink_path: str) -> str | None:
+ """Read the destination of the symbolic link"""
+ try:
+ destination_path = os.readlink(symlink_path)
+ except OSError:
+ return None
+
+ # os.readlink() may return a relative target.
+ # Interpret it relative to the link directory
+ if not os.path.isabs(destination_path):
+ destination_path = os.path.join(os.path.dirname(symlink_path), destination_path)
+
+ destination_path = os.path.realpath(destination_path)
+ return destination_path if os.path.isfile(destination_path) else None
+
+
+def _list_dumps(dump_path: str) -> list[dict]:
+ """Return a sorted list of vmcore entries found under *dump_path*.
+
+ kdump-tools stores each capture in its own timestamped sub-directory
+ using the layout:
+
+ <dump_path>/<YYYYMMDDHHMM>/dump.<YYYYMMDDHHMM>
+ <dump_path>/<YYYYMMDDHHMM>/dmesg.<YYYYMMDDHHMM> (optional)
+
+ The reported size is the combined size of both files when the dmesg
+ companion file is present.
+ """
+
+ entries = []
+ if not os.path.isdir(dump_path):
+ return entries
+
+ # Each sub-directory name is the timestamp of the crash event
+ for name in sorted(os.listdir(dump_path)):
+ sub = os.path.join(dump_path, name)
+ if not os.path.isdir(sub):
+ continue
+
+ dump = os.path.join(sub, f'dump.{name}')
+ dmesg = os.path.join(sub, f'dmesg.{name}')
+
+ if os.path.isfile(dump):
+ stat_dump = os.stat(dump)
+ stat_dmesg = os.stat(dmesg) if os.path.isfile(dmesg) else None
+ full_size = stat_dump.st_size + (stat_dmesg.st_size if stat_dmesg else 0)
+
+ entry = {
+ 'directory': sub,
+ 'size_bytes': full_size,
+ 'size_human': bytes_to_human(full_size),
+ 'modify_time': stat_dump.st_mtime,
+ }
+
+ entries.append(entry)
+
+ return entries
+
+
+def _get_raw_status() -> dict:
+ """Collect all kdump runtime and configuration data into a single dict"""
+
+ cfg = _get_config()
+
+ memory = cfg.get('memory')
+ dump_path = cfg.get('dump_path')
+ dumps = _list_dumps(dump_path) if dump_path else []
+ last_dump = dumps[-1] if dumps else None
+
+ return {
+ 'configured': bool(cfg),
+ 'service_active': is_systemd_service_active(KDUMP_SERVICE),
+ 'crash_kernel_loaded': is_crash_kernel_loaded(),
+ 'crash_kernel_size': get_crash_kernel_size(),
+ 'cmdline_value': get_kernel_boot_arg('crashkernel') or '',
+ 'vmlinuz_path': _read_link(f'{KDUMP_LIB_PATH}/vmlinuz'),
+ 'initrd_path': _read_link(f'{KDUMP_LIB_PATH}/initrd.img'),
+ 'memory': memory,
+ 'dump_path': dump_path,
+ 'last_dump': last_dump,
+ }
+
+
+def _get_raw_dumps() -> list[dict]:
+ cfg = _get_config()
+ dump_path = cfg.get('dump_path')
+ return _list_dumps(dump_path) if dump_path else []
+
+
+def _verify_config():
+ """Raise UnconfiguredSubsystem when kdump has not been configured"""
+
+ cfg = _get_config()
+ if not cfg:
+ conf_command = ' '.join(['set'] + base)
+ raise vyos.opmode.UnconfiguredSubsystem(
+ f'kdump is not configured - use "{conf_command}" to enable it.'
+ )
+
+
+def _format_status(data: dict) -> str:
+ """Render the kdump status dict as a human-readable plain-text table"""
+
+ header = 'Kernel crash dump (kdump) status'
+
+ cfg_str = 'configured' if data['configured'] else 'not configured'
+ svc_str = 'active' if data['service_active'] else 'inactive'
+ loaded_str = 'loaded' if data['crash_kernel_loaded'] else 'not loaded'
+
+ rows = [
+ ('Configuration', cfg_str),
+ ('Systemd service', svc_str),
+ ('Crash kernel', loaded_str),
+ ]
+
+ if data['crash_kernel_loaded']:
+ # Show reserved memory size and the boot argument that produced it
+
+ raw = data['crash_kernel_size']
+ rows.append(('Reserved memory', bytes_to_human(raw)))
+
+ rows.append(("Boot 'crashkernel' value", data['cmdline_value']))
+ else:
+ # Capture kernel is not loaded and memory reservation requires a reboot
+ rows.append(('Reserved memory', 'none (reboot required to reserve memory)'))
+
+ if data['configured']:
+ rows.append(('Memory parameter', data['memory']))
+ rows.append(('Directory to save dumps', data['dump_path']))
+
+ # Display the latest date of created dump
+ last_dump_date = 'none'
+ if data['last_dump']:
+ dt = datetime.fromtimestamp(data['last_dump']['modify_time'])
+ last_dump_date = dt.strftime(DEFAULT_TIME_FORMAT)
+ rows.append(('Last dump date', last_dump_date))
+
+ # Show `initrd.img` and `vmlinuz` status which are critical
+ # for a successful boot process
+ rows.append(('Initial RAM disk image', data['initrd_path'] or 'none'))
+ rows.append(('Linux kernel executable file', data['vmlinuz_path'] or 'none'))
+
+ # Expand each row to a three-column tuple so tabulate can align the
+ # separator colon independently of the label and value columns
+ rows = [(header, ':', value) for header, value in rows]
+ table = tabulate(rows, tablefmt='plain')
+
+ return f'{header}\n\n{table}'
+
+
+def _format_dumps(entries: list[dict]) -> str:
+ """Render the crash dump list as a human-readable table"""
+
+ if not entries:
+ return 'No kernel crash dumps recorded'
+
+ headers = ['DIRECTORY', 'SIZE', 'TIME']
+ rows = []
+
+ for entry in entries:
+ dt = datetime.fromtimestamp(entry['modify_time'])
+ row = (
+ entry['directory'],
+ entry['size_human'],
+ dt.strftime(DEFAULT_TIME_FORMAT),
+ )
+ rows.append(row)
+
+ # Append a blank separator row followed by an aggregate totals row
+ total = sum(entry['size_bytes'] for entry in entries)
+ rows.append(('', '', ''))
+ rows.append((f'Total: {len(entries)} dump(s)', bytes_to_human(total), ''))
+
+ return tabulate(rows, headers, tablefmt='simple')
+
+
+def show_status(raw: bool):
+ """Show kdump service status and configuration summary"""
+
+ _verify_config()
+
+ data = _get_raw_status()
+ return data if raw else _format_status(data)
+
+
+def show_dumps(raw: bool):
+ """Show recorded kernel crash dumps"""
+
+ _verify_config()
+
+ data = _get_raw_dumps()
+ return data if raw else _format_dumps(data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/memory.py b/src/op_mode/memory.py
index 20d937243..47a9c476e 100755
--- a/src/op_mode/memory.py
+++ b/src/op_mode/memory.py
@@ -18,23 +18,16 @@
import sys
import vyos.opmode
+from vyos.utils.memory import get_memory_info
def _get_raw_data():
- from re import search as re_search
+ mem_info = get_memory_info()
- def find_value(keyword, mem_data):
- regex = keyword + ':\s+(\d+)'
- res = re_search(regex, mem_data).group(1)
- return int(res)
-
- with open("/proc/meminfo", "r") as f:
- mem_data = f.read()
-
- total = find_value('MemTotal', mem_data)
- available = find_value('MemAvailable', mem_data)
- buffers = find_value('Buffers', mem_data)
- cached = find_value('Cached', mem_data)
+ total = mem_info['MemTotal']
+ available = mem_info['MemAvailable']
+ buffers = mem_info['Buffers']
+ cached = mem_info['Cached']
used = total - available
diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py
index 2434da29a..fa6bff6ba 100644
--- a/src/op_mode/show_techsupport_report.py
+++ b/src/op_mode/show_techsupport_report.py
@@ -543,6 +543,11 @@ REPORTS: dict[str, tuple[BaseSpec]] = {
CommandSpec('', 'vppctl show mpls tunnel'),
CommandSpec('', 'vppctl show trace'),
),
+ 'kernel-dump': (
+ CommandSpec('Kdump status and configuration', op('show system kdump')),
+ CommandSpec('', 'kdump-config show'),
+ CommandSpec('Recorded kernel crash dumps', op('show system kdump dumps')),
+ ),
}