Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apport/procutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (C) 2026 Canonical Ltd.
# Author: Benjamin Drung <bdrung@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

"""Functions to operate on files in /proc."""

from collections.abc import Iterable


def parse_meminfo(keys: Iterable[str]) -> dict[str, int]:
"""Parse /proc/meminfo and return a dictionary for the requested keys."""
remaining_keys = set(keys)
meminfo = {}
with open("/proc/meminfo", "r", encoding="utf-8") as meminfo_file:
for line in meminfo_file:
key, remaining = line.split(":", maxsplit=1)
if key not in remaining_keys:
continue
value = remaining.strip().split(maxsplit=1)[0]
meminfo[key] = int(value)
remaining_keys.remove(key)
if not remaining_keys:
return meminfo
raise KeyError(f"{' '.join(sorted(remaining_keys))} not found in /proc/meminfo")
16 changes: 4 additions & 12 deletions data/apport
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ from collections.abc import Callable

import apport.fileutils
import apport.report
from apport.procutils import parse_meminfo
from apport.user_group import UserGroupID
from problem_report import CompressedFile

Expand Down Expand Up @@ -338,20 +339,11 @@ def write_user_coredump(
os.close(core_file)


def usable_ram():
def usable_ram() -> int:
"""Return how many bytes of RAM is currently available that can be
allocated without causing major thrashing."""

# abuse our excellent RFC822 parser to parse /proc/meminfo
r = apport.report.Report()
with open("/proc/meminfo", "rb") as f:
r.load(f)

memfree = int(r["MemFree"].split()[0])
cached = int(r["Cached"].split()[0])
writeback = int(r["Writeback"].split()[0])

return (memfree + cached - writeback) * 1024
meminfo = parse_meminfo({"Cached", "MemFree", "Writeback"})
return (meminfo["MemFree"] + meminfo["Cached"] - meminfo["Writeback"]) * 1024


def _run_with_output_limit_and_timeout(
Expand Down
7 changes: 2 additions & 5 deletions tests/system/test_apport_valgrind.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@
import tempfile
import unittest

from apport.procutils import parse_meminfo
from tests.helper import get_gnu_coreutils_cmd, skip_if_command_is_missing
from tests.paths import local_test_environment

with open("/proc/meminfo", encoding="utf-8") as f:
for line in f.readlines():
if line.startswith("MemTotal"):
MEM_TOTAL_MiB = int(line.split()[1]) // 1024
break
MEM_TOTAL_MiB = parse_meminfo({"MemTotal"})["MemTotal"]


@skip_if_command_is_missing("valgrind")
Expand Down
9 changes: 3 additions & 6 deletions tests/system/test_signal_crashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import apport.fileutils
import apport.report
from apport.procutils import parse_meminfo
from tests.helper import (
get_gnu_coreutils_cmd,
get_init_system,
Expand Down Expand Up @@ -159,12 +160,8 @@ def test_limit_size(self) -> None:
assert self.apport_path is not None
# determine how much data we have to pump into apport in order to make
# sure that it will refuse the core dump
r = apport.report.Report()
with open("/proc/meminfo", "rb") as f:
r.load(f)
totalmb = int(r["MemFree"].split()[0]) + int(r["Cached"].split()[0])
totalmb = int(totalmb / 1024)
del r
meminfo = parse_meminfo({"Cached", "MemFree"})
totalmb = (meminfo["MemFree"] + meminfo["Cached"]) // 1024

test_proc = self.create_test_process()
try:
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/test_procutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright (C) 2026 Canonical Ltd.
# Author: Benjamin Drung <bdrung@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

"""Unit tests for the apport.procutils module."""

from unittest.mock import mock_open, patch

import pytest

from apport.procutils import parse_meminfo


def test_parse_meminfo() -> None:
"""Test parse_meminfo() reading all values successfully."""
open_mock = mock_open(
read_data=(
"MemTotal: 128887668 kB\n"
"MemFree: 81203612 kB\n"
"HugePages_Total: 0\n"
"DirectMap1G: 127926272 kB\n"
)
)
with patch("builtins.open", open_mock):
meminfo = parse_meminfo({"MemTotal", "MemFree", "HugePages_Total"})
assert meminfo["MemTotal"] == 128887668
assert meminfo["MemFree"] == 81203612
assert meminfo["HugePages_Total"] == 0
assert len(meminfo) == 3


def test_parse_meminfo_missing_key() -> None:
"""Test parse_meminfo() failing to read the requested keys."""
open_mock = mock_open(
read_data=("MemTotal: 128887668 kB\nMemFree: 81203612 kB\n")
)
with patch("builtins.open", open_mock):
with pytest.raises(KeyError) as exc_info:
parse_meminfo({"MemTotal", "MemAvailable"})
exc_info.match("MemAvailable not found in /proc/meminfo")
Loading