From eaf53188ae0c8c4311052d3c68a73c7b03f2be9c Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Tue, 17 Jun 2014 22:38:17 +0200 Subject: rare/interrupt-graph.py: monitor /proc/interrupts Experiment to visualize updates to /proc/interrupts. "Rarely" used, hence a subfolder "rare". --- rare/interrupts-graph.py | 223 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100755 rare/interrupts-graph.py 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 + +# 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) -- cgit v1.2.1