Commit 35f88d24 authored by Luke Macken's avatar Luke Macken

Merge branch 'feature/sparklines' into develop

parents d45d67a0 d50799b6
...@@ -28,7 +28,9 @@ import psutil ...@@ -28,7 +28,9 @@ import psutil
import logging import logging
import keyword import keyword
import tokenize import tokenize
import threading
from os.path import join, abspath, dirname
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
...@@ -37,6 +39,19 @@ from pyrasite.utils import setup_logger, run, humanize_bytes ...@@ -37,6 +39,19 @@ from pyrasite.utils import setup_logger, run, humanize_bytes
log = logging.getLogger('pyrasite') log = logging.getLogger('pyrasite')
socket_families = dict([(getattr(socket, k), k) for k in dir(socket)
if k.startswith('AF_')])
socket_types = dict([(getattr(socket, k), k) for k in dir(socket)
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):
""" """
...@@ -77,6 +92,7 @@ class PyrasiteWindow(Gtk.Window): ...@@ -77,6 +92,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)
...@@ -270,12 +286,10 @@ class PyrasiteWindow(Gtk.Window): ...@@ -270,12 +286,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 = """
<html><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%%; }
...@@ -290,29 +304,39 @@ class PyrasiteWindow(Gtk.Window): ...@@ -290,29 +304,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>
<body>
<h2>%(title)s</h2> <h2>%(title)s</h2>
<ul> <div class="grid">
<li><b>CPU:</b> %(cpu)0.2f%% (%(cpu_user)s user, %(cpu_sys)s system)</li> <table>
<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> <tr><th>CPU: <span id="cpu_details"/></th>
<li><b>Read bytes:</b> %(read_bytes)s</li> <th>Memory: <span id="mem_details"/></th></tr>
<li><b>Write count:</b> %(write_count)s</li> </thead>
<li><b>Write bytes:</b> %(write_bytes)s</li> <tbody>
</ul> <tr><td><span id="cpu_graph" class="cpu_graph"></span></td>
""" % dict( <td><span id="mem_graph" class="mem_graph"></span></td></tr>
title = proc.title, </tbody>
cpu = p.get_cpu_percent(interval=1.0), </table>
cpu_user = cputimes.user, </div>
cpu_sys = cputimes.system, """ % dict(title = proc.title)
mem = p.get_memory_percent(),
mem_rss = humanize_bytes(meminfo.rss), self.info_html += """
mem_vms = humanize_bytes(meminfo.vms), <h3>I/O Counters</h3>
read_count = io.read_count, <div class="grid">
read_bytes = humanize_bytes(io.read_bytes), <table>
write_count = io.write_count, <thead><tr><th></th><th>Count</th><th>Size</th></tr></thead>
write_bytes = humanize_bytes(io.write_bytes), <tbody>
) <tr><td>Read</td><td><span id="read_count">%s</span></td>
<td><span id="read_size">%s</span></td></tr>
<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:
...@@ -339,16 +363,12 @@ class PyrasiteWindow(Gtk.Window): ...@@ -339,16 +363,12 @@ class PyrasiteWindow(Gtk.Window):
<th>Local</th><th>Remote</th><th>Status</th></tr></thead> <th>Local</th><th>Remote</th><th>Status</th></tr></thead>
<tbody> <tbody>
""" """
families = dict([(getattr(socket, k), k) for k in dir(socket)
if k.startswith('AF_')])
types = dict([(getattr(socket, k), k) for k in dir(socket)
if k.startswith('SOCK_')])
for i, c in enumerate(conns): for i, c in enumerate(conns):
self.info_html += """ self.info_html += """
<tr%s><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td> <tr%s><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td>
<td>%s</td></tr> <td>%s</td></tr>
""" % (i % 2 and ' class="alt"' or '', c.fd, """ % (i % 2 and ' class="alt"' or '', c.fd,
families[c.family], types[c.type], socket_families[c.family], socket_types[c.type],
':'.join(map(str, c.local_address)), ':'.join(map(str, c.local_address)),
':'.join(map(str, c.remote_address)), ':'.join(map(str, c.remote_address)),
c.status) c.status)
...@@ -372,6 +392,8 @@ class PyrasiteWindow(Gtk.Window): ...@@ -372,6 +392,8 @@ class PyrasiteWindow(Gtk.Window):
thread.user_time, thread.system_time) thread.user_time, thread.system_time)
self.info_html += "</tbody></table></div>" self.info_html += "</tbody></table></div>"
self.info_html += "</body></html>"
self.info_view.load_string(self.info_html, "text/html", "utf-8", '#') self.info_view.load_string(self.info_html, "text/html", "utf-8", '#')
# The Details tab # The Details tab
...@@ -397,6 +419,42 @@ class PyrasiteWindow(Gtk.Window): ...@@ -397,6 +419,42 @@ 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", '#')
global cpu_intervals, mem_intervals, cpu_details, mem_details
cpu_intervals = [p.get_cpu_percent(interval=1.0)]
mem_intervals = [p.get_memory_info().rss]
if not self.resource_thread:
self.resource_thread = ResourceUsagePoller(proc.pid)
#self.resource_thread.process = p
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("""
jQuery('#cpu_graph').sparkline(%s, {'height': 50});
jQuery('#mem_graph').sparkline(%s, {'height': 50, lineColor: '#f00',
fillColor: '#ffa', minSpotColor: false, maxSpotColor: false,
spotColor: '#77f', spotRadius: 3});
jQuery('#cpu_details').text('%s');
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")
js = join(dirname(abspath(__file__)), 'js')
jquery = file(join(js, 'jquery-1.7.1.min.js'))
self.info_view.execute_script(jquery.read())
jquery.close()
sparkline = file(join(js, '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:
self.progress.set_text(text + '...') self.progress.set_text(text + '...')
...@@ -603,6 +661,7 @@ class PyrasiteWindow(Gtk.Window): ...@@ -603,6 +661,7 @@ 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)
try:
for x in tokenize.generate_tokens(InputStream(data).readline): for x in tokenize.generate_tokens(InputStream(data).readline):
# x has 5-tuples # x has 5-tuples
tok_type, tok_str = x[0], x[1] tok_type, tok_str = x[0], x[1]
...@@ -651,6 +710,8 @@ class PyrasiteWindow(Gtk.Window): ...@@ -651,6 +710,8 @@ class PyrasiteWindow(Gtk.Window):
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()
...@@ -683,8 +744,43 @@ class InputStream(object): ...@@ -683,8 +744,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()
......
This diff is collapsed.
This diff is collapsed.
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