diff --git a/.travis.yml b/.travis.yml index 7d2db52..a1b4b62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,11 @@ os: env: matrix: - - PYTHON_VERSION=2.7 - - PYTHON_VERSION=3.3 - - PYTHON_VERSION=3.4 - - PYTHON_VERSION=3.5 + - PYTHON_VERSION=2.7 PSUTIL_VERSION=1 + - PYTHON_VERSION=2.7 PSUTIL_VERSION=2 + - PYTHON_VERSION=3.3 PSUTIL_VERSION=3 + - PYTHON_VERSION=3.4 PSUTIL_VERSION=4 + - PYTHON_VERSION=3.5 PSUTIL_VERSION=5 global: - CONDA_DEPENDENCIES='numpy matplotlib psutil' - PIP_DEPENDENCIES='coveralls pytest-cov' @@ -17,10 +18,11 @@ env: before_install: - git clone git://github.com/astropy/ci-helpers.git - - source ci-helpers/travis/setup_conda_$TRAVIS_OS_NAME.sh + - source ci-helpers/travis/setup_conda.sh script: - py.test --cov psrecord psrecord + - python setup.py check --restructuredtext after_success: - coveralls diff --git a/README.md b/README.rst similarity index 50% rename from README.md rename to README.rst index 6c5ce99..4a94ff3 100644 --- a/README.md +++ b/README.rst @@ -1,36 +1,31 @@ -[![Build Status](https://travis-ci.org/astrofrog/psrecord.svg?branch=master)](https://travis-ci.org/astrofrog/psrecord) [![Coverage Status](https://coveralls.io/repos/astrofrog/psrecord/badge.svg)](https://coveralls.io/r/astrofrog/psrecord) +|Build Status| |Coverage Status| About ===== ``psrecord`` is a small utility that uses the -[psutil](https://code.google.com/p/psutil/) library to record the CPU and -memory activity of a process. The package is still under development and is -therefore experimental. +`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. +The code is released under a Simplified BSD License, which is given in +the ``LICENSE`` file. Requirements ============ -* Python 2.x or 3.x -* [psutil](https://code.google.com/p/psutil/) -* [matplotlib](http://www.matplotlib.org) (optional, used for plotting) +- Python 2.7 or 3.3 and higher +- `psutil `__ +- `matplotlib `__ (optional, used for + plotting) Installation ============ -To install: +To install, simply do:: pip install psrecord -or if you want the latest developer version: - - git clone https://github.com/astrofrog/psrecord - cd psrecord - python setup.py install - Usage ===== @@ -39,11 +34,15 @@ Basics To record the CPU and memory activity of an existing process to a file: +:: + 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: +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 @@ -52,27 +51,35 @@ Plotting To make a plot of the activity: +:: + psrecord 1330 --plot plot.png This will produce a plot such as: -![screenshot](screenshot.png) +.. 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: +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: +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: +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 @@ -81,10 +88,17 @@ Subprocesses To include sub-processes in the CPU and memory stats, use: +:: + psrecord 1330 --log activity.txt --include-children Reporting issues ================ -Please report any issues in the -[issue tracker](https://github.com/astrofrog/psrecord/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://coveralls.io/repos/astrofrog/psrecord/badge.svg + :target: https://coveralls.io/r/astrofrog/psrecord diff --git a/psrecord/main.py b/psrecord/main.py index a194d77..b0b9678 100644 --- a/psrecord/main.py +++ b/psrecord/main.py @@ -47,10 +47,14 @@ def get_memory(process): def all_children(pr): processes = [] + children = [] try: + children = pr.children() + except AttributeError: children = pr.get_children() - except: - children = [] + except Exception: + pass + for child in children: processes.append(child) processes += all_children(child) @@ -143,6 +147,8 @@ def monitor(pid, logfile=None, plot=None, duration=None, interval=None, pr_status = pr.status() except TypeError: # psutil < 2.0 pr_status = pr.status + except psutil.NoSuchProcess: + break # Check if process status indicates we should exit if pr_status in [psutil.STATUS_ZOMBIE, psutil.STATUS_DEAD]: @@ -167,18 +173,19 @@ def monitor(pid, logfile=None, plot=None, duration=None, interval=None, if include_children: for child in all_children(pr): try: - current_cpu += child.get_cpu_percent() - current_mem = child.get_memory_info() + current_cpu += get_percent(child) + current_mem = get_memory(child) except: continue current_mem_real += current_mem.rss / 1024. ** 2 current_mem_virtual += current_mem.vms / 1024. ** 2 if logfile: - f.write("{0:12.3f} {1:12.3f} {2:12.3f} {3:12.3f}\n".format(current_time - start_time, - current_cpu, - current_mem_real, - current_mem_virtual)) + f.write("{0:12.3f} {1:12.3f} {2:12.3f} {3:12.3f}\n".format( + current_time - start_time, + current_cpu, + current_mem_real, + current_mem_virtual)) f.flush() if interval is not None: diff --git a/psrecord/tests/test_main.py b/psrecord/tests/test_main.py index 8e7e999..9973349 100644 --- a/psrecord/tests/test_main.py +++ b/psrecord/tests/test_main.py @@ -2,7 +2,32 @@ import sys import subprocess -from ..main import main, monitor +import psutil +from ..main import main, monitor, all_children + +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('{0} {1}'.format(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(object): @@ -19,6 +44,10 @@ def test_simple(self): 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)