clean up script & make pip-installable
diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644
index 0000000..5878d4a --- /dev/null +++ b/.bumpversion.cfg @@ -0,0
+1,13 @@ +[bumpversion] +current_version = 0.2.0 +commit = True +tag =
True + +[bumpversion:file:setup.py] +search =
version="{current_version}" +replace = version="{new_version}" +
+[bumpversion:file:monitor/__init__.py] +search = __version__ =
'{current_version}' +replace = __version__ = '{new_version}' + diff
--git a/README.rst b/README.rst index 985842c..372e5de 100644 ---
a/README.rst +++ b/README.rst @@ -58,16 +58,19 @@ subprocess.
Installation ------------
-``monitor.py`` is a simple, self-contained Python script, so just
-download it from GitHub, ``chmod +x`` it, and run it.
-
-The simplest way to get the whole source folder is:: +Download the
source from GitHub::
git clone [--depth=1] https://github.com/javiljoen/monitor.git
where the ``depth`` parameter will get you the latest version only
instead of the whole commit history.
+Then install the package into a virtual environment, e.g.:: + + conda
create -n monitor python=3 psutil=1 docopt + source activate monitor +
pip install monitor +
Requirements ^^^^^^^^^^^^ @@ -84,7 +87,7 @@ Testing
::
- monitor/monitor.py tests/testscript.sh + monitor
monitor/tests/testscript.sh
------------------------------------------------------------------------
diff --git a/monitor/monitor.py b/monitor/monitor.py index
9cf1273..ea9ecd5 100755 --- a/monitor/monitor.py +++
b/monitor/monitor.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3
-'''Per-process resource monitor +"""Per-process resource monitor
Usage:
- monitor.py [-i INTERVAL] [-s SEP] [-p PROCTYPE] [-o OUTPUT] CMD
- monitor.py -h + monitor [-i INTERVAL] [-s SEP] [-p PROCTYPE] [-o
OUTPUT] CMD + monitor -h
Options: -h --help this message @@ -19,120 +19,88 @@ with single
quotes. (If not escaped, it may be unclear whether the options apply to
the monitoring or the monitored process.)
e.g.
- monitor.py 'sleep 2' + monitor 'sleep 2'
Warning: Likely to fail on short-running commands (like `du -s .`)!
-This script is inspired by: +""" +from time import sleep
- * https://github.com/jeetsukumaran/Syrupy
- * https://github.com/Dieterbe/profile-process +import psutil +from
docopt import docopt
-It differs from Syrupy in also accounting for child processes, while
-being much less sophisticated in all other regards. It also requires
-the `psutil` module to be installed to provide the `.get_children()`
-method.
-The `psutil` loop is a reimplementation of the one in `profile-process`
-that does not send the data straight to matplotlib for plotting, and
-breaks it up into usage stats per subprocess. +def monitor(command,
interval=1.0, sep='\t', proctype='pname'): + """Record resource usage
of `command` every `interval` seconds.
-Dependencies:
- * Python 3
- * psutil 1.2 (syntax changes in v. 2)
- * docopt + Returns the resource usage of `command` and each
subprocess it + spawned as a list of `sep`-separated strings: +
one for each process at each sampling point in time.
-Tested with Python 3.3.4 and psutil 1.2.1. + `proctype` must be
either 'pname' or 'cmdline'. + """
-''' + def measure_resources(process): + if
process.is_running(): + cpu_percent =
process.get_cpu_percent(interval=0) + n_threads =
process.get_num_threads() + mem_info =
process.get_memory_info() + io_counters =
process.get_io_counters() + return cpu_percent, n_threads,
mem_info, io_counters
-from time import sleep -import psutil + def format_resources(t, pid,
pname, res=None, sep='\t'): + """Convert bytes to MB and return
fields as a `sep`-separated string.
-def monitor(command, interval, sep, proctype):
- '''(str, float, str) -> list(str)
-
- Record resource usage of `command` every `interval` seconds,
- do some formatting and return the resource usage of `command` and
- each subprocess it spawned as a list of `sep`-separated strings:
- one line for each process at each sampling point in time.
- `proctype` is either 'pname' or 'cmdline'.
-
- '''
- def measure_resources(p):
- '''Process -> (int, int, [int, int], [int, int, int, int])'''
- if p.is_running():
- CPU = p.get_cpu_percent(interval=0)
- THD = p.get_num_threads()
- MEM = p.get_memory_info()
- IO = p.get_io_counters()
- return (CPU, THD, MEM, IO)
-
- def format_resources(t, pid, pname, res, sep):
- '''(float, int, str, (result of measure_resources()), str) ->
str
-
- Convert bytes to MB and return fields as a `sep`-separated
string. If measure_resources() returned None, return the
string with empty fields where appropriate.
-
- ''' + """ if res is not None:
- CPU, THD, MEM, IO = res
- RSS = MEM[0] // 1048576 # convert to MB
- VMS = MEM[1] // 1048576 # convert to MB
- IOr, IOw, IOrb, IOwb = IO
- IOrb = IOrb // 1048576 # convert to MB
- IOwb = IOwb // 1048576 # convert to MB
- data = (t, CPU, THD, RSS, VMS, IOr, IOw, IOrb, IOwb, pid,
pname) + cpu_percent, n_threads, mem_info,
io_counters = res + bytes_per_mb = 2 ** 20 +
mem_rss = mem_info[0] // bytes_per_mb + mem_vms
= mem_info[1] // bytes_per_mb + reads, writes,
read_bytes, written_bytes = io_counters +
read_mb = read_bytes // bytes_per_mb +
written_mb = written_bytes // bytes_per_mb +
data = (t, + cpu_percent, n_threads,
mem_rss, mem_vms, + reads, writes,
read_mb, written_mb, + pid, pname) else:
data = (t, '' * 8, pid, pname)
return sep.join((str(n) for n in data))
- ## initialize
# res_use = []
t = 0
- ## start the command running
- proc = psutil.Popen(command, shell=False)
-
- ## in each sampling interval:
- ## * check that the main process has not yet terminated
- ## * get a list of its child processes
- ## * then for each one, measure resource usage
- while proc.poll() is None:
- procs = [proc] + proc.get_children(recursive=True)
-
- # for p in procs:
- # resource_use = measure_resources(p)
- # output_str = format_resources(t, p.pid, p.name,
resource_use, sep)
- # res_use.append(output_str)
-
- ## do all measurements first.
- ## if using a `for` loop with formatting and IO (as above),
- ## the next process might end before it is measured
- resource_use = [measure_resources(p) for p in procs]
- # output_str = [format_resources(t, procs[i].pid,
procs[i].name, resource_use[i], sep)
- # for i in range(len(procs))]
- # res_use += output_str
- for i in range(len(procs)): + process =
psutil.Popen(command, shell=False) + + # In each sampling
interval: + # * check that the main process has not yet
terminated + # * get a list of its child processes + # *
then for each one, measure resource usage + while
process.poll() is None: + processes = [process] +
process.get_children(recursive=True) + + # Do all
measurements first: + # If doing formatting inside this
`for` loop, the next process + # might have ended before
it is measured. + resource_use = [measure_resources(p)
for p in processes] + + for proc, res in zip(processes,
resource_use): if proctype == 'cmdline':
- cmdline = ' '.join(procs[i].cmdline)
- yield format_resources(t, procs[i].pid, cmdline,
resource_use[i], sep) + pname = '
'.join(proc.cmdline) else:
- yield format_resources(t, procs[i].pid, procs[i].name,
resource_use[i], sep) + pname =
proc.name + + yield format_resources(t,
proc.pid, pname, res, sep)
sleep(interval) t += interval
- # return res_use
-
-
-if __name__ == '__main__':
- from docopt import docopt
- import sys
- ## parse user settings +def main(): args = docopt(__doc__) interval
= float(args['-i']) sep = args['-s'] @@ -145,21 +113,21 @@ if
__name__ == '__main__': 'IO reads', 'IO writes', 'IO read MB', 'IO
written MB', 'PID', 'Process')
+ resource_usage = monitor(child_args, interval, sep, proctype) + if
outfile != '-': with open(outfile, 'w') as out:
- # ## run the command and record resource usage
- # resource_use = monitor(child_args, interval, sep)
-
- ## write out results out.write(sep.join(data_heads) + '\n')
- # for l in resource_use:
- for i, l in enumerate(monitor(child_args, interval, sep,
proctype)):
- out.write(l + '\n') + for i, line in
enumerate(resource_usage): +
out.write(line + '\n') if i % 100 == 0: out.flush()
else:
- sys.stdout.write(sep.join(data_heads) + '\n')
- for l in monitor(child_args, interval, sep, proctype):
- sys.stdout.write(l + '\n') +
print(sep.join(data_heads)) + for line in
resource_usage: + print(line)
+ +if __name__ == '__main__': + main() diff --git a/requirements.txt
b/requirements.txt new file mode 100644 index 0000000..af7189a ---
/dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +docopt==0.6.2
+psutil==1.2.1 diff --git a/setup.py b/setup.py new file mode 100644
index 0000000..2492f46 --- /dev/null +++ b/setup.py @@ -0,0 +1,31 @@
+import setuptools + +setuptools.setup( + name='monitor', +
version='0.2.0', + url='https://github.com/javiljoen/monitor', + +
author='Jack Viljoen', +
author_email='javiljoen@users.noreply.github.com', +
+ description='Resource monitor for profiling processes and their subprocesses',
+ long_description=open('README.rst').read(),
+
+ packages=['monitor'],
+
+ install_requires=[
+ 'docopt',
+ 'psutil>=1,<2',
+ ],
+
+ classifiers=[
+ 'Development Status :: 2 - Pre-Alpha',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 3',
+ ],
+ entry_points={
+ 'console_scripts': [
+ 'monitor = monitor.monitor:main',
+ ],
+ },
+)