aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xnet_monitor.py481
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()