Commit ade3f247 authored by Luke Macken's avatar Luke Macken

Graph CPU & memory usage.

parent 00ddfe03
...@@ -28,6 +28,7 @@ import psutil ...@@ -28,6 +28,7 @@ import psutil
import logging import logging
import keyword import keyword
import tokenize import tokenize
import threading
from meliae import loader from meliae import loader
from gi.repository import GLib, GObject, Pango, Gtk, WebKit from gi.repository import GLib, GObject, Pango, Gtk, WebKit
...@@ -39,9 +40,17 @@ log = logging.getLogger('pyrasite') ...@@ -39,9 +40,17 @@ log = logging.getLogger('pyrasite')
socket_families = dict([(getattr(socket, k), k) for k in dir(socket) socket_families = dict([(getattr(socket, k), k) for k in dir(socket)
if k.startswith('AF_')]) if k.startswith('AF_')])
socket_types = dict([(getattr(socket, k), k) for k in dir(socket) socket_types = dict([(getattr(socket, k), k) for k in dir(socket)
if k.startswith('SOCK_')]) if k.startswith('SOCK_')])
POLL_INTERVAL = 3
cpu_intervals = []
cpu_details = ''
mem_intervals = []
mem_details = ''
read_count = read_bytes = write_count = write_bytes = 0
class Process(pyrasite.PyrasiteIPC, GObject.GObject): class Process(pyrasite.PyrasiteIPC, GObject.GObject):
""" """
...@@ -82,6 +91,7 @@ class PyrasiteWindow(Gtk.Window): ...@@ -82,6 +91,7 @@ class PyrasiteWindow(Gtk.Window):
self.processes = {} self.processes = {}
self.pid = None # Currently selected pid self.pid = None # Currently selected pid
self.resource_thread = None
self.set_title('Pyrasite v%s' % pyrasite.__version__) self.set_title('Pyrasite v%s' % pyrasite.__version__)
self.set_default_size (600, 400) self.set_default_size (600, 400)
...@@ -275,22 +285,10 @@ class PyrasiteWindow(Gtk.Window): ...@@ -275,22 +285,10 @@ class PyrasiteWindow(Gtk.Window):
def generate_description(self, proc, title): def generate_description(self, proc, title):
p = psutil.Process(proc.pid) p = psutil.Process(proc.pid)
cputimes = p.get_cpu_times()
meminfo = p.get_memory_info()
io = p.get_io_counters() io = p.get_io_counters()
self.info_html = """ self.info_html = """
<?xml version="1.0" encoding="UTF-8"?> <html><head>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<head>
<style> <style>
body {font: normal 12px/150%% Arial, Helvetica, sans-serif;} body {font: normal 12px/150%% Arial, Helvetica, sans-serif;}
.grid table { border-collapse: collapse; text-align: left; width: 100%%; } .grid table { border-collapse: collapse; text-align: left; width: 100%%; }
...@@ -305,34 +303,39 @@ class PyrasiteWindow(Gtk.Window): ...@@ -305,34 +303,39 @@ class PyrasiteWindow(Gtk.Window):
.grid table tbody .alt td { background: #E1EEf4; color: #00557F; } .grid table tbody .alt td { background: #E1EEf4; color: #00557F; }
.grid table tbody td:first-child { border: none; } .grid table tbody td:first-child { border: none; }
</style> </style>
</head> </head>
<body> <body>
<h2>%(title)s</h2> <h2>%(title)s</h2>
<ul> <div class="grid">
Sparkline: <table>
<span id="cpu_graph" class="cpu_graph">Loading</span> <thead>
<br/> <tr><th>CPU: <span id="cpu_details"/></th>
<li><b>CPU:</b> %(cpu)0.2f%% (%(cpu_user)s user, %(cpu_sys)s system)</li> <th>Memory: <span id="mem_details"/></th></tr>
<li><b>Memory:</b> %(mem)0.2f%% (%(mem_rss)s RSS, %(mem_vms)s VMS)</li> </thead>
<li><b>Read count:</b> %(read_count)s</li> <tbody>
<li><b>Read bytes:</b> %(read_bytes)s</li> <tr><td><span id="cpu_graph" class="cpu_graph"></span></td>
<li><b>Write count:</b> %(write_count)s</li> <td><span id="mem_graph" class="mem_graph"></span></td></tr>
<li><b>Write bytes:</b> %(write_bytes)s</li> </tbody>
</ul> </table>
""" % dict( </div>
title = proc.title, """ % dict(title = proc.title)
cpu = p.get_cpu_percent(interval=1.0),
cpu_user = cputimes.user, self.info_html += """
cpu_sys = cputimes.system, <h3>I/O Counters</h3>
mem = p.get_memory_percent(), <div class="grid">
mem_rss = humanize_bytes(meminfo.rss), <table>
mem_vms = humanize_bytes(meminfo.vms), <thead><tr><th></th><th>Count</th><th>Size</th></tr></thead>
read_count = io.read_count, <tbody>
read_bytes = humanize_bytes(io.read_bytes), <tr><td>Read</td><td><span id="read_count">%s</span></td>
write_count = io.write_count, <td><span id="read_size">%s</span></td></tr>
write_bytes = humanize_bytes(io.write_bytes), <tr><td>Write</td><td><span id="write_count">%s</span></td>
) <td><span id="write_size">%s</span></td></tr>
</tbody>
</table>
</div>
""" % (io.read_count, humanize_bytes(io.read_bytes),
io.write_count, humanize_bytes(io.write_bytes))
open_files = p.get_open_files() open_files = p.get_open_files()
if open_files: if open_files:
...@@ -415,24 +418,40 @@ class PyrasiteWindow(Gtk.Window): ...@@ -415,24 +418,40 @@ class PyrasiteWindow(Gtk.Window):
self.details_view.load_string(self.details_html, "text/html", "utf-8", '#') self.details_view.load_string(self.details_html, "text/html", "utf-8", '#')
def test(): global cpu_intervals, mem_intervals, cpu_details, mem_details
# Inject jQuery cpu_intervals = [p.get_cpu_percent(interval=1.0)]
jquery = file('jquery-1.7.1.min.js') mem_intervals = [p.get_memory_info().rss]
self.info_view.execute_script(jquery.read())
jquery.close()
# Inject Sparkline if not self.resource_thread:
sparkline = file('jquery.sparkline.min.js') self.resource_thread = ResourceUsagePoller(proc.pid)
self.info_view.execute_script(sparkline.read()) #self.resource_thread.process = p
sparkline.close() self.resource_thread.daemon = True
self.resource_thread.info_view = self.info_view
self.resource_thread.start()
self.resource_thread.process = p
def poll_resource_usage():
self.info_view.execute_script(""" self.info_view.execute_script("""
jQuery(document).ready(function() { jQuery('#cpu_graph').sparkline(%s, {'height': 50});
jQuery('#cpu_graph').sparkline([10,8,3,7,4,4,1]); jQuery('#mem_graph').sparkline(%s, {'height': 50, lineColor: '#f00',
}); fillColor: '#ffa', minSpotColor: false, maxSpotColor: false,
""") spotColor: '#77f', spotRadius: 3});
jQuery('#cpu_details').text('%s');
GObject.timeout_add(500, test) jQuery('#mem_details').text('%s');
""" % (cpu_intervals, mem_intervals, cpu_details, mem_details))
return True
GObject.timeout_add(500, self.inject_js)
GObject.timeout_add(3500, poll_resource_usage)
def inject_js(self):
log.debug("Injecting jQuery")
jquery = file('jquery-1.7.1.min.js')
self.info_view.execute_script(jquery.read())
jquery.close()
sparkline = file('jquery.sparkline.min.js')
self.info_view.execute_script(sparkline.read())
sparkline.close()
def update_progress(self, fraction, text=None): def update_progress(self, fraction, text=None):
if text: if text:
...@@ -640,54 +659,57 @@ class PyrasiteWindow(Gtk.Window): ...@@ -640,54 +659,57 @@ class PyrasiteWindow(Gtk.Window):
end_iter.set_line(erow-1) end_iter.set_line(erow-1)
end_iter.set_line_offset(ecol) end_iter.set_line_offset(ecol)
for x in tokenize.generate_tokens(InputStream(data).readline): try:
# x has 5-tuples for x in tokenize.generate_tokens(InputStream(data).readline):
tok_type, tok_str = x[0], x[1] # x has 5-tuples
srow, scol = x[2] tok_type, tok_str = x[0], x[1]
erow, ecol = x[3] srow, scol = x[2]
erow, ecol = x[3]
if tok_type == tokenize.COMMENT:
prepare_iters()
self.source_buffer.apply_tag_by_name('comment', start_iter, end_iter)
elif tok_type == tokenize.NAME:
if tok_str in keyword.kwlist or tok_str in builtin_constants:
prepare_iters()
self.source_buffer.apply_tag_by_name('keyword', start_iter, end_iter)
if tok_str == 'def' or tok_str == 'class': if tok_type == tokenize.COMMENT:
# Next token is going to be a function/method/class name
is_func = True
continue
elif tok_str == 'self':
prepare_iters() prepare_iters()
self.source_buffer.apply_tag_by_name('italic', start_iter, end_iter) self.source_buffer.apply_tag_by_name('comment', start_iter, end_iter)
else: elif tok_type == tokenize.NAME:
if is_func is True: if tok_str in keyword.kwlist or tok_str in builtin_constants:
prepare_iters() prepare_iters()
self.source_buffer.apply_tag_by_name('bold', start_iter, end_iter) self.source_buffer.apply_tag_by_name('keyword', start_iter, end_iter)
elif is_decorator is True:
if tok_str == 'def' or tok_str == 'class':
# Next token is going to be a function/method/class name
is_func = True
continue
elif tok_str == 'self':
prepare_iters() prepare_iters()
self.source_buffer.apply_tag_by_name('decorator', start_iter, end_iter) self.source_buffer.apply_tag_by_name('italic', start_iter, end_iter)
elif tok_type == tokenize.STRING: else:
prepare_iters() if is_func is True:
self.source_buffer.apply_tag_by_name('string', start_iter, end_iter) prepare_iters()
elif tok_type == tokenize.NUMBER: self.source_buffer.apply_tag_by_name('bold', start_iter, end_iter)
prepare_iters() elif is_decorator is True:
self.source_buffer.apply_tag_by_name('number', start_iter, end_iter) prepare_iters()
elif tok_type == tokenize.OP: self.source_buffer.apply_tag_by_name('decorator', start_iter, end_iter)
if tok_str == '@': elif tok_type == tokenize.STRING:
prepare_iters() prepare_iters()
self.source_buffer.apply_tag_by_name('decorator', start_iter, end_iter) self.source_buffer.apply_tag_by_name('string', start_iter, end_iter)
elif tok_type == tokenize.NUMBER:
prepare_iters()
self.source_buffer.apply_tag_by_name('number', start_iter, end_iter)
elif tok_type == tokenize.OP:
if tok_str == '@':
prepare_iters()
self.source_buffer.apply_tag_by_name('decorator', start_iter, end_iter)
# next token is going to be the decorator name # next token is going to be the decorator name
is_decorator = True is_decorator = True
continue continue
if is_func is True: if is_func is True:
is_func = False is_func = False
if is_decorator is True: if is_decorator is True:
is_decorator = False is_decorator = False
except Exception, e:
log.exception(str(e))
def close(self): def close(self):
self.progress.show() self.progress.show()
...@@ -720,8 +742,43 @@ class InputStream(object): ...@@ -720,8 +742,43 @@ class InputStream(object):
return line return line
class ResourceUsagePoller(threading.Thread):
"""A thread for polling a processes CPU & memory usage"""
process = None
def __init__(self, pid):
super(ResourceUsagePoller, self).__init__()
self.process = psutil.Process(pid)
def run(self):
global cpu_intervals, mem_intervals, cpu_details, mem_details
global read_count, read_bytes, write_count, write_bytes
while True:
if self.process:
if len(cpu_intervals) >= 100:
cpu_intervals = cpu_intervals[1:100]
mem_intervals = mem_intervals[1:100]
cpu_intervals.append(
self.process.get_cpu_percent(interval=POLL_INTERVAL))
mem_intervals.append(self.process.get_memory_info().rss)
cputimes = self.process.get_cpu_times()
cpu_details = '%0.2f%% (%s user, %s system)' % (
cpu_intervals[-1], cputimes.user, cputimes.system)
meminfo = self.process.get_memory_info()
mem_details = '%0.2f%% (%s RSS, %s VMS)' % (
self.process.get_memory_percent(),
humanize_bytes(meminfo.rss),
humanize_bytes(cputimes.system))
io = self.process.get_io_counters()
read_count = io.read_count
read_bytes = humanize_bytes(io.read_bytes)
write_count = io.write_count
write_bytes = humanize_bytes(io.write_bytes)
def main(): def main():
mainloop = GLib.MainLoop() mainloop = GLib.MainLoop()
GObject.threads_init()
window = PyrasiteWindow() window = PyrasiteWindow()
window.show() window.show()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment