diff options
-rwxr-xr-x | net_monitor.py | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/net_monitor.py b/net_monitor.py new file mode 100755 index 0000000..c1f699f --- /dev/null +++ b/net_monitor.py @@ -0,0 +1,481 @@ +#!/usr/bin/python + +import gobject +import gtk +import pango + +import gc +import os +from stat import * +import datetime +import getopt +import sys +import traceback + +from threading import Thread +from Queue import Queue +import time + +import textwrap + +# localization +import gettext +try: + gettext.install("msec") +except IOError: + _ = str + +ifaces = {} +HISTOGRAM_SIZE=50 + +def readnet(): + """Reads values from /proc/net/dev""" + net = {} + data = open("/proc/net/dev").readlines()[2:] + for l in data: + dev, vals = l.split(":") + dev = dev.strip() + vals = vals.split() + net[dev] = vals + return net + +def get_traffic(iface): + net = readnet() + if iface in net: + bytes_in = int(net[iface][0]) + bytes_out = int(net[iface][8]) + else: + bytes_in = 0 + bytes_out = 0 + return bytes_in, bytes_out + +def format_size(size): + """Pretty-Formats size""" + return size + +# borrowed from gnome-network-monitor 0.9.1 (gnetworkmonitor.sourceforge.net) +class LoadGraph: + """ + This class is able do display a nicely formatted graph if interface + bandwidth load. + """ + # we don't want to allow the window to shrink below this height + min_height = 70 + padding = { "left" : 50, "right" : 10, "top" : 10, "bottom" : 10 } + colors = ( "bg", "bg_outer", "in", "out" ) + + def __init__(self, widget, hist, size): + """ + widget => GtkDrawingArea we paint into + hist => a list of integers containing the hist of incoming traffic + width, height => initial size of the GtkDrawingArea widget + """ + # set the minimum height of the widget + widget.set_size_request(-1, 70) + + # the object holding the history and its size + self.__hist = hist + self.__size = size + + # strings holding 0, middle and max values for displayed graph + self.__str_min = "0" + self.__str_mid = "" # gets computed later + self.__str_max = "" # gets computer later + + # size of the GtkDrawingArea + self.__rect = self.__inner = gtk.gdk.Rectangle() + self.maxval = 0 # maximum value in the history + self.__mesh_x = self.__mesh_y = 0 # distance in pixels between items + self.__get_max() + self.__on_size(widget.get_allocation()) + + # save reference to the widget we paint into + self.__widget = widget + + # lists holding bandwidth history mapped to actual coordinates + self.__in = list() + self.__out = list() + + self.__colors = dict() + self.set_color(bg=(0, 0, 0), fg_in=(255, 0, 0), fg_out=(0, 255, 0)) + self.__context = None + + def __set_context_color(self, con, col_tuple): + """ Cleaner might be to extend the context class, but who cares? """ + con.set_source_rgb(col_tuple[0], col_tuple[1], col_tuple[2]) + + def __draw(self): + """ Strokes the rectangles and draws the curves """ + if (self.__context == None): + return + # stroke the outer rectangle + self.__context.rectangle(0, 0, self.__rect.width, self.__rect.height) + self.__set_context_color(self.__context, self.__colors["bg"]) + self.__context.fill_preserve() + self.__context.stroke() + + # stroke the inner rectangle + self.__context.rectangle(self.__inner.x, + self.__inner.y, + self.__inner.width, + self.__inner.height) + self.__set_context_color(self.__context, self.__colors["bg"]) + self.__context.fill_preserve() + self.__context.stroke() + + # stroke the quad around + self.__context.move_to(self.__inner.x, self.__inner.y + self.__inner.height) + self.__context.line_to(self.__inner.x, self.__inner.y) + self.__context.line_to(self.__inner.x + self.__inner.width - self.__mesh_x, self.__inner.y) + self.__context.line_to(self.__inner.x + self.__inner.width - self.__mesh_x, self.__inner.y + self.__inner.height) + self.__set_context_color(self.__context, (255, 255, 255)) + self.__context.stroke() + + # draw the actual bandwidth curves + self.__draw_bw(self.__in, self.__colors["fg_in"]) + self.__draw_bw(self.__out, self.__colors["fg_out"]) + + # draw minimum, middle and max numbers + self.__draw_num(self.__inner.height + self.__inner.y, self.__str_min, (255, 255, 255)) + self.__draw_num(self.__rect.height/2, self.__str_mid, (255, 255, 255)) + self.__draw_num(self.__inner.y, self.__str_max, (255, 255, 255)) + + def __draw_num(self, ypos, num, color): + """ + The leftmost column is used to draw info about maximum, minimum + and average bw + """ + self.__context.move_to(5, ypos) + self.__context.show_text(num) + self.__set_context_color(self.__context, color) + self.__context.stroke() + + def __draw_bw(self, bw_list, color): + """ Draws a curve from points stored in bw_list in color """ + self.__context.move_to(self.__inner.x, self.__inner.y + self.__inner.height) + self.__set_context_color(self.__context, color) + + x = self.__inner.x + self.__mesh_x + for i in bw_list[1:]: + self.__context.line_to(x, i) + x += self.__mesh_x + self.__context.stroke() + + def __convert_one_hist(self, hist): + """ + Maps values from one history object to real coordinates of the + drawing area + """ + converted = list() + + if self.__mesh_y == 0: + return [self.__inner.height + self.__inner.y] * len(hist) + + for item in hist: + if item <= 100: item = 0 # treshold to get rid of really small peaks + converted.append((self.__inner.height - int(item / self.__mesh_y)) + self.__inner.y) + return converted + + def __convert_points(self): + """ + The bandwidth history object has the bandwidth stored as bytes. This method + converts the bytes into actual coordiantes of the rectangle displayed + """ + # compute the aspect ratio + self.__mesh_x = float(self.__inner.width) / float(self.__size) + self.__mesh_y = float(self.maxval) / float(self.__inner.height) + + self.__in = self.__convert_one_hist(self.__hist["in"]) + self.__out = self.__convert_one_hist(self.__hist["out"]) + + def __get_max(self): + """ Finds the maximum value in both incoming and outgoing queue """ + if self.__hist["in"]: + maxin = max(self.__hist["in"]) + else: + maxin = 0 + if self.__hist["out"]: + maxout = max(self.__hist["out"]) + else: + maxout = 0 + self.maxval = max(maxin, maxout) + + def __text_size(self): + """ Computes the size of the text and thus the left border """ + val = self.maxval + if val == 0 and self.maxval != 0: + val = self.maxval + + self.__str_max = "%d %s" % (val, _("KB")) + self.__str_mid = "%d %s" % (val/2, _("KB")) + LoadGraph.padding["left"] = self.__context.text_extents(self.__str_max)[2] + 10 + + def __on_size(self, rect): + """ rect => a rectangle holding the size of the widget """ + self.__rect = rect + + self.__inner.x = LoadGraph.padding["left"] + self.__inner.y = LoadGraph.padding["top"] + self.__inner.width = rect.width - LoadGraph.padding["right"] - self.__inner.x + self.__inner.height = rect.height - LoadGraph.padding["bottom"] - self.__inner.y + + self.__convert_points() + + def on_expose(self, widget, event): + """ A signal handler that is called every time we need to redraw + the widget """ + self.__context = widget.window.cairo_create() + + self.__get_max() + self.__text_size() + self.__on_size(widget.get_allocation()) + + self.__draw() + + return False + + def set_history(self, hist): + """ Called typically on change of interface displayed """ + self.__hist = hist + self.__convert_points() + + def update(self): + """ Redraws the area """ + alloc = self.__widget.get_allocation() + self.__widget.queue_draw_area(0, 0, alloc.width, alloc.height) + + def change_colors(self, col_in = None, col_out = None): + """ Sets the colors to draw the curves with """ + if ( col_in ) : self.__colors["fg_in"] = col_in + if ( col_out ) : self.__colors["fg_out"] = col_out + + def set_color(self, *args, **kwargs): + """ Sets the colors of the graph """ + for key, value in kwargs.items(): + self.__colors[key] = value + +class Monitor: + def __init__(self): + self.window = gtk.Window() + self.window.set_title(_("Network monitor")) + self.window.set_default_size(640, 440) + self.window.connect('delete-event', lambda *w: gtk.main_quit()) + + self.main_vbox = gtk.VBox() + self.window.add(self.main_vbox) + + # notebook + self.notebook = gtk.Notebook() + self.main_vbox.pack_start(self.notebook) + #self.notebook.connect('switch-page', self.show_net_status) + + self.ifaces = readnet() + self.enabled_ifaces = [] + + for iface in self.ifaces: + data_in, data_out = get_traffic(iface) + self.ifaces[iface] = {'data_in': 0, + 'data_out': 0, + 'total_in': 0, + 'total_out': 0, + 'widget_in': None, + 'widget_out': None, + 'widget_speed_in': None, + 'widget_speed_out': None, + 'graph': None, + 'histogram': [], + } + iface_stat = self.build_iface_stat(iface) + self.notebook.append_page(iface_stat, gtk.Label(iface)) + if self.check_network_accounting(iface): + self.enabled_ifaces.append(iface) + + # configure timer + gobject.timeout_add(1000, self.update) + + self.window.show_all() + + def update(self, interval=1): + """Updates traffic counters (interval is in seconds)""" + for iface in self.ifaces: + old_data_in = self.ifaces[iface]['data_in'] + old_data_out = self.ifaces[iface]['data_out'] + total_in = self.ifaces[iface]['total_in'] + total_out = self.ifaces[iface]['total_out'] + data_in, data_out = get_traffic(iface) + # is it the first measure? + if old_data_in == 0 and old_data_out == 0: + old_data_in = data_in + old_data_out = data_out + # check total download + diff_in = data_in - old_data_in + diff_out = data_out - old_data_out + # checking for 32bits overflow + if diff_in < 0: + diff_in += 2**32 + if diff_out < 0: + diff_out += 2**32 + total_in += diff_in + total_out += diff_out + # speed + speed_in = diff_in / interval + speed_out = diff_out / interval + # update saved values + self.ifaces[iface]['data_in'] = data_in + self.ifaces[iface]['data_out'] = data_out + self.ifaces[iface]['total_in'] = total_in + self.ifaces[iface]['total_out'] = total_out + # update widgets + for widget, value in [('widget_in', total_in), + ('widget_out', total_out), + ('widget_speed_in', speed_in), + ('widget_speed_out', speed_out)]: + if widget in self.ifaces[iface]: + self.ifaces[iface][widget].set_text(str(value)) + else: + print "%s not found in %s" % (widget, iface) + # updating graph + hist_in = self.ifaces[iface]['histogram']['in'] + hist_in.append(speed_in) + if len(hist_in) > HISTOGRAM_SIZE: + del hist_in[0] + hist_out = self.ifaces[iface]['histogram']['out'] + hist_out.append(speed_out) + if len(hist_out) > HISTOGRAM_SIZE: + del hist_out[0] + graph = self.ifaces[iface]['graph'] + graph.update() + gobject.timeout_add(interval * 1000, self.update) + + def check_network_accounting(self, iface): + """Checks if network accounting was enabled on interface""" + try: + os.stat("/var/lib/vnstat/%s" % iface) + return True + except: + return False + + def build_iface_stat(self, iface): + """Builds graphical view for interface""" + iface_hbox = gtk.HBox() + # statistics vbox + stats_vbox = gtk.VBox() + iface_hbox.add(stats_vbox) + if self.check_network_accounting(iface): + # graph + graph_vnstat = gtk.Image() + pixbuf = self.load_graph_from_vnstat(iface, type="daily") + graph_vnstat.set_from_pixbuf(pixbuf) + stats_vbox.pack_start(graph_vnstat) + # buttons + frame = gtk.Frame(_("Show interface statistics")) + stats_vbox.add(frame) + vbox = gtk.VBox() + frame.add(vbox) + # summary + button = gtk.RadioButton(None, _("Summary")) + button.connect('toggled', self.update_stat_iface, (iface, graph_vnstat, "summary")) + vbox.pack_start(button, False, False) + # summary + button = gtk.RadioButton(button, _("Hourly traffic")) + button.connect('toggled', self.update_stat_iface, (iface, graph_vnstat, "hourly")) + vbox.pack_start(button, False, False) + # summary + button = gtk.RadioButton(button, _("Daily traffic")) + button.connect('toggled', self.update_stat_iface, (iface, graph_vnstat, "daily")) + vbox.pack_start(button, False, False) + # summary + button = gtk.RadioButton(button, _("Monthly traffic")) + button.connect('toggled', self.update_stat_iface, (iface, graph_vnstat, "monthly")) + vbox.pack_start(button, False, False) + # summary + button = gtk.RadioButton(button, _("Top 10 traffic days")) + button.connect('toggled', self.update_stat_iface, (iface, graph_vnstat, "top")) + vbox.pack_start(button, False, False) + else: + label = gtk.Label(_("Network accounting was not enabled on interface %s.\nPlease enable network accounting on the interface in order to view traffic statistics.")) + stats_vbox.add(label) + # instant data vbox + traf_vbox = gtk.VBox() + iface_hbox.add(traf_vbox) + frame = gtk.Frame(_("Interface statistics")) + traf_vbox.pack_start(frame) + vbox = gtk.VBox() + frame.add(vbox) + # configuring callbacks + sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + # interface + iface_h, iface_p = self.build_value_pair(sizegroup, _("Network interface:"), iface) + vbox.pack_start(iface_h, False, False) + iface_s, iface_status = self.build_value_pair(sizegroup, _("Device status:"), _("Up")) + vbox.pack_start(iface_s, False, False) + + # traffic + total_in_h, total_in = self.build_value_pair(sizegroup, _("Received data:")) + self.ifaces[iface]["widget_in"] = total_in + vbox.pack_start(total_in_h, False, False) + total_out_h, total_out = self.build_value_pair(sizegroup, _("Sent data:")) + self.ifaces[iface]["widget_out"] = total_out + vbox.pack_start(total_out_h, False, False) + speed_in_h, speed_in = self.build_value_pair(sizegroup, _("Download speed:")) + self.ifaces[iface]["widget_speed_in"] = speed_in + vbox.pack_start(speed_in_h, False, False) + speed_out_h, speed_out = self.build_value_pair(sizegroup, _("Upload speed:")) + self.ifaces[iface]["widget_speed_out"] = speed_out + vbox.pack_start(speed_out_h, False, False) + + # graph + draw = gtk.DrawingArea() + vbox.pack_start(draw) + histogram = {"in": [], "out": []} + graph = LoadGraph(draw, histogram, HISTOGRAM_SIZE) + draw.connect('expose_event', graph.on_expose) + self.ifaces[iface]['graph'] = graph + self.ifaces[iface]['histogram'] = histogram + + return iface_hbox + + def build_value_pair(self, sizegroup, text, value_text=None): + """Builds a value pair""" + hbox = gtk.HBox() + name = gtk.Label(text) + hbox.pack_start(name) + value = gtk.Label(value_text) + hbox.pack_start(value) + sizegroup.add_widget(name) + return hbox, value + + def update_stat_iface(self, widget, data): + """Updates graphic statistics""" + iface, graph, type = data + pixbuf = self.load_graph_from_vnstat(iface, type) + graph.set_from_pixbuf(pixbuf) + + def load_graph_from_vnstat(self, iface, type="hourly"): + """Loads graph from vnstat. Right now uses vnstati to do all the dirty job""" + # load image from data + if type == "hourly": + param="-h" + elif type == "monthly": + param="-m" + elif type == "daily": + param="-d" + elif type == "top": + param="-t" + elif type == "summary": + param="-s" + else: + # show summary if parameter is unknown + print "Unknown parameter %s, showing summary.." % type + param="-s" + data = os.popen("vnstati %s -o - -i %s" % (param, iface)).read() + loader = gtk.gdk.PixbufLoader() + loader.write(data) + loader.close() + pixbuf = loader.get_pixbuf() + return pixbuf + +if __name__ == "__main__": + monitor = Monitor() + gtk.main() |