Skip to content

Commit

Permalink
Merge 581e75e into be9581f
Browse files Browse the repository at this point in the history
  • Loading branch information
lexover authored Mar 8, 2018
2 parents be9581f + 581e75e commit 0148fcd
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 17 deletions.
12 changes: 12 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ To include sub-processes in the CPU and memory stats, use:

psrecord 1330 --log activity.txt --include-children

Memory on linux
------------

To record memory activity on linux system as "real memory"
better suited unique set size (USS). Uss is the portion of
main memory (RAM) occupied by a process which is guaranteed to
be private to that process:

::

psrecord 1330 --log activity.txt --linux

Reporting issues
================

Expand Down
80 changes: 64 additions & 16 deletions psrecord/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import psutil
import time
import argparse
from sys import platform as _platform


def get_percent(process):
Expand All @@ -38,9 +39,10 @@ def get_percent(process):
return process.get_cpu_percent()


def get_memory(process):
def get_memory(process, use_uss):
try:
return process.memory_info()
memory_info = process.memory_full_info() if (use_uss) else process.memory_info()
return memory_info
except AttributeError:
return process.get_memory_info()

Expand All @@ -61,6 +63,23 @@ def all_children(pr):
return processes


def is_uss_possible():
#Test is platform linux
if (_platform == "linux" or _platform == "linux2"):
#Linux option works only with psutil version +2.6x on Linux
if (psutil.version_info >= (2, 6)):
return True
else:
print (("Warning: flag --linux ignored. Version of psutil module have"
"to be greater than 2.6 to use --linux flag. Your version is: "
"{0:d}.{1:d}").format(psutil.version_info[0], psutil.version_info[1]))
return False
else:
print (("Warning: flag --linux ignored. Your OS detected "
"as: {0:s}").format(_platform))
return False


def main():

parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -90,6 +109,12 @@ def main():
'in a slower maximum sampling rate).',
action='store_true')

parser.add_argument('--linux',
help='in additional to real memory usage show unique '
'set size (USS) as more suitable to know how '
'much memory program were using.',
action='store_true')

args = parser.parse_args()

# Attach to process
Expand All @@ -106,14 +131,18 @@ def main():
pid = sprocess.pid

monitor(pid, logfile=args.log, plot=args.plot, duration=args.duration,
interval=args.interval, include_children=args.include_children)
interval=args.interval, include_children=args.include_children,
linux=args.linux)

if sprocess is not None:
sprocess.kill()


def monitor(pid, logfile=None, plot=None, duration=None, interval=None,
include_children=False):
include_children=False, linux=False):

#If flag linux true test is it possible to use this flag
if (linux):
linux = is_uss_possible()

pr = psutil.Process(pid)

Expand All @@ -122,18 +151,25 @@ def monitor(pid, logfile=None, plot=None, duration=None, interval=None,

if logfile:
f = open(logfile, 'w')
f.write("# {0:12s} {1:12s} {2:12s} {3:12s}\n".format(
f.write("# {0:12s} {1:12s} {2:12s} {3:12s}".format(
'Elapsed time'.center(12),
'CPU (%)'.center(12),
'Real (MB)'.center(12),
'Virtual (MB)'.center(12))
)
if (linux):
f.write(" {0:12s}".format(
'USS (MB)'.center(12))
)
f.write("\n")

log = {}
log['times'] = []
log['cpu'] = []
log['mem_real'] = []
log['mem_virtual'] = []
if (linux):
log['mem_uss'] = []

try:

Expand Down Expand Up @@ -163,29 +199,36 @@ def monitor(pid, logfile=None, plot=None, duration=None, interval=None,
# Get current CPU and memory
try:
current_cpu = get_percent(pr)
current_mem = get_memory(pr)
current_mem = get_memory(pr, linux)
except:
break
current_mem_real = current_mem.rss / 1024. ** 2
current_mem_virtual = current_mem.vms / 1024. ** 2
if (linux):
current_mem_uss = current_mem.uss / 1024. ** 2

# 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 = get_memory(child, linux)
except:
continue
current_mem_real += current_mem.rss / 1024. ** 2
current_mem_virtual += current_mem.vms / 1024. ** 2

if (linux):
current_mem_uss += current_mem.uss / 1024. ** 2

if logfile:
f.write("{0:12.3f} {1:12.3f} {2:12.3f} {3:12.3f}\n".format(
f.write("{0:12.3f} {1:12.3f} {2:12.3f} {3:12.3f}".format(
current_time - start_time,
current_cpu,
current_mem_real,
current_mem_virtual))
if (linux):
f.write(" {0:12.3f}".format(current_mem_uss))
f.write("\n")
f.flush()

if interval is not None:
Expand All @@ -197,6 +240,8 @@ def monitor(pid, logfile=None, plot=None, duration=None, interval=None,
log['cpu'].append(current_cpu)
log['mem_real'].append(current_mem_real)
log['mem_virtual'].append(current_mem_virtual)
if (linux):
log['mem_uss'].append(current_mem_uss)

except KeyboardInterrupt: # pragma: no cover
pass
Expand All @@ -215,14 +260,17 @@ def monitor(pid, logfile=None, plot=None, duration=None, interval=None,

ax.set_ylabel('CPU (%)', color='r')
ax.set_xlabel('time (s)')
ax.set_ylim(0., max(log['cpu']) * 1.2)

max_val = max(log['cpu']) if log['cpu'] else 100
ax.set_ylim(0., max_val * 1.2)

mem_selector = 'mem_uss' if linux else 'mem_real'
ax2 = ax.twinx()
ax2.plot(log['times'], log[mem_selector], '-', lw=1, color='b')
max_val = max(log[mem_selector]) if log[mem_selector] else 100
ax2.set_ylim(0., max_val * 1.2)

ax2.plot(log['times'], log['mem_real'], '-', lw=1, color='b')
ax2.set_ylim(0., max(log['mem_real']) * 1.2)

ax2.set_ylabel('Real Memory (MB)', color='b')
mem_label= 'Unique set size (MB)' if linux else 'Real Memory (MB)'
ax2.set_ylabel(mem_label, color='b')

ax.grid()

Expand Down
22 changes: 21 additions & 1 deletion psrecord/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
import subprocess

import psutil
from ..main import main, monitor, all_children
from ..main import main, monitor, all_children, is_uss_possible

TEST_CODE = """
import subprocess
p = subprocess.Popen('sleep 5'.split())
p.wait()
"""

def test_is_uss_possible():
if ((sys.platform.startswith("linux")) and psutil.version_info >= (2, 6)):
assert is_uss_possible() == True
else:
assert is_uss_possible() == False

def test_all_children(tmpdir):

Expand Down Expand Up @@ -59,6 +64,21 @@ def test_plot(self, tmpdir):
monitor(self.p.pid, plot=filename, duration=3)
assert os.path.exists(filename)

def test_logfile_linux(self, tmpdir):
filename = tmpdir.join('test_logfile').strpath
monitor(self.p.pid, logfile=filename, duration=3, linux=True)
assert os.path.exists(filename)
is_uss_active = (open(filename, 'r').readlines()[0].find("USS") > 0)
assert is_uss_possible() == is_uss_active

def test_plot_linux(self, tmpdir):
filename = tmpdir.join('test_plot.png').strpath
monitor(self.p.pid, plot=filename, duration=3, linux=True)
assert os.path.exists(filename)

def test_linux_with_children(self, tmpdir):
monitor(os.getpid(), duration=3, include_children=True, linux=True)

def test_main(self):
sys.argv = ['psrecord', '--duration=3', "'sleep 10'"]
main()
Expand Down

0 comments on commit 0148fcd

Please sign in to comment.