From: Richard Cooper Date: Fri, 14 Aug 2020 11:19:55 +0000 (+0100) Subject: ext: Monkeypatch os.waitpid to extract CPU time from subprocess X-Git-Tag: develop-gem5-snapshot~716 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=fe2817270ea9630c2fbc868bb093581b8a6b8a09;p=gem5.git ext: Monkeypatch os.waitpid to extract CPU time from subprocess Added utility class `TimedWaitPID` which monkey-patches os.waitpid() with a functor that has the same signature, but calls os.wait4() instead. This allows the process's user and system CPU time to be obtained from the OS when using APIs (such as subprocess) which use os.waitpid() internally. The process CPU time is stored within the functor and can be read back later by calling TimedWaitPID.get_time_for_pid(). JIRA: https://gem5.atlassian.net/browse/GEM5-548 Change-Id: I9ebe9ca1241a4f28c90ad31f672f32ac52786664 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/32652 Maintainer: Bobby R. Bruce Tested-by: kokoro Reviewed-by: Hoa Nguyen --- diff --git a/ext/testlib/helper.py b/ext/testlib/helper.py index ff8340986..01ca539dc 100644 --- a/ext/testlib/helper.py +++ b/ext/testlib/helper.py @@ -1,3 +1,15 @@ +# Copyright (c) 2020 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# # Copyright (c) 2017 Mark D. Hill and David A. Wood # All rights reserved. # @@ -29,7 +41,7 @@ ''' Helper classes for writing tests with this test library. ''' -from collections import MutableSet +from collections import MutableSet, namedtuple import difflib import errno @@ -42,6 +54,83 @@ import tempfile import threading import time +class TimedWaitPID(object): + """Utility to monkey-patch os.waitpid() with os.wait4(). + + This allows process usage time to be obtained directly from the OS + when used with APIs, such as `subprocess`, which use os.waitpid to + join child processes. + + The resource usage data from os.wait4() is stored in a functor and + can be obtained using the get_time_for_pid() method. + + To avoid unbounded memory usage, the time record is deleted after + it is read. + + """ + TimeRecord = namedtuple( "_TimeRecord", "user_time system_time" ) + + class Wrapper(object): + def __init__(self): + self._time_for_pid = {} + self._access_lock = threading.Lock() + + def __call__(self, pid, options): + pid, status, resource_usage = os.wait4(pid, options) + with self._access_lock: + self._time_for_pid[pid] = ( + TimedWaitPID.TimeRecord( + resource_usage.ru_utime, + resource_usage.ru_stime + ) + ) + return (pid, status) + + def has_time_for_pid(self, pid): + with self._access_lock: + return pid in self._time_for_pid + + def get_time_for_pid(self, pid): + with self._access_lock: + if pid not in self._time_for_pid: + raise Exception("No resource usage for pid {}".format(pid)) + time_for_pid = self._time_for_pid[pid] + del self._time_for_pid[pid] + return time_for_pid + + _wrapper = None + _wrapper_lock = threading.Lock() + _original_os_waitpid = None + + @staticmethod + def install(): + with TimedWaitPID._wrapper_lock: + if TimedWaitPID._wrapper is None: + TimedWaitPID._wrapper = TimedWaitPID.Wrapper() + if TimedWaitPID._original_os_waitpid is None : + TimedWaitPID._original_os_waitpid = os.waitpid + os.waitpid = TimedWaitPID._wrapper + + @staticmethod + def restore(): + with TimedWaitPID._wrapper_lock: + if TimedWaitPID._original_os_waitpid is not None : + os.waitpid = TimedWaitPID._original_os_waitpid + TimedWaitPID._original_os_waitpid = None + + @staticmethod + def has_time_for_pid(pid): + with TimedWaitPID._wrapper_lock: + return TimedWaitPID._wrapper.has_time_for_pid(pid) + + @staticmethod + def get_time_for_pid(pid): + with TimedWaitPID._wrapper_lock: + return TimedWaitPID._wrapper.get_time_for_pid(pid) + +# Patch os.waitpid() +TimedWaitPID.install() + #TODO Tear out duplicate logic from the sandbox IOManager def log_call(logger, command, *popenargs, **kwargs): '''