improve the graph code

This commit is contained in:
Damien Churchill 2008-10-03 13:49:45 +00:00
parent 9b47465052
commit e075f46d6a
5 changed files with 265 additions and 182 deletions

View File

@ -1,6 +1,7 @@
#
# core.py
#
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) Marcos Pinto 2007 <markybob@gmail.com>
#
@ -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"

View File

@ -1,4 +1,37 @@
#graph.py
#
# graph.py
#
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) Marcos Pinto 2007 <markybob@gmail.com>
#
# 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

View File

@ -0,0 +1,8 @@
<html>
<head>
<meta http-equiv="refresh" content="2" />
</head>
<body>
<img src="output_async.png" />
</body>
</html>

View File

@ -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()

View File

@ -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):