Skip to content

Commit 1d28f9a

Browse files
authored
gh-150114: Log the memory usage in regrtest (#150255)
On Linux, log the total memory usage of all Python test processes. Read the private memory in /proc/pid/smaps.
1 parent c7cab73 commit 1d28f9a

5 files changed

Lines changed: 65 additions & 4 deletions

File tree

Lib/test/libregrtest/logger.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import time
3+
from typing import Callable
34

45
from test.support import MS_WINDOWS
56
from .results import TestResults
@@ -19,16 +20,27 @@ def __init__(self, results: TestResults, quiet: bool, pgo: bool):
1920
self._results: TestResults = results
2021
self._quiet: bool = quiet
2122
self._pgo: bool = pgo
23+
self.get_mem_usage: Callable[[], int | None] | None = None
2224

2325
def log(self, line: str = '') -> None:
2426
empty = not line
2527

26-
# add the system load prefix: "load avg: 1.80 "
28+
# Add the memory usage: "mem: 1 GiB "
29+
if self.get_mem_usage is not None:
30+
mem = self.get_mem_usage()
31+
if mem:
32+
mib = mem / (1024*1024)
33+
if mib >= 1024:
34+
line = f"mem: {mib / 1024:.1f} GiB {line}"
35+
else:
36+
line = f"mem: {mib:.1f} MiB {line}"
37+
38+
# Add the system load prefix: "load avg: 1.80 "
2739
load_avg = self.get_load_avg()
2840
if load_avg is not None:
2941
line = f"load avg: {load_avg:.2f} {line}"
3042

31-
# add the timestamp prefix: "0:01:05 "
43+
# Add the timestamp prefix: "0:01:05 "
3244
log_time = time.perf_counter() - self.start_time
3345

3446
mins, secs = divmod(int(log_time), 60)

Lib/test/libregrtest/run_workers.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .single import PROGRESS_MIN_TIME
2323
from .utils import (
2424
StrPath, TestName,
25-
format_duration, print_warning, count, plural)
25+
format_duration, print_warning, count, plural, get_process_memory_usage)
2626
from .worker import create_worker_process, USE_PROCESS_GROUP
2727

2828
if MS_WINDOWS:
@@ -452,6 +452,12 @@ def wait_stopped(self, start_time: float) -> None:
452452
print_warning(f"Failed to join {self} in {format_duration(dt)}")
453453
break
454454

455+
def get_mem_usage(self):
456+
popen = self._popen
457+
if popen is None:
458+
return
459+
return get_process_memory_usage(popen.pid)
460+
455461

456462
def get_running(workers: list[WorkerThread]) -> str | None:
457463
running: list[str] = []
@@ -473,6 +479,7 @@ def __init__(self, num_workers: int, runtests: RunTests,
473479
logger: Logger, results: TestResults) -> None:
474480
self.num_workers = num_workers
475481
self.runtests = runtests
482+
self.logger = logger
476483
self.log = logger.log
477484
self.display_progress = logger.display_progress
478485
self.results: TestResults = results
@@ -598,9 +605,21 @@ def _process_result(self, item: QueueOutput) -> TestResult:
598605

599606
return result
600607

608+
def get_mem_usage(self):
609+
usage = 0
610+
main_mem = get_process_memory_usage(os.getpid())
611+
if main_mem:
612+
usage += main_mem
613+
for worker in self.workers:
614+
worker_mem = worker.get_mem_usage()
615+
if worker_mem:
616+
usage += worker_mem
617+
return usage
618+
601619
def run(self) -> None:
602620
fail_fast = self.runtests.fail_fast
603621
fail_env_changed = self.runtests.fail_env_changed
622+
self.logger.get_mem_usage = self.get_mem_usage
604623

605624
self.start_workers()
606625

@@ -625,3 +644,4 @@ def run(self) -> None:
625644
# worker when we exit this function
626645
self.pending.stop()
627646
self.stop_workers()
647+
self.logger.get_mem_usage = None

Lib/test/libregrtest/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,3 +752,26 @@ def display_title(title):
752752
print(title)
753753
print("#" * len(title))
754754
print(flush=True)
755+
756+
757+
def get_process_memory_usage(pid: int) -> int | None:
758+
"""
759+
Read the private memory in bytes from /proc/pid/smaps.
760+
"""
761+
try:
762+
fp = open(f"/proc/{pid}/smaps", "rb")
763+
except OSError:
764+
return None
765+
766+
try:
767+
total = 0
768+
with fp:
769+
for line in fp:
770+
# Include both Private_Clean and Private_Dirty sections.
771+
line = line.rstrip()
772+
if line.startswith(b"Private_") and line.endswith(b'kB'):
773+
parts = line.split()
774+
total += int(parts[1]) * 1024
775+
return total
776+
except ProcessLookupError:
777+
return None

Lib/test/test_regrtest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@
4141

4242
ROOT_DIR = os.path.join(os.path.dirname(__file__), '..', '..')
4343
ROOT_DIR = os.path.abspath(os.path.normpath(ROOT_DIR))
44-
LOG_PREFIX = r'[0-9]+:[0-9]+:[0-9]+ (?:load avg: [0-9]+\.[0-9]{2} )?'
44+
LOG_PREFIX = (
45+
r'[0-9]+:[0-9]+:[0-9]+ '
46+
r'(?:load avg: [0-9]+\.[0-9]{2} )?'
47+
r'(?:mem: [0-9]+\.[0-9] (?:MiB|GiB) )?'
48+
)
4549
RESULT_REGEX = (
4650
'passed',
4751
'failed',
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
On Linux, regrtest now logs the total memory usage of all Python processes.
2+
Read the private memory in ``/proc/pid/smaps``. Patch by Victor Stinner.

0 commit comments

Comments
 (0)