pax_global_header00006660000000000000000000000064146127327400014520gustar00rootroot0000000000000052 comment=b76d59f3cca6686caf498fe512ca226c82936bb5 psrecord-1.4/000077500000000000000000000000001461273274000132055ustar00rootroot00000000000000psrecord-1.4/.github/000077500000000000000000000000001461273274000145455ustar00rootroot00000000000000psrecord-1.4/.github/workflows/000077500000000000000000000000001461273274000166025ustar00rootroot00000000000000psrecord-1.4/.github/workflows/main.yml000066400000000000000000000017301461273274000202520ustar00rootroot00000000000000name: CI on: push: pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: envs: | - linux: py39-test-psutil56 - linux: py310-test-psutil57 - linux: py311-test-psutil58-alldeps - linux: py312-test-psutil59 - macos: py39-test-psutil56 - macos: py310-test-psutil57 - macos: py311-test-psutil58-alldeps - macos: py312-test-psutil59 - windows: py39-test-psutil56 - windows: py310-test-psutil57 - windows: py311-test-psutil58-alldeps - windows: py312-test-psutil59 publish: needs: tests uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v1 with: test_extras: 'test' test_command: pytest --pyargs psrecord secrets: pypi_token: ${{ secrets.pypi_token }} psrecord-1.4/.github/workflows/update-changelog.yaml000066400000000000000000000015451461273274000227020ustar00rootroot00000000000000# This workflow takes the GitHub release notes an updates the changelog on the # main branch with the body of the release notes, thereby keeping a log in # the git repo of the changes. name: "Update Changelog" on: release: types: [released] jobs: update: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 with: ref: main - name: Update Changelog uses: stefanzweifel/changelog-updater-action@v1 with: release-notes: ${{ github.event.release.body }} latest-version: ${{ github.event.release.name }} path-to-changelog: CHANGES.md - name: Commit updated CHANGELOG uses: stefanzweifel/git-auto-commit-action@v4 with: branch: main commit_message: Update CHANGELOG file_pattern: CHANGES.md psrecord-1.4/.gitignore000066400000000000000000000060341461273274000152000ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ psrecord/_version.py psrecord-1.4/.pre-commit-config.yaml000066400000000000000000000003471461273274000174720ustar00rootroot00000000000000ci: autofix_prs: false autoupdate_schedule: 'monthly' repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.3.4" hooks: - id: ruff args: ["--fix", "--show-fixes"] - id: ruff-format psrecord-1.4/CHANGES.md000066400000000000000000000035541461273274000146060ustar00rootroot00000000000000## v1.3 - 2024-04-26 ### What's Changed * Updated package infrastructure by @astrofrog in https://github.com/astrofrog/psrecord/pull/73 * setup.py: Add matplotlib by @JohnAZoidberg in https://github.com/astrofrog/psrecord/pull/53 * If neither --log nor --plot is passed, log to stdout. Closes #50 by @CristianCantoro in https://github.com/astrofrog/psrecord/pull/68 * Add Monitor as exposed function by @Vincent-CIRCL in https://github.com/astrofrog/psrecord/pull/54 * Added ability to include I/O stats in output by @astrofrog in https://github.com/astrofrog/psrecord/pull/74 * Add support for csv and modernise code by @astrofrog in https://github.com/astrofrog/psrecord/pull/75 ### New Contributors * @JohnAZoidberg made their first contribution in https://github.com/astrofrog/psrecord/pull/53 * @CristianCantoro made their first contribution in https://github.com/astrofrog/psrecord/pull/68 * @Vincent-CIRCL made their first contribution in https://github.com/astrofrog/psrecord/pull/54 **Full Changelog**: https://github.com/astrofrog/psrecord/compare/v1.2...v1.3 ## 1.2 (2020-05-28) - Fixed compatibility with latest versions of psutil and fixed issue with determining CPU usage from child processes. [#56] - Fixed plotting to work on machines where Agg is not the default Matplotlib backend. [#44] ## 1.1 (2018-06-16) - Fixed installation via pip if psutil was not installed. [#37] ## 1.0 (2016-12-05) - Fix compatibility with recent versions of psutil. [#18, #19] - psutil is now properly defined as a dependency in install_requires. [#16] ## 0.2 (2014-10-22) - Recognize zombie processes. [#7] - Improve general reliability. - Allow interval to be a floating-point value. - Fix compatibility with psutil >= 2.0. [#10] - Ensure that log file gets closed and plot gets drawn if process is interrupted. [#13] ## 0.1 (2013-12-17) - Initial release psrecord-1.4/LICENSE000066400000000000000000000024231461273274000142130ustar00rootroot00000000000000Copyright (c) 2013, Thomas P. Robitaille All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. psrecord-1.4/MANIFEST.in000066400000000000000000000001151461273274000147400ustar00rootroot00000000000000include README.rst include LICENSE include screenshot.png include CHANGES.md psrecord-1.4/README.rst000066400000000000000000000052341461273274000147000ustar00rootroot00000000000000|Build Status| |Coverage Status| About ===== ``psrecord`` is a small utility that uses the `psutil `__ library to record the CPU and memory activity of a process. The package is still under development and is therefore experimental. The code is released under a Simplified BSD License, which is given in the ``LICENSE`` file. Requirements ============ - Python 2.7 or 3.3 and higher - `psutil `__ 1.0 or later - `matplotlib `__ (optional, used for plotting) Installation ============ To install, simply do:: pip install psrecord To install with the optional plotting dependencies, do:: pip install psrecord[plot] Usage ===== Basics ------ To record the CPU and memory activity of an existing process to a file (use sudo for a root process): :: psrecord 1330 --log activity.txt where ``1330`` is an example of a process ID which you can find with ``ps`` or ``top``. You can also use ``psrecord`` to start up a process by specifying the command in quotes: :: psrecord "hyperion model.rtin model.rtout" --log activity.txt Plotting -------- To make a plot of the activity: :: psrecord 1330 --plot plot.png This will produce a plot such as: .. image:: https://github.com/astrofrog/psrecord/raw/master/screenshot.png You can combine these options to write the activity to a file and make a plot at the same time: :: psrecord 1330 --log activity.txt --plot plot.png Duration and intervals ---------------------- By default, the monitoring will continue until the process is stopped. You can also specify a maximum duration in seconds: :: psrecord 1330 --log activity.txt --duration 10 Finally, the process is polled as often as possible by default, but it is possible to set the time between samples in seconds: :: psrecord 1330 --log activity.txt --interval 2 Subprocesses ------------ To include sub-processes in the CPU and memory stats, use: :: psrecord 1330 --log activity.txt --include-children Running tests ============= To run tests, you will need `pytest `_. You can install it with:: pip install pytest You can then run the tests with:: pytest psrecord Reporting issues ================ Please report any issues in the `issue tracker `__. .. |Build Status| image:: https://travis-ci.org/astrofrog/psrecord.svg?branch=master :target: https://travis-ci.org/astrofrog/psrecord .. |Coverage Status| image:: https://codecov.io/gh/astrofrog/psrecord/branch/master/graph/badge.svg :target: https://codecov.io/gh/astrofrog/psrecord psrecord-1.4/psrecord/000077500000000000000000000000001461273274000150265ustar00rootroot00000000000000psrecord-1.4/psrecord/__init__.py000066400000000000000000000027311461273274000171420ustar00rootroot00000000000000# Copyright (c) 2013, Thomas P. Robitaille # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from .main import main # noqa from .main import monitor # noqa from ._version import __version__ # noqa __all__ = ["main", "monitor", "__version__"] psrecord-1.4/psrecord/__main__.py000066400000000000000000000025701461273274000171240ustar00rootroot00000000000000# Copyright (c) 2013, Thomas P. Robitaille # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from .main import main __version__ = "1.3.dev0" main() psrecord-1.4/psrecord/main.py000066400000000000000000000247131461273274000163330ustar00rootroot00000000000000# Copyright (c) 2013, Thomas P. Robitaille # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import argparse import sys import time children = [] def get_percent(process): return process.cpu_percent() def get_memory(process): return process.memory_info() def all_children(pr): global children try: children_of_pr = pr.children(recursive=True) except Exception: # pragma: no cover return children for child in children_of_pr: if child not in children: children.append(child) return children def main(): parser = argparse.ArgumentParser(description="Record CPU and memory usage for a process") parser.add_argument("process_id_or_command", type=str, help="the process id or command.") parser.add_argument( "--log", type=str, help="output the statistics to a file. If neither " "--log nor --plot are specified, print to print " "to standard output.", ) parser.add_argument( "--log-format", type=str, default="plain", help='the format of the log file, can be one of "plain" or "csv"', ) parser.add_argument("--plot", type=str, help="output the statistics to a plot.") parser.add_argument( "--duration", type=float, help="how long to record for (in seconds). If not " "specified, the recording is continuous until " "the job exits.", ) parser.add_argument( "--interval", type=float, help="how long to wait between each sample (in " "seconds). By default the process is sampled " "as often as possible.", ) parser.add_argument( "--include-children", help="include sub-processes in statistics (results " "in a slower maximum sampling rate).", action="store_true", ) parser.add_argument("--include-io", help="include include_io I/O stats", action="store_true") args = parser.parse_args() # Attach to process try: pid = int(args.process_id_or_command) print(f"Attaching to process {pid}") sprocess = None except Exception: import subprocess command = args.process_id_or_command print(f"Starting up command '{command}' and attaching to process") sprocess = subprocess.Popen(command, shell=True) pid = sprocess.pid monitor( pid, logfile=args.log, plot=args.plot, duration=args.duration, interval=args.interval, include_children=args.include_children, include_io=args.include_io, log_format=args.log_format, ) if sprocess is not None: sprocess.kill() def monitor( pid, logfile=None, plot=None, duration=None, interval=None, include_children=False, include_io=False, log_format="plain", ): # We import psutil here so that the module can be imported even if psutil # is not present (for example if accessing the version) import psutil pr = psutil.Process(pid) # Record start time start_time = time.time() f = None if logfile is None and plot is None: f = sys.stdout logfile = "" elif logfile is not None: f = open(logfile, "w") if logfile: if log_format == "plain": f.write( "# {:12s} {:12s} {:12s} {:12s}".format( "Elapsed time".center(12), "CPU (%)".center(12), "Real (MB)".center(12), "Virtual (MB)".center(12), ), ) if include_io: f.write( " {:12s} {:12s} {:12s} {:12s}".format( "Read count".center(12), "Write count".center(12), "Read bytes".center(12), "Write bytes".center(12), ) ) elif log_format == "csv": f.write("elapsed_time,nproc,cpu,mem_real,mem_virtual") if include_io: f.write(",read_count,write_count,read_bytes,write_bytes") else: raise ValueError( f"Unknown log format: '{log_format}', should be either 'plain' or 'csv'" ) f.write("\n") log = {} log["times"] = [] log["cpu"] = [] log["mem_real"] = [] log["mem_virtual"] = [] if include_io: log["read_count"] = [] log["write_count"] = [] log["read_bytes"] = [] log["write_bytes"] = [] try: # Start main event loop while True: # Find current time current_time = time.time() elapsed_time = current_time - start_time try: pr_status = pr.status() except TypeError: # psutil < 2.0 pr_status = pr.status except psutil.NoSuchProcess: # pragma: no cover break # Check if process status indicates we should exit if pr_status in [psutil.STATUS_ZOMBIE, psutil.STATUS_DEAD]: print(f"Process finished ({elapsed_time:.2f} seconds)") break # Check if we have reached the maximum time if duration is not None and elapsed_time > duration: break # Get current CPU and memory try: current_cpu = get_percent(pr) current_mem = get_memory(pr) except Exception: break current_mem_real = current_mem.rss / 1024.0**2 current_mem_virtual = current_mem.vms / 1024.0**2 if include_io: counters = pr.io_counters() read_count = counters.read_count write_count = counters.write_count read_bytes = counters.read_bytes write_bytes = counters.write_bytes n_proc = 1 # Get information for children if include_children: for child in all_children(pr): try: current_cpu += get_percent(child) current_mem = get_memory(child) current_mem_real += current_mem.rss / 1024.0**2 current_mem_virtual += current_mem.vms / 1024.0**2 if include_io: counters = child.io_counters() read_count += counters.read_count write_count += counters.write_count read_bytes += counters.read_bytes write_bytes += counters.write_bytes n_proc += 1 except Exception: continue if logfile: if log_format == "plain": f.write( f"{elapsed_time:12.3f} {current_cpu:12.3f}" f" {current_mem_real:12.3f} {current_mem_virtual:12.3f}" ) if include_io: f.write( f" {read_count:12d} {write_count:12d}" f" {read_bytes:12d} {write_bytes:12d}" ) elif log_format == "csv": f.write( f"{elapsed_time},{n_proc},{current_cpu},{current_mem_real},{current_mem_virtual}" ) if include_io: f.write(f",{read_count},{write_count},{read_bytes},{write_bytes}") f.write("\n") f.flush() if interval is not None: time.sleep(interval) # If plotting, record the values if plot: log["times"].append(elapsed_time) log["cpu"].append(current_cpu) log["mem_real"].append(current_mem_real) log["mem_virtual"].append(current_mem_virtual) if include_io: log["read_count"].append(read_count) log["write_count"].append(write_count) log["read_bytes"].append(read_bytes) log["write_bytes"].append(write_bytes) except KeyboardInterrupt: # pragma: no cover pass # close the logfile, if it's not stdout if logfile and logfile != "": f.close() if plot: # Use non-interactive backend, to enable operation on headless machines # We import matplotlib here so that the module can be imported even if # matplotlib is not present and the plotting option is unset import matplotlib.pyplot as plt with plt.rc_context({"backend": "Agg"}): fig = plt.figure() ax = fig.add_subplot(1, 1, 1) ax.plot(log["times"], log["cpu"], "-", lw=1, color="r") ax.set_ylabel("CPU (%)", color="r") ax.set_xlabel("time (s)") ax.set_ylim(0.0, max(log["cpu"]) * 1.2) ax2 = ax.twinx() ax2.plot(log["times"], log["mem_real"], "-", lw=1, color="b") ax2.set_ylim(0.0, max(log["mem_real"]) * 1.2) ax2.set_ylabel("Real Memory (MB)", color="b") ax.grid() fig.savefig(plot) psrecord-1.4/psrecord/tests/000077500000000000000000000000001461273274000161705ustar00rootroot00000000000000psrecord-1.4/psrecord/tests/__init__.py000066400000000000000000000000001461273274000202670ustar00rootroot00000000000000psrecord-1.4/psrecord/tests/test_main.py000066400000000000000000000045321461273274000205310ustar00rootroot00000000000000import csv import os import subprocess import sys import psutil import pytest from ..main import all_children, main, monitor TEST_CODE = """ import subprocess p = subprocess.Popen('sleep 5'.split()) p.wait() """ def test_all_children(tmpdir): filename = tmpdir.join("test.py").strpath with open(filename, "w") as f: f.write(TEST_CODE) p = subprocess.Popen(f"{sys.executable} {filename}".split()) import time time.sleep(1) pr = psutil.Process(p.pid) children = all_children(pr) assert len(children) > 0 p.kill() class TestMonitor: def setup_method(self, method): self.p = subprocess.Popen("sleep 10", shell=True) def teardown_method(self, method): self.p.kill() def test_simple(self): monitor(self.p.pid, duration=3) def test_simple_with_interval(self): monitor(self.p.pid, duration=3, interval=0.1) def test_with_children(self, tmpdir): # Test with current process since it has a subprocess (self.p) monitor(os.getpid(), duration=3, include_children=True) def test_logfile(self, tmpdir): filename = tmpdir.join("test_logfile").strpath monitor(self.p.pid, logfile=filename, duration=3) assert os.path.exists(filename) assert len(open(filename).readlines()) > 0 def test_logfile_csv(self, tmpdir): filename = tmpdir.join("test_logfile.csv").strpath monitor(self.p.pid, logfile=filename, duration=3, log_format="csv") assert os.path.exists(filename) assert len(open(filename).readlines()) > 0 with open(filename) as csvfile: data = csv.reader(csvfile) assert next(data) == ["elapsed_time", "nproc", "cpu", "mem_real", "mem_virtual"] def test_plot(self, tmpdir): pytest.importorskip("matplotlib") filename = tmpdir.join("test_plot.png").strpath monitor(self.p.pid, plot=filename, duration=3) assert os.path.exists(filename) def test_main(self): sys.argv = ["psrecord", "--duration=3", "'sleep 10'"] main() def test_main_by_id(self): sys.argv = ["psrecord", "--duration=3", str(os.getpid())] main() @pytest.mark.skipif(sys.platform == "darwin", reason="Functionality not supported on MacOS") def test_io(self, tmpdir): monitor(os.getpid(), duration=3, include_io=True) psrecord-1.4/pyproject.toml000066400000000000000000000021031461273274000161150ustar00rootroot00000000000000[build-system] requires = ["setuptools>=64", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] version_file = "psrecord/_version.py" [project] name = "psrecord" dynamic = [ "version" ] authors = [ { name = "Thomas Robitaille", email = "thomas.robitaille@gmail.com" }, ] classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: Python", "License :: OSI Approved :: BSD License", ] description = "Python package to record activity from processes" readme = "README.rst" dependencies = [ "psutil>=5.6", ] [project.optional-dependencies] test = [ "pytest>=7.0", ] plot = [ "matplotlib", ] [project.scripts] psrecord = "psrecord.main:main" [project.license] text = "Simplified BSD License" [project.urls] Homepage = "https://github.com/astrofrog/psrecord" [tool.setuptools] zip-safe = true provides = [ "psrecord", ] include-package-data = false [tool.setuptools.packages.find] namespaces = false [tool.ruff] lint.select = ["F", "E", "W", "I", "UP"] line-length = 100 [tool.black] line-length = 100 psrecord-1.4/screenshot.png000066400000000000000000001447731461273274000161100ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxy|Tw@*B!k]hE("*֍XP*bAj*?iEEE#J`BeJQ `r' If&̽g<wn%3sq.Hkh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@kh@Dڵk5e:CtQGiرڸqc}7lؠÇM6JOOU\\\U>}={*'''/Z8Exmzwt***RNN٣իW㏗$m߾]T핝ݻwWݵflٲkΛ7O'Nѣug7Oi c;VTTT$IҥK}t颒8pjߤ% 99YڱcGDRBM$Iiii}'--M~}UWاK.^ЀjĈrJ~U~k^ o.]TQQZaD]viС&.{ֿ/&}t9hӦMzջwZqԩ֮][s׬YT} GQSeee}k***Raa|I'/ thɒ%^^dc7ӆ t%h@$UTThرzw /褓Nw .@kdƍ:t:t蠹sj@Ν֭[kȑAӧ222"Ib1؋lF>+I7p^|Es9*..֓O>YK.Dt-gљg뮻Nw}ݧk„ Ujƌ"{~8u0 |&Xckh@kh@0eeey] {n4eK@c/؍|W4 @ u |E6"kh@S^^% ؋lF>+ L .A>"{~8u0 |eddx]Z154 \C54 @&Lu |E6"w؋lE6v#`YUZ154 \CiժU^ ^dc/h@0͞=؋lE6v# Ey] {nԪU+K@c/؍|W4 \C54 @Nu |E6"Sݽ.A^dc7_%8x]eff*??_^p|p Ѐa*,,A>"{~Eiڴi^ ^dc/h@0x] {nrv#{n4k,K@c/؍|W4 @JKK.A^dc7_%8x]eff*??_^p|p Ѐa*..A>"{~E/A>"{~Ei^ ^dc/h@0҅^dc/h@kh@0z] {nTPPu |E6"*q"`(33SL<kЀp ЀaA>"{~Eiʔ)^ ^dc/h@0 6؋lE6v# Ѐp >X#>_ZPڴIڸQO?5vJV؁_Ȃ+]rוDw؋llGNIzq)1Q*-mn޽u  "qçƍu |E6t6yy6o_isgڴ3TVJG`[b=1Bz"Z8vW4 7 I7$}4cF_ibi_7?~NykҞ=jװ?ݻWgֻᆱ5kh׮]_׵^ziÆ uז-[tG*;;[SL6D ĝ;[n-3'܋G#]tһR۶?hFܶsN͘1CGu 7xC Z=:͛7O'Nѣu77TvvJKK5mڴhAYjN=T@^dcf8ҕWJkH&HW]%{l n>$iٜDY|E6jn6KH/d~{n G '>z9geXcGwիRviXv9JNNVΝ%IN"qTYY={mC9+VPII&MTkɓ`-[L_|qdw "# -A>"{5%R,nymh7ca}GC=4[HGZYRZZm۪T۷׸q4k,nݺjuI Ts322Ӏ~n@Zju |E6jl6*]zs{۶CE0Hi34Wڵ~+##Czo$IRQQԱcZtЀ r|>=KT[7s9 `t㥕+Ml3g1cԳgOzzg5vXIfHrrr_#%%EeeeQ5:"|ج:tU'K|n*-_n) _D-_j[ZZ߾}Jeh@9>o>Nu "T>!4ѦM' ;ME8ҦM{Ǟ=fK/ɽFyPhiϖ>L91dH㖊^pj=ԛ:j[.]TQQZD]vJ-%Xh,(IÆI}>ҽ{~R'ͫ˥$N#0'T>^E'ԏp1t>vtlLi83㏫;VZHZЌ|~G!/6a4utu7߄Uv!$YTs޽[ԩSնJ֮]#FTmTYYD7ЀI/7-^,\ȼ{6j7k7oK<#MnVӸk6m.?P)94""w_5[MJ:ʬ_S|>DZޝB%N?]ڼٜ}9n*+,gOs҉'ꛪTfw3F汃kUQa.Ii0# GO_.kmojs-}4thj89++3'N P Lʕ2~X;jl1FImVOEIIIj~-ul_ڴ1'Q18C ջwo@b=1XQƙ7 #c7fjv̶{'믿37ۻ\ΛzMo۷όzaF4# C?qxUрX@Af$E s%%U9ܓ۲2sbs[=Lc~4f={uCtf~#IL4d3dt97.E%р4s@~r9"l|K@~')Ɍv4GZ}m[wA8`V_ 4gXtΧ_Cyrv#j4d]-[J7snF7 h>DBA|d7V؋lE6v# " Hii% ؋lF>+H/6rYw+?_# h@94bK@c/؍|W4 7 _~% ؋lF>+Dӧ{] {nAa ^dc7_р r|| 2h@Y4!{wܡÇCJLLwe)11ΣO>~\GiiiٳrrrRKrss.A^dc7_р`Ν1c>S 0@P)))z'k=:͛7OW^y맜 2Dٚ={vT_g|ހx] {nJp16ByyvڥΝ;+??_c=.s=.+++ӑGSN9EK.~饗*//OڵkWs ؜xvҖ-^W1fF@~Ν;KBd㨲2hb hҤIO֭$ 4 %&&j_kuk׮ UVV_?_o񆒒$IEEEJJJRǎk}~rrӵc/ʏ.H#͜9S3gѣ5f=ܣzK>l~eeeJNNk̭$++؋lE6v# H\JLL˫ۧ4sG@Lu |E6"$RSSաCTmҥ***T\\\krk׮ ~_ʪ2djkɓ^PP:q5kVm۶mSVV km衇4uZJKKUVj>.\ &ԩmرjaÆuQZZw֡ZsΕ$K5j-[#FToSOO+DIu ^dc7_р r|>2n8K@c/؍|W4 7 h>D BAdYj% ؋lF>+DI={% ؋lF>+D/Zh% ؋lF>+DVZy] {nA@р rh@ "ԩS.A^dc7_р r|>ҽ{wK@c/؍|W 3SPPL+##rOiVJ","# h>D BA(,,A>"{~EȴiӼ.A^dc7_р r|ހx] {nAa9D^dc7_р r|ހh@Yfy] {nA)V#{nJp1Ƙeff*??_^xG%m& * IDATķ?_# @ȡ@4 @F؋lE6v# "ϧ _u |E6"ӧ{] {nAa ^dc7_р r|x rh@9>@р r|ހz] {nA)((A>"{~8>=[AT~~~lN1Q:s_Z:wAЀx㏥o^j^: MѾ9Uif)?_ɑf̐~Ko$tD,K@c/؍|THK/I_~)-Y"z4n4|4btEfsϙ}^z|΀ѯD)Sx] {n䃦zƍbliå WW# 6Æ A>"{M՜K\ A|/ʤ;4 k?HwY}i;HI[K&qEiu |E6"wO4 I4lc%f$kDuЀ r|>pBK@c/؍|w-ujstرK7Jyy?!m$G{р r|x<^ ^dc/9+4ͥXvI'˯:t~s[h@9>_}%u^#0uo׮޽%р h@8Rb3[߶hFk#?wA$0a% ؋lF>hk:v4^̶ϯxLn] r|xpGZ^dc7As~{w2Yc )((Pff󕑑u9"K_-uu5k,D/@q " ʭDUVSO 4|E6"c`Ӏ{Z UTDDOٳgbc/؍|\))ȑgK-[sFȢE.A^dc7As̛'-X =҅J]$2 UV^ ^dc/94[HӦI#vԣtMxW "ϧ @M:UZNcKŋN^}h@y4 Gn)/O:tOwuDF4SNA>"{g=Qۥn.ZȨـ޽% ؋lF>h~Zz)iå1c\餓+q|z |eu'HZn*&X9_;,i C7+` *%Zp# 8˗K憄;wJ>h,]Nm4 ktnni{ӳ@ȈP{ 4|E6"c+hWw|ڌL6؋lE6v# "#.A>"{MUZ熋 !ڍ|E6"#;ۥowg!S 2sJw!ut.edHG-ooNվFڼYZVzuij㤇~}4 +#]pn^^^ҰaҒ%RV; ,DF̚5؋lE6v#4GRty /HvI+WJ>*fۮ]M AdARƬ,4؋lE6v#DJj ǧg1@WFF4R۶υR^1}f .B4 s@.A^dc7_fR\l|y] h@.rK@c/؍|Wрc1>|ڴit?rssէOgϞiR}:yGڸQVӘ1KW^)\٤/(v7 رCZVs+o ]UY9!^ ^dc/Ğ;wjƌO5`IRBBBo߮N;M}^xZl:,8p־ӕW^~)''GC QvvfϞݬZҤVm/ ~4h'9j9 IH~ 87{]Y;|Kirj8κu' ;_ۿW_98̟?~'NtZn|U^u'!!yG:9S/CqF8C:NR$';ι:Β%S^8838ΑG:ҍ# W_mƑ>L*+ ,>@0üe'ϒ%K4j(u֭j~3Skc^bJJJ4iҤZ?ydݻW˖- < ͝+6噵ZZF3:Zo@G頡(IfݛozRR̿h~%B 74!/sN 4sֺg?oFF~KkHa/RzzoVɊ67˫=#C5KڶM{l NItR.]jHQQ{%''+==];v{_֭=Hz$?_:G?>lϬ%֣,RRiB྆bTVV% ؋lF>TVV&IJII\3)99ޯR_0{kjőgh{? 'H14N֭f->3cK^`))jc/؍|)--M:۷>iii*o/֭܎֭Zq}'ڵx\Cf1#:%XÆ A>"{{ .… kmo5^.ŪHjٲeվ*..uVyyJJJԵkװf'J i@^}ay?[LB>3n87ֶeff6kqԩ֮][5kT?D(IZvFQSeee}կI307)+|?޴u#mu+. .@?}m˗/ƍuVm:t:t蠹ssu9rdX3̍W4?`yiaddץ{ڷ̑F4sBKtԯ$˦Y6".yuh؋lE6v#ؔ]vUPtRm۶Mm[n3<3"{'JNMZ\O"7| M}LDON6`mJO=%~bo@$Svqb`8<7V[oI^ӥSN1;ؽ oi7TVVq oU)`\DZc/DI6 k.*:3ϭ^^=v7 G)yTϝ"}tRѫ FpGZgjs)'\DZc/D Wb ?_QQ= v_5wt]wK~ssƣ6qo*Ykךqի㎓~8wB]BМ%Dǎn%>:7 )S.z֭(, G@ƌ>HZĬu=' qYB_?sWYt'm{;աC%&&ja >\mڴQzzƏzU>}={*'' ֪ =2R4 WkLi.O_ v{~IJJ;<͔i_.3SJMmٹsf̘:J ozݾ}N;4o^{vޭ_~֬Y-[V;o:WZSYZ! ǎn%qzuK?o^Νf[N~} չsgIrɒ%5jTU!I?ԳgϪFCVXM4OX_|;wjРAuOYY:)))UŴF@X 6b e2et!ҫя0iiiyn߾}IKKSyyy_g߾}UŴ;8k.ȦK<ñc/DMu'ydK.]N=7 kTSƏ:v kҷѮ]VZtm&IV۶mu-gљg뮻Nw}ݧLMMՌ34yd3FÆ ʕ+`͜93oB(վH1 Ui:嗛_.}]}ܫ{nqi<شwMl@̙[JSBBƏm۪[nw馛QFiΜ9U?&N-[jΜ9Ztwx@M:#NEHqM7IzI='u5Ѐ+%E;V&͛7_߾}+W\+5Y*X hB2ЀЀxc^dc78l@) ͇U%?_#VٝЧMu "d>n/r*;"O$\ dz釉pA =''D| ǎn%q؀<<\^jNƍʼ__s,1?zo@9'2 RW[ exh@R\b=}X?4" J}H7hZTwg5G@XHqyn^WPs@l6o6{J*eg|}@⣵g͚u "d>4 ر؍|$߀^5^OG@JKK.A'Tر؍|H+s[{wI4gi{F2oԻ8鷿*44;2F@<ñc/ͭ#'on;0~}i\#H>^=7 yyĉ_HPNo2q <7:4IoYݫO9ƌ ]kܱcb}uu-F@?,@|s^3w8?hwoThf[nY{iokw 9xyqq% Ѐxc^dc7Lܹwv);ͼv58˽.A'3GCر؍|$# ? f[o@SJX}@exOu "d>xc^dc79믗~Z5ˬ%SK.1s޽zd[m:lRww5^OG@222.Ȧ3;"O$\\"m*zt-f ),_W Hf;̣&7#ÃHq{nr뭦y93QY){tf'7߀luhH`#I> 1 m"IGV 9 C`#NF@rss.AG?c^dc7imcoY憅!pGUPPu "d>{ȧ_ӯԿ`>nG{L)M&ky2'x]`yc^dc7iѹI0,J^Өo@/.@яIGղh@\,ËGԦMx!Cw1ǎ.H:D24}O9yɝh@8[g_]:do {ȧh@f700:o@MʤCիgj|Y4uū'sqҀ 6D| ǎnNei@IInY|74bU~#g_L2яbmHw˳6mZ~Sdb@вi>^ǂSOI"{ҰaR ;̅k/(U?׵tUfc14,.{iJ'oH]zHVi>Z2^9|wjD>Z;<<ڶ5/oD;2ЀЀxc^dc7 C˖#`k>#h&2 .B$tp؋lF>a4 y]Abpާ~D| ǎnq4U``s@ O`6uS&XEexh@GF@b \Kd;4 1MSsnG0a% 3;"O VLA[Qȧ`qGZ}'t4ñc^dc7 CtnDSh@4CscԸq.ȦK<ñc/) U,Ë ݱwy^d8K##pBRƂ5~|k &pІۀ Gd")/O*+F@Cn5 \ UVy]O<6 opXlF>a`z>iЀipd^ B$tp؋lF>a`zzh-4 h`s@?ǨEy]OXc^dc7 `$怠ipUV^ BC{' i@|hcIҺuҷ=MKH/wjA;i;^?O7{>P:xo;c<ҽz M]ᅍaHXnI:PZl{iv駥]YܫMԩS.Ȧ3;"OX+,o%]}tQ /.HFAӄslݻ{]`yc^dc7 `R:pv$~|A4,^{% 0{' =͛͟y4&nA4t&$0viʊVq؀}xq'M2+bСc˰*XhDߎ рTVJ-Z4I*WfLrJ{J>kn]a󟥝; Haa% skر؍|Bg4AL! VnMZ^z=i@L:`M6D|N٘əQc/@$EEK{xW BoWc 2x[ܻ׻:ıc3@(yyR^GHҚ5fΝҀϻW BoW8` /\2>Y K㵴Ի:ıc3$^ސH:˭j*4%=^=4 1Zx1.6 q8 TZʬu!Cuܫ5ugs@hKGر ?aW_W Bklr?̚5D|xc^dc7 $lZUy^=4 1TREZ|mzc]{'y4t@RzQi0Ah4vNNK@!a3;"O wK. ,͛gA ';p֘Uj6 >[ 1.F@ * [[oI;K}I+o&}{p[!f;$!ח`!/F@ *h@_JJM?1H;ǜƹ%X}|p{]OԷ CIO>z);"O4 Mҡt'c>/;νh@Z#F@|0/2xk@ȳJ˖^ ǎn HHSOIfIwW?WRb.MZF?$.B8 o|62}tK@!UjίVsnK8vE6v#hѸ8mtic7K+WJk֘~ :U:|jAh˧ HFF% Hkݺ4 ;"OR&}K*m"y4f~tIf駻_ B,A5 9h{".sJи+˥.3MFK^(ҥ& jY݃' /lfet5ޞfƍ w^W$ /h@H 27ի͝jH4=k@8vU'X";!p# **ozjAh")((27 rr!:;tz>x0*ñc:8+-±# !m,T?>ljo</^ B|mGg^164 c.1QjƜTz4±cZTkp@`4~EЀ ^Us{Ie +59 hߛ4 4 Ao^W0%XRwBOJAtc%(F@71vHDIab HP]u cBk%X5oIVsd.`1p8`>2XeK&vpCQs[M MBoJ-u "d>GgK ǎjeCbԷoIAhͽO.2e% ~D9aĢK8vU+Xc'` ȧJ6A{8)}ava_Y\k5+DOJ@ yUjHbAK":(;K8k>3ꮮ骮[   <',X-XE S۴1 غa\$_V7! =?exϪ,Az+[y]HLd`:ar/`@r@b ! {v,MH28 ߪ`)A@2  ֮uޮ\s:dI?DH!D`T1`M<9C}qd-X( mtċ$B,Xi <ۇA;Bnтe 6JV lT~,XB@|A;5b)T\lʁI&E}x' WadZd$YXyx VFaa߂ LۮXv:w=zK/Ŗ-[~LqDQ )΄iΡ2C( N$ePE ֠AۥB@Ă%H'ڔ5 :sQGeZ7xׯѣѭ[7L>555{tR,XqG B@D',]"v_! с-XC@R` mmbQkԨQя~wߍz,^}8S?OC;8@,X{UՃgҹ3бcΔ᭪.pmL`+0 [**=5k0Tp4*ex}A;Hd( 0 ԠZ1nܸv't! { ;N@vvU2eʔAJ+wD(Lm 5tX]Eyx $d2qDTVVcƌ… ?۰a6oތ#<2:(,^8 Ă%G*XV9bz> \ǎl+cw< mvQ=Љa2l&&Z[S`p/,--Ź瞋O?={IJepbԨQpazM_~ ߯_?l۶ 9"D` U|#i9B@b>Ȃ+H(Oq=;/5Z[鯠@OsN $tky4cű~ܸq8s1|pzxQJKK P__D,X{Q?ρA#_r@멿=X,X 9) ~vܞP^k/ۨ"[o0 ms {U"xL.H /Uxǁ/1`ǂW҅rw׀_UT `P7#_d dܹ;wiNP[[nb+j#@tįK' 9h1cFԇ pkgmm,XX2 SX*W~ HϞZ6-/ύ*X55y.i \'_,Xfe"W@ݻdIW5[`w!Ph73\# YK JԩS> \ ڂQ&z'BDK8;.\h0.\qƬY mCQOJ{o8@90N80&L07K/m?0~@l޼'|2 B4޷ƽFccx4 #~;(/j~q$:k%QQ~aPøfz}UqLf>?mSOFYa쩧n3.] Dia  3sLo67-[}:;ҫ j%<a]hQԇ pkDRWG˰[`ȓ}>_<^A?Lm%K@461 Iiyx-Dc ! qgc˖-5kZ<8s'hߎg \'X۷AB2}ϛ( m( D '*,-,۫`ows/I2-273\ +G` Q[⦀U :{7U .(6ow# ۷r-9 a7u&ɆՂ%D%łY|j-3HM?ӳܗߪb' ( 2a"< N,6 IDAT|8%( JdDe* NIq' b~,XwS絵ɝA29 B@b! { ]^̈́c9 v5kDmi1=wƂ+0SicGl& ~}$yx ~IBU˪뮋.pm[ƌfJ~y_1`;|JJ؅޷1u*p+D' &fOZRQ@D ! {ptIB >ϋ ~pm[6n9 57Ƃy+,@U|󡣽m(Ҳ81TT$Z$t N,NZD] V!D` /G-X,F*dࣗA`'u@(9fbG0>̟R^Nmͥx᭯tr-' B@3~9id1R?)ZZZp0āpȳ>2YZ*A[&D7-.TIv @cGzImmz"cn =a{P,k%, g?~;ر8,`ppmT͛m + Hiļ]\+" 6L /?+`9:H.w9 D@ʒߡ?wjPr@B@[+r@ΝV{X:(\'Yb8 VjnN@ UaU2w-XB@2* t*h-,Xn HiinT[AW@TBd;XDr@b! {˜1<S;92o޼pwl~(GIp`M 2m"Ƞdw@f)NeRW+ ݉b:଀דs'D@D=Wp;) |c1 VH&!H o. Vu5懮-X+ =\]@n[0hF9 [$)<$p"AN9 y@Ɯc᧪ 9$p$Pҷ[˂7?tX|Cr@9ixdίb( ;#[!D`*X:Z[,UA,X!YACjS@i}=m HL,X!`-ʮ]LP@>>hk3U@8f$2CvuW-cL81+%O& oq?q!e¥CHMX2q`) &{UB0/3t"! CһV^mRH]oClK̄" wf/ [2, )pme4HSpz.T jJRLuu>MH6DH")&& nI+O> v„ O>NDX)n_eHw$$ho/d@ZRds-$П;z6lܨfҡ!D`=8s0鰖* j0SIHxHUI _ VkBDbgs`o`eEIJ{wUJr@k?L䀴<6 Ȇ …i{Q@ B@" |%g+U$ =QV@QE; V9 }`emRQC ^ejʧ$4LDÕX lT@^~=֍ދU"UNn! 3g wbJ $t݂֦F~xN<ׂ4ND"wĂqMS1>}-Xx =ĉ=w,|G G@VFPE*E}- <rM=;wr@O?`OeʂkWw @AJEE6|m$aw V6~s@**^jݎp9 ^ Hk+zE2~GKjLj~a4@j_DcQ@mG܏N8قܢT( )} $Hn`1t`"L@2|;b,vPm䀡08(Sjo:ikS혫ex/8aN* Q oMZ;wZ"*kd".. =;$<$K@i[I8U8+sz-N Ν< v뷱Bnp_NQ T}IX۷i餀F@՝#"ȥ3M@D ȺuU -p%M6ĪD4o+sغjzoU@t|~-[uީVG(h wBST@!Y0% ~K ǽn-Yy) D`6hj2wTfgQ 0ãٜ;on0`@ VJpm= b`q{o⇮ wѕ*`$kak\”3v9 :( fUUW[i};}XqRYZZTҴ[Hz$# 4q_OCMF|?3B@"=ڜ 's0׹bs4iR$%OI`UTe Wz{}G,X VG ,]&|29:~D@`%+qޓ%995qS@؂Vf;* Qd~(YXCV) :r̂: oxHՂJ,~,Xz祀xx፻I,X <ňKƭ  >&1,/O ֭t} NsA׿&wޡJz `'g s: $ D`6s~`tFɜr@Hz΂L5S,쨂%,nJ@ )+S l{P@w V@IMré~lܨQ@XxY'(-XL`ˤjz{_~г1>êEL`1=B@,9'`pm(ق+ Xt!DXڵ) ` ,l ybMڵ΁_~ǹ%k7~ [Cd)$\@CC6"W^i^WX@$t~`'pʔ)O,X)}Iw=ifD;bRȐ2gD=k/IsmY-" ɒmۀ=Z](IUkUR\Lnexu$wߩYZ/%_W7D@etE*) J@lszݟXRk#unHg,'< ={k<;z;+C ȃN WȐ!t_M.u7ҫSޝH[;'a͛6ǁK.vI碝 0τwd%;lQS>0u* G@WgU"dV@xL`^WHJU7I*X~r@vofxMDQ`T^Q@h2VDLR: ΝKtƒUdtXguJ@ B஻h;=몫Q@؂妀0Aswп?,'H$|ܙ+ ={# ܦdlOrB@pJB.?_frQU#J zm z{OD V * 1`X2nU@t) Qj݆ f}lhZ2:V_o.`O)CV,QOrTg* JV oWڰ~ۿt&3vL:N ?DB@hkSFg Sur v7D@rN $=[oV^-[&ԨsBo}a:.^"ݺe|5ѵ?¿)Dc! { `& ˩ N@t?QUhĦ, `ƌOSkD]li]S`yCH^ ) 3>޻) v,@YN;6 HQ#4*ߓŋimD,Xc}ұZ#wL@uľ+ tl@J my]`t:ڥ[;p=t 4W)-&ySr@B@0 g[+9 ۷ӲNbNLԅ]P$3Q@ZZD᠃ BX|>Q@25Ԃ$I L#FQkzJ߶ɮokTXC1 :`~"gJR:$!}8oFA) t_ڕ~uD:ʢ`} _ XT2MVKBwR@yM6-Y-X42;7G>d݂ `O@t®}RI+ɂ?A$ᆪN@RQ@o {o9޻Y*+ן~:US:`.ii!TK py5(~6e 'кuse&R@ojYÇ*Z?1P@nG wߑ͋}JE@ HIO? ֭tNx{D̾Iyyt?W},=׈!$鶴U+quתr@ft:,HW ; ([PT HqK/Sk`Ii9֦Vuq>L Ͻ$;U:f=ּ=6|)-u Vc#Uzi WfۗWWÆA}Q߫&uᦛYaqekF?}EE+sd0_\qy&Z]|J4ٵ;Ar@D! ~BJ*Ń|_2h < a&mmwڏDzY] тeW:ɂg-PMexNbѓp{˂RTDX~ Hee #wHK}k7]:C-볉u#1jDWCǸՂJJʴi;:wϬy 73jjHؽ﷟ ľLۗҘ12lSNooL>wJ确Wmv H]GV~k.I`B@v@à7p*Xv8'$s@,/pgU@DD S@N#}?N /~y)eed!?qlNBNm-#&XH'3<F9q~TNa.W/"s|_~1CUK7>ikSɹ+Z[ͿS+U! |C;( YO{{j]9G#G w$t Vҫa9YTVfjhS@) }G,X OI@FnV/$C@VCϞt} )/jnVs0i[d7ѧ]eehYv`b(-ytkAs FlNDHG$tq˖ľ@7*~;咋&N<3:q_^OB…exSŧRakNu^DW@ $NDJkp[ ]k+tX;A&X npR@յ+.=V}D.9  By79'qԾxL,X@9rZZѣ)FASh~xb@߾zDz{1Ec#tUc9x"%_}E/2ҿ˯dVR0֭3↓^7OGnmY\VGP8x:G_:{ͲeܵiFV'̘A7n$2j:~&OL6t` _HP佉 E@8K@{΂%  V8,m8ykG?FU&ۏD*}{ ?@ *XݺQ۝Q@2ގ;L,oH d‒h HN}4%kWӟ:ԉ۱8ӟ(og**ځ HSp1oZFC mUXHjзhN;ik.$RRXHs4_]M7|2цAhDT@Ă #8raoJBM47X͜9sWk Jr̹n˚ҿ?-oxr^eUs9D'- t<33[b@vN{ }Gr@RMBo}suJ@BXK;,z/(ߣ[*,$R_XHD3&00O QTD @p UTI P3UUy?cǪǛo44PyTJϨ$}OU@D ! 7 b`ܩ:z* - o_z ,âs.ۚD^SL(Lޭ`A IDAT$UX-X9aR' t,N}+Ȩ w Bq/g< X(;뚚H+ =As@,2 -X:xD/bU@RPN9Ere7,.G~|9SN߳n" RzZZ$tVUd;F٬9 ~`ف 'ϖeeٳۗ~$ ?cĂeU@o~Z7WD}G)kkU9pJJ@.: GK!ex=U"Ojn!8ܳ΢ߑ,E@ـ9V6b-,Xv ȣb"5k(ߟj./|ݏN<_'<;.'GܮG$t?Uf+Fgg͢SNKJT @~ŋS@JJ* !,T]AҿK{""ɠHSWGLB' zbv2 H޵Ky L9(/O 383y-S'nh_ZL@ ǑKOޛoM\Np_Gmpz[M<;Ǭ$ L@7S޳ʄN& |}l`A-"b`% pD ֐!$'tK@tFAH#$,V{:'/^7T炓uq4S~^* cPŭ W\A/YB(h}FHHf&,xmz'P ݂% 7vkkW\{Hv7ҫUMN:vAV,}[KwMB׭A-X" J+q̓{o?`+*矧҄ MA:kY`ɾ ^jqrZ'* UXhj2ݪ`T |yF/X-X. o| ^ ]*$y@A@w{PUpq3C-^L˵ki= UV ;H$ O:IO:7G*s"B@~gBGRZ:xMܷoPv? >.IkNq`RQA£u:J`Dd@:n" \i;v^0 XBHon LB HCyg7lu8#1chnƌٽ^mzT[u*bc9 ݫ WH`)d" + xmNP'Mp~4~@> 5טGt: C&"%;Q@ɎLfKg[G7}V vMTA,X=D343XilTBW@r@'nǂᄡ WVS! CI‡ڱ byΉL̾…UW%}<1`+ [!=Q{?dfHX>*̲yz~tݽ[=B@Nl\NI<ږ N@!F҆  {Ώ( + ܖGEUZZ:v4#F&qR@{Ԥђ$.]h47nDW@ĂEK'#旀t Fj#a#.d&7)1:aGZ`Y';v1fBq1O~BcU;uc$B@^UFRUcmC@&Ν ,Ybl)]hh}6F]1 "i]ŘL;YUp_Tݻ-Xn H_wߌ'Pw RTm[& 鱪q(yy;<I&-XVs p0 D@2~AD ;R`% k)ŦlH\a$l?9s$WVNʀu{(U^t&֪-V5+H]q_y{&t`)Cqأnп? + V7 ٱCUcCk/'G@ h)$H& =U xt)ل`C@& (x:P_O.hmt~[?8C!<  s@,X*Xv9 :ij2?m+  GeD0aFXtdH6QsU(`` ; pF@&L@`S Q61;U@>7rfawXޏB#a|44 rT\\ymRQ@{IPdN,? șg-mN 9 a EE漫Lצ~u.t l‚N87nTyq}FH!$7 |po-x懺ݍ>Sa#GKJUr`ωX6KBohP 'CZX'=lX~g)r@] J@ $jK:ȱFU>玕Q)("|?΂%ICHoz:-X Ƃ՞zi$-Xx.v0 :KBwR@ Nv +a7Épo vi:o&pXwI%Q-2D@{m& )ftY$Ax1p#x>\J_r@b! 'qI@_\E g0 ӈu$+GYD@ґ1튊#~ W̩H @>cpH9sFXt=o)V,K6Ν)ЪS4#zMM9sfvX(~RjYQ)DH6[lvpS@Yn,T,X*X[VF^d`pi ,ҳ'&gaDI![sBd G`wbUѣIVEnuR@t VQ*X@ɓ7ĂzOy_tQ[Hk@mᢤ& ɓ'ömB@( |LۂWbwoȤG3kNH>2,^m{^' n ÚXnNҙ0aZguBq,XJ@sv' Lh{3L`O),~ cG0f# l$t@Is?7,'$n1LX`Ms@( |s lG"ѷ~K;P2t(}ԔdU\+$L4߆"9 ;vҎw\q"m#ӱ#)]$[~/8;30{:t?]6]^עEୀ%376_RZL@DNYin6g^ޕhx59 n 8ݒ=( A,X}mmy) a&-- I|tg:?9 uuD`ͪXНr@x =R@g,}>9nIbRhiQHT@{v]=sko#${d=l$39 nh7+/+CUUUz t۹VKΖJ_gE29 L@8 NłNhyijvݽ *Kl_*# ehw"A{}aذ&3FkwNٸ2Ȕ)ST$bN7 ֮]u>}bLYĉD%}Ѓ( Y{⋉0yw`AYߔ)Sm+ br& ? y$ DAC`l2 5ϙH!$?3mo?[*.N7-+Ã>^GoZq 𗄞 %A y@x P]N'RP`&,N,?((l> e`1tR_H*$ F /i) Hr`̄93,XzzX,8p8M0`@8otuNKqS@b*w*Y0 r z~޿,Xnʆ/2@@|zkz pR@jkԕOF}}`"exa">dHtͰQ@"|WH,Æ^㖄y4l**H*X [H^wުX^Ăd,X$xW* Low'z_3V"I iB@bQ&@& n9 ]P pOX+1}}=߆N@c2uugVm<d .+SD%l( ( ( q$ uuXvHhEf̘+ XJK)Hs>j* 53x!VpP,78) Bee$l VF3P.ӦM|Hq1%0U ۺUo+ z]Rig{ϼ,\o/RQH@RQ@x]]?RZJ}.iӦЯr:] u/$ F }3CK] AH>ÉUՂĊLS/N: h]j]YK2oNZY)bw]~-XW%0# :8O @ޮ TgQ@тepp5x1cyU?2ƀ( N|Zj ݵǂb܏ۤj}[7Z~F6kZOclkfB@L:JKgB/(PUZ4lٲ%7n݀B4f +*"gj" Oျ[8|]◀pԂ %1@M3 nR ]ߛXeˀ=^ H G׫z: $g=j$K,X_~y8ޛHApY*`z5`<8ڥ/Lwa꬀n9 s:O:o!S\~6 RK_ҩH0/9@Di4 B@br :ujHy`atL#=GCE}kmNr T}Z^Fe]n9 N'?}t+ ZBԩS7#,'ET+ IDATM~M9M O' 듯} ȲeijmB'-bB@~;vϺ:v4O#G_}X H׮@dݛԑsϥ^~uIR@hȊ E@YL@tI<׀{@)<\஻uh: k/Zn}bȑ[s f X ^4nUߎگm LNԪUhAI38DHTVR$)@N%2Q>Yy9fGM7ɏ?~{Ҫ_Otg ~_RQT/gM94-[f|--Y6+ |$ Vĉ9*+ ?]{ D@Ăqg9;·` &2Uo_zm7*c`%񡇀sIF@vmhH*y@Vt]k{7ekW:ȎܲŜ*}"NtI^WV ~5Z:-<0,dHzrN H&#wq &ZZB@̙>J?LmlV8Rz۶ԏ/``Lŋ3g | nǎ~Уz )-т5g-X݃DQĪX N~'|-L6!f+6c%`pѢEox3f.7i<>ɚGX,XyJK EEF~ѩFv#ޥЯ_ ȢE?  V>+ v٘VT|@tm#>i+JDgIB¶`=p_x} F@oe6۵IÆeumbb֭Ls0i.J?7<{,OG5M7,XL@ur9# | L@mK|&<(c?<| ssa<^:V:jkxӭ b>f栏H6’BzxW^jĂF'~s@ ]"?z@ K+O<]P + zăLNuӟW]׊ E@FhLc;UD43ϴmBtEh\N4'd<|bJs@V^cK% ]ꋩZ *_OmfXguk*XL@+ti)pt=L _q9F7=c.8#głUQAVD.pR@ggr`)k9~xsbA]< 3dS( ?~<`"b**2iO=\xahJ ydpyGҁ[n߮T*.&5 (Y&>!CoFzHA]NԊRP\pb\F9}ҌB"z^n4f|OAJψK:@#_}w;) wUICLv _%;K 4'Fy9GDX̣% kȾse*_" ) AF? $VbP+ 6HB' UU%:HTSc?|xP֦Maki?WW$H^&CG<^P/_y'0ryN+dp`d<]#eq~Rr*y~UZzN\ISOn:׿.HB@BDcc#~_`BEE9QBK =;tP䭷6-} Dt.j$vv#""7ܠsN @ HS_nfKRģ)G^߾: xqQُzs'*ﺋF Nƍ*ΝtƁT 9|e`Ne1K`:rv6޼jkxF8]5uÚ5mѽ0(NTrpit\Lh.X*:NV:wH|Et<ٜ3t^*+eV pCi)͋/+T D~J}jZ""ݻSbB@Be]YfK.ߏ:OZZ^MNr-, Ddzz5ٕҙG[H6n$rQ~Jhխ~0,WHMT 2h S@di}T5 .4^ѷ=FF}=ݐw"8aO>I|b?L' Am0Y+xT#Y imt47S;t𗿐܏v+Vhqhu1z* Z~;ի7 &~K[>,Kҹ3"@o6ο_DJ92~ǀ*)8 ;/ѫ ̜x" <,F>> A(裏k_` 28lg…c…9{1JC 0vuZE_>DO}ĉѱa}atb-m3~aPqzQa壭r1"_ʕt7oH^z.]}Oҥ4jD%|zjj+( Usga>WZ\&J=sgRyqSt| ƍt<>YRBe{kVsZ33{~{{[o_y%}siGtt۲% RYqHW\A}걄"ŋcG'D~ϧ~a)Q%c̟rJ4SRBAQYf`r ٍ8@nm͉;w&PnSOQYevJ<0䓁eˀ9stWh )gGIp.t6n$r`%.X~-7 P'>rQr~MZvNm;_VV9ѣ)YAY4^FV5?R5 OӹIvOfұ#-wRB$8 c2;A2cA'֓O@*3>.CZ[^0K_^d7o{57RG54ҽ;#r΁vl 6wA㿘@HHF?AnRE][=GmOe%?FB8Fb mm䑾F uSN!"c~@_z7W++ks3Tb 87D9ws  _6 jXbРAÄ_~QPP`OgFW c(g mmѵawlLiƶm#F W/2F0;0ɯ FI -ͣ 0}Z&-=07վ08 ViHp-wڕI~-߼CAZq{.tt>('{wc8Z܍6c矓ǝ?}0<7V;tax olk[8 Stz~7 c~?9IkX@'pI&GL ,Hh<7TTR~uO~XAGIf|B#Ud.Eq~KVc8!Vx1:}n֟p>Yw̡mW=dUI ).XW]EK\`XEA];Fm릪ӧ(Ť.MN^--tO2PCuW;:,tLMwR1zkORMf${ͨQt|?Lfo,\s\P@*Dze{U PP! !K-qD~YH%JJ4h=8 c("HHxV8?aoȞyf~_ ~={TF>+V(_}rQټwN*ՍuŚ5َ{%Qٰ[;WZ\^_6gʙ?V~t,5rrzǻ3d27#h9RY5sϡC0}t >k׮)Ο?W0n"$$EEED">sU[ss0x`Wđ.---AA",Y"d2ʕ+ ڵKkM BVQQ! sU1&xzz 6#a„ hW^^ Hc.hll+>yz\%,wU"<<j6|.Cvv6MƏ=>zzꓵСCQVVjc6C*bj1qoFBBAИa>MMM~8q P.noo| jSNcXԉW#F7rH0I\~VVV.{.n޼ .`˖-_b Glmmm¢ETK|ij`8qjsOnn.+W`Ȑ!0339.]̧xjkkakkVSS!Qjkk˼/( Ԃ4`.xk.U3G\III¯~}={6L+++={7oƿ/ӓ̙3pBlܸyyyضmnݺtM,@Dv=jnQyɦgރ/͛肘iii1͛|DT__kbڵa>iӦa6lVZ0ݾ}wŒ%K9s&Z[[sN|̧Kdƪ577nȂyڵk:u*,,,Zl7d`ܹ8pƏ˗h͚5BTTT}npvvƌ3A:9s樵w>v& "tUԞD]fA|5667_~}FeeeG$HNNFTTq%\t hmm˗|tڊ;w0u۷Z{LJ4440Dd^^^(//GSSZ{QQSaQ'~* UIDATkkk?~\c_qq1A͘>}:Ο?g6cyT*e>"z*۱l2 4HNNNc>:ŋ066)sQljjo[[[Ύt ͞=mmm7t^JJ ƌ8:@=)>| qdϯ6=zt84 [rէ.1޽{o>նw^Cxx8mݜ>}?3&Mjc.޽[믿>0iEHH݋8;;#55%%%8|0^}Ukl߾nBMM 0k,;˖-C>}P]] ///⋈FSS6m{{{?~ӭ=`HLL;}B;w.0jjp5))) |t?qUѮ666?k.C \ĴpBٳC~~>zj'02n"_| FFFѣC=^QH$D"R JU__|Yٳ믿.d2R nܸ!ȟo,IRF{aĉB~}}}A. SLrss52/xxxh3ILLF-rA___߿&\pA/sB>Q000\\\[jc>O3 DDDDD5<i """"" DDDDD5,@HkXְ!"""""aBDDDDDZQ/ = K.ŤI蘤$888FEDD((('|F}DQuwFll`bΝ=42"". DD r$''0mݺ kDb޼y/zhdDD],@zA4 /0) !88˗GFDD "ܺub R)R)h7@*ȑ#,,,xb( 444 ,, Xr}#!!nnn066F~[n=rGA}}=&Lo۶mpssL&%F >ްO?Di " ddd !!VVVs@`ggף077cÆ ؿ?6mwww]x1RSS;`x"oߎR=zzz]SPPD///dDGG#((111hnnӧQ\\9sѣG1#"9///ddd`̙lmm~@DDΟ?͛7cɒ%رc`ѢEpttĞ={Tȑ#G{nddd $$Dƍɓ'#33S`xPYYr9LMMwww ""%XDDԩpۣFhJ>|8.^j̄9Ə7o6ooodGQ__ v \r%%%ݻG%""bBDDL9` hllv=;;Y~ʕ055ŨQH ___=񮮮HOOGSS 88P(5kzjUVV_BDD`2 ԖJ=K8!!!hkkC\\Fwz1Bs=n뫊 Bɓ}3 DD# >f̘NϷxR~???,^6lS0qD룢YYYHLLĬY^cǎ\.Gnn.ƍj4ilmm닾}ܹsرcN*ĉhhhoퟋ= DD#$;wDll,P;c500oLǫ#""-[8p f333V/""g8p8::bՈ9 DDs7>q)))044DDDD3 DDDDD5!"""""aBDDDDDZi """"" DDDDD5,@HkXְ!"""""aBDDDDDZi """""Қ>0f4rIENDB`psrecord-1.4/tox.ini000066400000000000000000000007471461273274000145300ustar00rootroot00000000000000[tox] envlist = py{39,310,311,312}-test{,-psutil56,-psutil57,-psutil58,-psutil59}{,-alldeps} requires = setuptools >= 30.3.0 pip >= 19.3.1 isolated_build = true [testenv] changedir = test: .tmp/{envname} description = test: run tests with pytest deps = psutil56: psutil==5.6.* psutil57: psutil==5.7.* psutil58: psutil==5.8.* psutil59: psutil==5.9.* extras = test alldeps: plot commands = pip freeze pytest --pyargs psrecord {posargs}