summaryrefslogtreecommitdiff
path: root/rare/interrupts-graph.py
diff options
context:
space:
mode:
Diffstat (limited to 'rare/interrupts-graph.py')
-rwxr-xr-xrare/interrupts-graph.py223
1 files changed, 223 insertions, 0 deletions
diff --git a/rare/interrupts-graph.py b/rare/interrupts-graph.py
new file mode 100755
index 0000000..88167c2
--- /dev/null
+++ b/rare/interrupts-graph.py
@@ -0,0 +1,223 @@
+#!/usr/bin/env python
+# Plot the changes in /proc/interrupts over time
+# Date: 2014-06-17
+# Author: Peter Wu <peter@lekensteyn.nl>
+
+# Wishlist:
+# Thicker legend lines
+# Nicier smoothing
+# split rendering thread from IO thread (otherwise the data is retrieved too
+# late when dragging the legend box)
+
+import matplotlib.pyplot as plt
+import matplotlib
+from collections import deque, OrderedDict
+import numpy as np
+from scipy.interpolate import spline, interp1d
+
+# Maximimum number of time units to keep
+XSCALE = 60
+# Delay between updating the graph
+INTERVAL = .5
+# Log scale base or 0 to disable logarithmic y scaling
+LOG_SCALE_BASE = 10
+# Whether to enable smooth curves or not
+SMOOTH_CURVES = True
+
+MARKER_DEFAULT = 'o'
+MARKER_SELECTED = 'v'
+
+def is_line_ok(name, yvalues):
+ """Returns True if a line should be displayed for this name."""
+ if max(yvalues) < 5:
+ return False
+
+ names_ok = ['hci', 'timer']
+ for name_ok_part in names_ok:
+ if name_ok_part in name:
+ return True
+
+ # Accept all
+ return True
+
+# Fix Unicode font
+matplotlib.rc('font', family='DejaVu Sans')
+
+def get_numbers():
+ # TODO: may break the graph if a line disappears
+ with open('/proc/interrupts') as pi:
+ ncpus = len(pi.readline().split())
+ for line in pi:
+ name, values = line.split(':', 1)
+ name = name.strip()
+ values = values.strip().split(None, ncpus)
+ if len(values) >= ncpus:
+ # Name is ID + description for uniqueness
+ name += ':' + values[-1];
+ yield name, sum(int(values[i]) for i in range(0, ncpus))
+
+prev = OrderedDict()
+def get_diffs():
+ for name, n in get_numbers():
+ if name in prev:
+ yield name, n - prev[name]
+ else:
+ yield name, 0
+ prev[name] = n
+
+plt.ylabel(u'\u0394interrupts')
+plt.xlabel(u'\u0394time (sec)')
+plt.grid('on')
+#plt.ion() # Not necessary if show() does not block.
+plt.show(block=False)
+plt.xlim(0, XSCALE)
+if LOG_SCALE_BASE > 0:
+ plt.yscale('log', nonposy='clip', basey=LOG_SCALE_BASE)
+
+# Used when picking a new line or in update()
+update_legend = False
+### BEGIN EVENTS
+
+# After pressing ^W, stop the main loop
+running = True
+def on_close(event):
+ global running
+ running = False
+plt.connect('close_event', on_close)
+
+# Space toggles updating
+paused = False
+def on_keypress(event):
+ global paused
+ if event.key == ' ':
+ paused = not paused
+ update_title()
+plt.connect('key_press_event', on_keypress)
+
+last_selected = None
+def select_line(line):
+ global last_selected
+ line = lines[line.get_label()]
+ if last_selected:
+ last_selected.set_marker(MARKER_DEFAULT)
+ if last_selected == line:
+ last_selected = None
+ else:
+ line.set_marker(MARKER_SELECTED)
+ last_selected = line
+
+def on_pick(event):
+ global update_legend
+ artist = event.artist
+ if isinstance(artist, matplotlib.lines.Line2D):
+ select_line(artist)
+ update_legend = True
+ do_draw()
+plt.connect('pick_event', on_pick)
+
+### END EVENTS
+
+names = [name for name, _ in get_diffs()]
+yvalues = {}
+# Create yvalues, save them and generate plot args
+for name in names:
+ ydata = deque([0] * XSCALE, XSCALE)
+ yvalues[name] = ydata
+lines = {}
+
+def update_title():
+ title = 'Figure'
+ if paused:
+ title += ' (paused - press Space to resume)'
+ plt.gcf().canvas.set_window_title(title)
+
+smooth = {}
+def update():
+ """Reads new data and updates the line values."""
+ global update_legend
+ # Update data
+ for name, n in get_diffs():
+ ys = yvalues[name]
+ ys.append(n)
+ # Consider only strictly positive values
+ ydata = [y for y in ys if y > 0]
+ xdata = [i for i, y in enumerate(ys) if y > 0]
+
+ if ydata and is_line_ok(name, ys):
+ # Data is significant, show it
+ if name in lines:
+ lines[name].set_data(xdata, ydata)
+ else:
+ lines[name], = plt.plot(xdata, ydata, MARKER_DEFAULT, label=name)
+ lines[name].set_picker(5) # Make selectable
+ update_legend = True
+
+ # Smooth curve
+ ydata_len = len(ydata)
+ if ydata_len > 3 and SMOOTH_CURVES:
+ min_x = min(xdata)
+ max_x = max(xdata)
+ xnew = np.linspace(min_x, max_x, (1 + max_x - min_x) * 8)
+ #ynew = spline(xdata, ydata, xnew)
+ # quadratic and cubic splines give too much deviations
+ ynew = interp1d(xdata, ydata, kind='slinear')(xnew)
+ if not name in smooth:
+ smooth[name], = plt.plot(xnew, ynew, color=lines[name].get_color())
+ # Smooth line is shown, hide straight lines
+ lines[name].set_linestyle('')
+ else:
+ smooth[name].set_data(xnew, ynew)
+ elif name in smooth:
+ smooth[name].remove()
+ del smooth[name]
+ # No smooth line is shown, fallback to straight lines
+ lines[name].set_linestyle('-')
+ elif name in lines:
+ # Data is insignificant, remove previous line
+ lines[name].remove()
+ del lines[name]
+ update_legend = True
+
+ largest = 10
+ for name in yvalues:
+ ydata = yvalues[name]
+ if is_line_ok(name, ydata):
+ largest = max(largest, max(ydata))
+ # Update iff graph becomes too large
+ #ymin, ymax = plt.ylim()
+ #if ymax - ymin < largest:
+ # plt.ylim(ymin, ymin + largest)
+ plt.ylim(0, largest)
+
+def do_draw():
+ """Actually draw the graph, updating the legend if necessary."""
+ global update_legend
+ # update legend if a line gets added, changed or removed
+ if update_legend:
+ old_legend = plt.axes().get_legend()
+ if old_legend:
+ # Undocumented API, use it to remember legend position
+ old_loc = old_legend._get_loc()
+
+ legend = plt.legend(loc='upper left',
+ framealpha=.5,
+ fontsize='small')
+ legend.draggable()
+ if old_legend:
+ legend._set_loc(old_loc)
+
+ # Enable selecting a line by clicking in the legend
+ for line in legend.get_lines():
+ line.set_picker(5)
+
+ update_legend = False
+
+ plt.draw()
+
+update_title()
+
+while running:
+ if not paused:
+ update()
+ do_draw()
+ plt.pause(INTERVAL)