Skip to content

Commit

Permalink
Merge fe8e363 into 27f629a
Browse files Browse the repository at this point in the history
  • Loading branch information
lexover committed Jun 17, 2018
2 parents 27f629a + fe8e363 commit 128d3b3
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 11 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

USS memory
---------

The USS (Unique Set Size) is the memory which is unique to a process and
which would be freed if the process was terminated right now. USS option
work only for Linux, Windows and OSX systems. To include USS in the memory
stats, use:

::

psrecord 1330 --log activity.txt --uss

Running tests
=============

Expand Down
55 changes: 44 additions & 11 deletions psrecord/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ def get_percent(process):
return process.get_cpu_percent()


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

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

parser.add_argument('--uss',
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 @@ -105,38 +111,54 @@ 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,
uss=args.uss)

if sprocess is not None:
sprocess.kill()


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

# 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)
# Check is it possible to use --uss flag
if uss:
try:
pr.memory_full_info()
uss = True
except AttributeError:
print("Flag --uss is ignored. Not found memory_full_info()")
uss = False

# Record start time
start_time = time.time()

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(uss):
f.write(" {0:12s}".format(
'USS (MB)'.center(12))
)
f.write("\n")

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

try:

Expand Down Expand Up @@ -166,29 +188,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, uss)
except Exception:
break
current_mem_real = current_mem.rss / 1024. ** 2
current_mem_virtual = current_mem.vms / 1024. ** 2
if (uss):
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, uss)
except Exception:
continue
current_mem_real += current_mem.rss / 1024. ** 2
current_mem_virtual += current_mem.vms / 1024. ** 2
if (uss):
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 (uss):
f.write(" {0:12.3f}".format(current_mem_uss))
f.write("\n")
f.flush()

if interval is not None:
Expand All @@ -200,6 +229,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 (uss):
log['mem_uss'].append(current_mem_uss)

except KeyboardInterrupt: # pragma: no cover
pass
Expand All @@ -220,12 +251,14 @@ def monitor(pid, logfile=None, plot=None, duration=None, interval=None,
ax.set_xlabel('time (s)')
ax.set_ylim(0., max(log['cpu']) * 1.2)

mem_selector = 'mem_uss' if uss else 'mem_real'
ax2 = ax.twinx()

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

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

ax.grid()

Expand Down
13 changes: 13 additions & 0 deletions psrecord/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,16 @@ def test_main(self):
def test_main_by_id(self):
sys.argv = ['psrecord', '--duration=3', str(os.getpid())]
main()

def test_logfile_uss(self, tmpdir):
filename = tmpdir.join('test_logfile').strpath
monitor(self.p.pid, logfile=filename, duration=3, uss=True)
assert os.path.exists(filename)

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

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

0 comments on commit 128d3b3

Please sign in to comment.