pax_global_header 0000666 0000000 0000000 00000000064 14612732740 0014520 g ustar 00root root 0000000 0000000 52 comment=b76d59f3cca6686caf498fe512ca226c82936bb5
psrecord-1.4/ 0000775 0000000 0000000 00000000000 14612732740 0013205 5 ustar 00root root 0000000 0000000 psrecord-1.4/.github/ 0000775 0000000 0000000 00000000000 14612732740 0014545 5 ustar 00root root 0000000 0000000 psrecord-1.4/.github/workflows/ 0000775 0000000 0000000 00000000000 14612732740 0016602 5 ustar 00root root 0000000 0000000 psrecord-1.4/.github/workflows/main.yml 0000664 0000000 0000000 00000001730 14612732740 0020252 0 ustar 00root root 0000000 0000000 name: 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.yaml 0000664 0000000 0000000 00000001545 14612732740 0022702 0 ustar 00root root 0000000 0000000 # 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/.gitignore 0000664 0000000 0000000 00000006034 14612732740 0015200 0 ustar 00root root 0000000 0000000 # 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.yaml 0000664 0000000 0000000 00000000347 14612732740 0017472 0 ustar 00root root 0000000 0000000 ci:
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.md 0000664 0000000 0000000 00000003554 14612732740 0014606 0 ustar 00root root 0000000 0000000 ## 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/LICENSE 0000664 0000000 0000000 00000002423 14612732740 0014213 0 ustar 00root root 0000000 0000000 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.
psrecord-1.4/MANIFEST.in 0000664 0000000 0000000 00000000115 14612732740 0014740 0 ustar 00root root 0000000 0000000 include README.rst
include LICENSE
include screenshot.png
include CHANGES.md
psrecord-1.4/README.rst 0000664 0000000 0000000 00000005234 14612732740 0014700 0 ustar 00root root 0000000 0000000 |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/ 0000775 0000000 0000000 00000000000 14612732740 0015026 5 ustar 00root root 0000000 0000000 psrecord-1.4/psrecord/__init__.py 0000664 0000000 0000000 00000002731 14612732740 0017142 0 ustar 00root root 0000000 0000000 # 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__.py 0000664 0000000 0000000 00000002570 14612732740 0017124 0 ustar 00root root 0000000 0000000 # 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.py 0000664 0000000 0000000 00000024713 14612732740 0016333 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14612732740 0016170 5 ustar 00root root 0000000 0000000 psrecord-1.4/psrecord/tests/__init__.py 0000664 0000000 0000000 00000000000 14612732740 0020267 0 ustar 00root root 0000000 0000000 psrecord-1.4/psrecord/tests/test_main.py 0000664 0000000 0000000 00000004532 14612732740 0020531 0 ustar 00root root 0000000 0000000 import 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.toml 0000664 0000000 0000000 00000002103 14612732740 0016115 0 ustar 00root root 0000000 0000000 [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.png 0000664 0000000 0000000 00000144773 14612732740 0016110 0 ustar 00root root 0000000 0000000 PNG
IHDR X vp sBIT|d pHYs a a?i IDATxy|Tw@ *B!k]hE("*֍XP*bAj*?iEEE#J`BeJQ `r'If&̽g<wn%3sq .H kh@ 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ѣug7O6mW/
wyGV-mڴIѣOH&M\֭$i:묳4o<]y啒2y:StҪy饗*//Oڵk+ %XRc=V}Uaaaն%KhԨQU͇$g?SϞ=xm+VPII&MTkN