From e075f46d6adb6d03bcb88ec4a5fa9042ca03cd72 Mon Sep 17 00:00:00 2001 From: Damien Churchill Date: Fri, 3 Oct 2008 13:49:45 +0000 Subject: [PATCH] improve the graph code --- deluge/plugins/graph/graph/core.py | 49 ++-- deluge/plugins/graph/graph/graph.py | 332 ++++++++++++++++----------- deluge/plugins/graph/graph/test.html | 8 + deluge/plugins/graph/graph/test.py | 40 ++-- deluge/plugins/graph/graph/webui.py | 18 +- 5 files changed, 265 insertions(+), 182 deletions(-) create mode 100644 deluge/plugins/graph/graph/test.html diff --git a/deluge/plugins/graph/graph/core.py b/deluge/plugins/graph/graph/core.py index 397d68725..5eb3a10e3 100644 --- a/deluge/plugins/graph/graph/core.py +++ b/deluge/plugins/graph/graph/core.py @@ -1,6 +1,7 @@ # # core.py # +# Copyright (C) 2008 Damien Churchill # Copyright (C) 2008 Martijn Voncken # Copyright (C) Marcos Pinto 2007 # @@ -35,6 +36,7 @@ import deluge from deluge.log import LOG as log from deluge.plugins.corepluginbase import CorePluginBase from deluge import component +from deluge import configmanager import gobject #from deluge.plugins.coreclient import client #1.1 and later only #client: see http://dev.deluge-torrent.org/wiki/Development/UiClient#Remoteapi @@ -52,35 +54,44 @@ class Core(CorePluginBase): def enable(self): self.core = component.get("Core") - self.savedUpSpeeds = [] - self.savedDownSpeeds = [] - self.savedConnections = [] - self.config = deluge.configmanager.ConfigManager("graph.conf", DEFAULT_PREFS) - self.update_timer = gobject.timeout_add(self.config.get("update_interval"), self.update_stats) + self.saved_stats = {} + self.add_stats( + 'upload_rate', + 'download_rate', + 'num_connections' + ) + + self.config = configmanager.ConfigManager("graph.conf", DEFAULT_PREFS) + self.update_timer = gobject.timeout_add( + self.config.get("update_interval"), self.update_stats) self.length = self.config.get("length") + + def add_stats(self, *stats): + for stat in stats: + if stat not in self.saved_stats: + self.saved_stats[stat] = [] def disable(self): gobject.source_remove(self.update_timer) def update_stats(self): try: - status = self.core.session.status() - self.savedUpSpeeds.insert(0, int(status.payload_upload_rate)) - if len(self.savedUpSpeeds) > self.length: - self.savedUpSpeeds.pop() - self.savedDownSpeeds.insert(0, int(status.payload_download_rate)) - if len(self.savedDownSpeeds) > self.length: - self.savedDownSpeeds.pop() + stats = self.core.export_get_stats() + for stat, stat_list in self.saved_stats.iteritems(): + stat_list.insert(0, int(stats[stat])) + if len(stat_list) > self.length: + stat_list.pop() except Exception,e: log.error(e.message) - return True - - def export_get_upload(self,length=None): - return self.savedUpSpeeds - - def export_get_download(self,length=None): - return self.savedDownSpeeds + + def export_get_stats(self, keys): + stats_dict = {} + for stat in self.saved_stats: + if stat not in keys: + continue + stats_dict[stat] = self.saved_stats[stat] + return stats_dict def export_set_config(self, config): "sets the config dictionary" diff --git a/deluge/plugins/graph/graph/graph.py b/deluge/plugins/graph/graph/graph.py index 4e7be8f0b..9363d25ef 100644 --- a/deluge/plugins/graph/graph/graph.py +++ b/deluge/plugins/graph/graph/graph.py @@ -1,4 +1,37 @@ -#graph.py +# +# graph.py +# +# Copyright (C) 2008 Damien Churchill +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) Marcos Pinto 2007 +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception + """ port of old plugin by markybob. """ @@ -7,177 +40,202 @@ import math from deluge.log import LOG as log import deluge.common import time +from datetime import datetime from deluge.ui.client import aclient -class NetworkGraph: - def __init__(self): +black = (0, 0, 0) +gray = (0.75, 0.75, 0.75) +white = (1.0, 1.0, 1.0) +darkred = (0.65, 0, 0) +red = (1.0, 0, 0) +green = (0, 1.0, 0) +blue = (0, 0, 1.0) +orange = (1.0, 0.74, 0) +def default_formatter(value): + return str(value) + +def change_opacity(color, opactiy): + """A method to assist in changing the opactiy of a color inorder to draw the + fills. + """ + color = list(color) + if len(color) == 4: + color[3] = opactiy + else: + color.append(opactiy) + return tuple(color) + +class Graph: + def __init__(self): self.width = 100 self.height = 100 - self.length = 150 - - self.savedUpSpeeds = [] - self.savedDownSpeeds = [] - - self.download_line = True - self.download_fill = True - self.upload_line = True - self.upload_fill = True - self.download_line_color = (0, 0.75,0, 1.0) - self.download_fill_color = (0.6 ,1.1 , 0.6, 1.0) - self.upload_line_color = (0, 0, 1.0, 0.75) - self.upload_fill_color = (0.43,0.43,1.1, 0.5) + self.stat_info = {} + self.line_size = 2 self.mean_selected = True self.legend_selected = True self.max_selected = True - self.line_size = 4 self.black = (0, 0 , 0,) - self.interval = 2000 #2secs - self.text_bg = (255, 255 , 255, 128) #prototyping. - + self.interval = 2000 # 2 secs + self.text_bg = (255, 255 , 255, 128) # prototyping + self.set_left_axis() + + def set_left_axis(self, **kargs): + self.left_axis = kargs + + def add_stat(self, stat, label='', axis='left', line=True, fill=True, color=None): + self.stat_info[stat] = { + 'axis': axis, + 'label': label, + 'line': line, + 'fill': fill, + 'color': color + } + def async_request(self): - """ - convenience method, see test.py - """ - aclient.graph_get_upload(self.set_upload) - aclient.graph_get_download(self.set_download) + aclient.graph_get_stats(self.set_stats, self.stat_info.keys()) aclient.graph_get_config(self.set_config) - #async callbacks: + def set_stats(self, stats): + self.stats = stats + def set_config(self, config): - self.length = config["stats_length"] + self.length = config["length"] self.interval = config["update_interval"] - - def set_upload(self , upload): - self.savedUpSpeeds = upload - - def set_download(self , download): - self.savedDownSpeeds = download - - + def draw(self, width, height): - self.width = width + self.width = width self.height = height - - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width,self.height) + + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height) self.ctx = cairo.Context(self.surface) - - if (self.download_fill or self.download_line) and (self.upload_fill or self.upload_line): - maxSpeed = max(max(self.savedDownSpeeds),max(self.savedUpSpeeds)) - meanSpeed = max(sum(self.savedUpSpeeds) /len(self.savedUpSpeeds), sum(self.savedDownSpeeds)/len(self.savedDownSpeeds)) - elif self.download_fill or self.download_line: - maxSpeed = max(self.savedDownSpeeds) - meanSpeed = sum(self.savedDownSpeeds)/len(self.savedDownSpeeds) - elif self.upload_fill or self.upload_line: - maxSpeed = max(self.savedUpSpeeds) - meanSpeed = sum(self.savedUpSpeeds) /len(self.savedUpSpeeds) - else: - maxSpeed = 0 - - - if maxSpeed > 0: - - if self.max_selected: - self.drawText(deluge.common.fspeed(maxSpeed),4,2) - - if self.download_fill: - self.drawSpeedPoly(self.savedDownSpeeds,self.download_fill_color, maxSpeed, True) - - if self.download_line: - self.drawSpeedPoly(self.savedDownSpeeds,self.download_line_color,maxSpeed, False) - - if self.upload_fill: - self.drawSpeedPoly(self.savedUpSpeeds,self.upload_fill_color,maxSpeed, True) - - if self.upload_line: - self.drawSpeedPoly(self.savedUpSpeeds,self.upload_line_color,maxSpeed, False) - - - if self.mean_selected: - mean = int(self.height - 1 - ((self.height-28)*meanSpeed/maxSpeed)) - - self.drawLine(self.black, 0,mean, self.width, mean) - self.drawText(deluge.common.fspeed(meanSpeed), 4, mean - 12 - 2) + self.draw_rect(white, 0, 0, self.width, self.height) + self.draw_x_axis() + self.draw_left_axis() if self.legend_selected: - self.drawLegend() + self.draw_legend() return self.surface + + def draw_x_axis(self): + now = time.time() + duration = self.length * (self.interval / 1000.0) + start = now - duration + ratio = (self.width - 40) / duration + seconds_to_minute = 60 - time.localtime(start)[5] + + for i in xrange(0, 5): + text = time.strftime('%H:%M', time.localtime(start + seconds_to_minute + (60*i))) + x = int(ratio * (seconds_to_minute + (60*i))) + self.draw_text(text, x + 46, self.height - 20) + x = x + 59.5 + self.draw_dotted_line(gray, x, 20, x, self.height - 20) + + y = self.height - 22.5 + self.draw_dotted_line(gray, 60, y, int(self.width), y) + + def draw_left_axis(self): + stats = {} + max_values = [] + for stat in self.stat_info: + if self.stat_info[stat]['axis'] == 'left': + stats[stat] = self.stat_info[stat] + stats[stat]['values'] = self.stats[stat] + stats[stat]['fill_color'] = change_opacity(stats[stat]['color'], 0.5) + stats[stat]['color'] = change_opacity(stats[stat]['color'], 0.8) + stats[stat]['max_value'] = max(self.stats[stat]) + max_values.append(stats[stat]['max_value']) + if len(max_values) > 1: + max_value = max(*max_values) + else: + max_value = max_values[0] + + if max_value < self.left_axis['min']: + max_value = self.left_axis['min'] + + height = self.height - self.line_size - 22 + high = float(max_value) + ratio = height / high + + for i in xrange(1, 6): + y = int(ratio * ((high / 5) * i)) - 0.5 + if i < 5: + self.draw_dotted_line(gray, 60, y, self.width, y) + text = self.left_axis['formatter']((high / 5) * (5 - i)) + self.draw_text(text, 0, y - 6) + self.draw_dotted_line(gray, 60.5, 20, 60.5, self.height - 20) + + for stat, info in stats.iteritems(): + self.draw_value_poly(info['values'], info['color'], max_value) + self.draw_value_poly(info['values'], info['fill_color'], max_value, info['fill']) + + def draw_legend(self): + pass + + def trace_path(self, values, max_value): + height = self.height - 24 + width = self.width + line_width = self.line_size + + self.ctx.set_line_width(line_width) + self.ctx.move_to(width, height) + self.ctx.line_to(width, + int(height - ((height - 28) * values[0] / max_value))) - def tracePath(self, speeds, maxSpeed): - lineWidth = self.line_size - - self.ctx.set_line_width(lineWidth) - self.ctx.move_to(self.width + lineWidth,self.height + lineWidth) - self.ctx.line_to(self.width + lineWidth,int(self.height-((self.height-28)*speeds[0]/maxSpeed))) - - for i in range(len(speeds)): - self.ctx.line_to(int(self.width-1-((i*self.width)/(self.length-1))),int(self.height-1-((self.height-28)*speeds[i]/maxSpeed))) - - self.ctx.line_to(int(self.width-1-(((len(speeds)-1)*self.width)/(self.length-1))),int(self.height)-1 + lineWidth) + x = width + step = (width - 60) / float(self.length) + for i, value in enumerate(values): + if i == self.length - 1: + x = 62 + self.ctx.line_to(x, + int(height - 1 - ((height - 28) * value / max_value)) + ) + x -= step + + self.ctx.line_to( + int(width + 62 - (((len(values) - 1) * width) / (self.length - 1))), + height) self.ctx.close_path() - - def drawSpeedPoly(self, speeds, color, maxSpeed, fill): - self.tracePath(speeds, maxSpeed) - self.ctx.set_source_rgba(color[0],color[1],color[2], color[3]) - + + def draw_value_poly(self, values, color, max_value, fill=False): + self.trace_path(values, max_value) + self.ctx.set_source_rgba(*color) + if fill: self.ctx.fill() else: self.ctx.stroke() - - def drawLegend(self): - showDown = self.download_fill or self.download_line - showUp = self.upload_fill or self.upload_line - downBox_X = self.width-113 - - self.drawText("Download:", self.width-180,3) - self.drawText("Upload:", self.width-80,3) - - if self.download_fill and self.download_line: - self.drawRect(self.download_line_color,downBox_X,5,12,12) - self.drawRect(self.download_fill_color,downBox_X+12,5,12,12) - elif self.download_fill: - self.drawRect(self.download_fill_color,downBox_X,5,24,12) - elif self.download_line: - self.drawRect(self.download_line_color,downBox_X,5,24,12) - - if self.upload_fill and self.upload_line: - self.drawRect(self.upload_line_color,self.width-30,5,12,12) - self.drawRect(self.upload_fill_color,self.width-18,5,12,12) - elif self.upload_fill: - self.drawRect(self.upload_fill_color,self.width-30,5,24,12) - elif self.upload_line: - self.drawRect(self.upload_line_color,self.width-30,5,24,12) - - if True: - txt_end = time.strftime("%H:%M:%S") - txt_start = time.strftime("%H:%M:%S",time.localtime(time.time() - self.length * (self.interval / 1000.0) )) - self.length * self.interval - self.drawText(txt_end, self.width-60,self.height - 20) - self.drawText(txt_start, 4 ,self.height - 20) - - - def drawText(self,text,x,y): - self.ctx.set_font_size(12) - self.ctx.move_to(x, y +12) + + def draw_text(self, text, x, y): + self.ctx.set_font_size(9) + self.ctx.move_to(x, y + 9) self.ctx.set_source_rgba(*self.black) self.ctx.show_text(text) - - def drawRect(self,color,x,y,height,width): - self.ctx.set_source_rgba(color[0],color[1],color[2],color[3],) - self.ctx.rectangle(x,y,height,width) + + def draw_rect(self, color, x, y, height, width): + self.ctx.set_source_rgba(*color) + self.ctx.rectangle(x, y, height, width) self.ctx.fill() - - def drawLine(self,color,x1,y1,x2,y2): + + def draw_line(self, color, x1, y1, x2, y2): self.ctx.set_source_rgba(*color) self.ctx.set_line_width(1) self.ctx.move_to(x1, y1) self.ctx.line_to(x2, y2) self.ctx.stroke() + + def draw_dotted_line(self, color, x1, y1, x2, y2): + self.ctx.set_source_rgba(*color) + self.ctx.set_line_width(1) + self.ctx.move_to(x1, y1) + self.ctx.line_to(x2, y2) + #self.ctx.stroke_preserve() + #self.ctx.set_source_rgba(*white) + #self.ctx.set_dash((1, 1), 4) + self.ctx.stroke() + #self.ctx.set_dash((1, 1), 0) if __name__ == "__main__": - import test - - + import test \ No newline at end of file diff --git a/deluge/plugins/graph/graph/test.html b/deluge/plugins/graph/graph/test.html new file mode 100644 index 000000000..9550451af --- /dev/null +++ b/deluge/plugins/graph/graph/test.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/deluge/plugins/graph/graph/test.py b/deluge/plugins/graph/graph/test.py index 460479ef9..332587a87 100644 --- a/deluge/plugins/graph/graph/test.py +++ b/deluge/plugins/graph/graph/test.py @@ -1,16 +1,16 @@ from deluge.ui.client import sclient, aclient sclient.set_core_uri() -from graph import NetworkGraph - +import graph +import deluge def test_sync(): - if 0: + if 1: upload = sclient.graph_get_upload() download = sclient.graph_get_download() print upload print download else: - upload = [66804, 66915, 66974, 67447, 67540, 67318, 67320, 67249, 66659, 66489, 67027, 66914, 66802, 67303, 67654, 67643, 67763, 67528, 67523, 67431, 67214, 66939, 67316, 67020, 66881, 67103, 67377, 67141, 67366, 67492, 67375, 67203, 67056, 67010, 67029, 66741, 66695, 66868, 66805, 66264, 66249, 66317, 66459, 66306, 66681, 66954, 66662, 66278, 65921, 65695, 65681, 65942, 66000, 66140, 66424, 66480, 66257, 66271, 66145, 65854, 65568, 65268, 65112, 65050, 65027, 64676, 64655, 64178, 64386, 63979, 63271, 62746, 62337, 62297, 62496, 62902, 63801, 64121, 62957, 62921, 63051, 62644, 63240, 64107, 63968, 63987, 63644, 63263, 63153, 62999, 62843, 62777, 63101, 63078, 63178, 63, 63, 6, 62, 625, 62254, 61485, 61264, 60937, 60568, 61011, 61109, 60325, 60196, 59640, 59619, 59514, 60813, 60572, 61632, 61689, 63365, 64583, 66396, 67179, 68209, 68295, 67674, 67559, 67195, 66178, 65632, 66124, 66456, 66676, 67183, 67620, 66960, 66347, 65925, 65907, 65896, 66738, 66703, 67060, 67004, 67007, 66329, 65304, 52002, 38969, 25433, 12426, 0, 0] + upload = [66804, 66915, 66974, 67447, 67540, 67318, 67320, 67249, 66659, 66489, 67027, 66914, 66802, 67303, 67654, 67643, 67763, 67528, 67523, 67431, 67214, 66939, 67316, 67020, 66881, 67103, 67377, 67141, 67366, 67492, 67375, 67203, 67056, 67010, 67029, 66741, 66695, 66868, 66805, 66264, 66249, 66317, 66459, 66306, 66681, 66954, 66662, 66278, 65921, 65695, 65681, 65942, 66000, 66140, 66424, 66480, 66257, 66271, 66145, 65854, 65568, 65268, 65112, 65050, 65027, 64676, 64655, 64178, 64386, 63979, 63271, 62746, 62337, 62297, 62496, 62902, 63801, 64121, 62957, 62921, 63051, 62644, 63240, 64107, 63968, 63987, 63644, 63263, 63153, 62999, 62843, 62777, 63101, 63078, 63178, 63126, 63401, 62630, 62451, 62505, 62254, 61485, 61264, 60937, 60568, 61011, 61109, 60325, 60196, 59640, 59619, 59514, 60813, 60572, 61632, 61689, 63365, 64583, 66396, 67179, 68209, 68295, 67674, 67559, 67195, 66178, 65632, 66124, 66456, 66676, 67183, 67620, 66960, 66347, 65925, 65907, 65896, 66738, 66703, 67060, 67004, 67007, 66329, 65304, 52002, 38969, 25433, 12426, 0, 0] download = [42926, 43853, 43157, 45470, 44254, 46272, 45083, 47344, 46716, 51963, 50112, 52334, 55525, 57545, 53691, 51637, 49574, 49836, 48295, 49843, 52878, 56014, 56966, 56938, 60065, 60461, 56542, 59526, 58678, 54424, 51862, 55109, 52132, 53783, 51687, 56567, 52182, 50758, 46714, 50511, 48161, 50920, 48694, 50528, 55074, 55420, 55882, 59268, 59958, 57938, 57115, 51424, 51180, 53184, 52879, 51177, 54417, 51097, 47901, 49870, 55865, 61118, 61476, 63498, 58878, 49630, 45975, 45632, 45892, 44855, 49495, 48304, 45829, 42152, 39403, 37574, 32384, 34933, 34901, 33492, 31953, 36271, 33826, 34515, 36408, 41106, 43054, 44110, 40810, 41383, 37267, 35881, 38660, 37525, 34857, 36718, 36842, 34281, 39528, 41854, 42952, 40021, 41722, 41045, 42917, 39287, 38672, 32824, 28765, 22686, 18490, 15714, 15268, 14793, 15305, 16354, 16720, 17502, 17857, 16622, 18447, 19929, 31138, 36965, 36158, 32795, 30445, 21997, 18100, 22491, 27227, 29317, 32436, 35700, 39140, 36258, 33697, 24751, 20354, 8211, 3836, 1560, 834, 2034, 1744, 1637, 1637, 1637, 0, 0] from graph import NetworkGraph @@ -22,10 +22,13 @@ def test_sync(): n.surface.write_to_png('output_sync.png') def test_async(): - n = NetworkGraph() - n.async_request() + g = graph.Graph() + g.add_stat('download_rate', color=graph.green) + g.add_stat('upload_rate', color=graph.blue) + g.set_left_axis(formatter=deluge.common.fspeed, min=10240) + g.async_request() aclient.force_call(True) - surface = n.draw(600,300) + surface = g.draw(600, 300) surface.write_to_png('output_async.png') def test_write(): @@ -37,12 +40,15 @@ def test_write(): self.data = [] def write(self, str): self.data.append(str) - - n = NetworkGraph() - n.async_request() + + g = graph.Graph() + g.add_stat('download_rate', color=graph.green) + g.add_stat('upload_rate', color=graph.blue) + g.set_left_axis(formatter=deluge.common.fspeed, min=10240) + g.async_request() aclient.force_call(True) - surface = n.draw(900,150) - + surface = g.draw(900, 150) + file_like = fake_file() surface.write_to_png(file_like) data = "".join(file_like.data) @@ -51,10 +57,6 @@ def test_write(): f.write(data) f.close() - -test_sync() -#test_async() -#test_write() - - - +#test_sync() +test_async() +test_write() \ No newline at end of file diff --git a/deluge/plugins/graph/graph/webui.py b/deluge/plugins/graph/graph/webui.py index e1b97fbbb..01a8553ee 100644 --- a/deluge/plugins/graph/graph/webui.py +++ b/deluge/plugins/graph/graph/webui.py @@ -35,11 +35,12 @@ # this exception statement from your version. If you delete this exception import os +from deluge.common import fspeed from deluge.log import LOG as log from deluge.ui.client import sclient, aclient from deluge.plugins.webuipluginbase import WebUIPluginBase from deluge import component -from graph import NetworkGraph +import graph api = component.get("WebPluginApi") forms = api.forms @@ -53,13 +54,16 @@ class graph_page: class network_png: @api.deco.check_session def GET(self, args): + self.data = '' vars = api.web.input(width = 600, height = 150) api.web.header("Content-Type", "image/png") - n = NetworkGraph() - n.async_request() + g = graph.Graph() + g.add_stat('download_rate', color=graph.green) + g.add_stat('upload_rate', color=graph.blue) + g.set_left_axis(formatter=fspeed, min=10240) + g.async_request() aclient.force_call(True) - self.data = "" - surface = n.draw(int(vars.width), int(vars.height)) + surface = g.draw(int(vars.width), int(vars.height)) surface.write_to_png(self) print self.data @@ -78,8 +82,8 @@ class WebUI(WebUIPluginBase): api.menu_manager.register_admin_page("graph", _("Graph"), "/graph") #<--top menu def disable(self): - api.config_page_manager.deregister('graph') - api.menu_manager.deregister_admin_page("graph") #<--top menu + api.config_page_manager.deregister('graph2') + api.menu_manager.deregister_admin_page("graph2") #<--top menu class ConfigForm(forms.Form):