Stats plugin update
This commit is contained in:
parent
3397c2487b
commit
16fbf27b90
|
@ -36,5 +36,4 @@ import pkg_resources
|
|||
import os.path
|
||||
|
||||
def get_resource(filename):
|
||||
return pkg_resources.resource_filename("deluge.plugins.stats",
|
||||
os.path.join("data", filename))
|
||||
return pkg_resources.resource_filename("deluge.plugins.stats", os.path.join("data", filename))
|
||||
|
|
|
@ -22,18 +22,7 @@
|
|||
# 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
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
# 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
|
||||
|
@ -44,21 +33,19 @@
|
|||
# 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
|
||||
|
||||
import logging
|
||||
from twisted.internet.task import LoopingCall
|
||||
import time
|
||||
|
||||
import deluge
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
from deluge import component
|
||||
from deluge import configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"test": "NiNiNi",
|
||||
"update_interval": 2, #2 seconds.
|
||||
"update_interval": 1, #2 seconds.
|
||||
"length": 150, # 2 seconds * 150 --> 5 minutes.
|
||||
}
|
||||
|
||||
|
@ -70,24 +57,55 @@ DEFAULT_TOTALS = {
|
|||
"stats": {}
|
||||
}
|
||||
|
||||
def get_key(config, key):
|
||||
try:
|
||||
return config[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def mean(items):
|
||||
try:
|
||||
return sum(items)/ len(items)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
class Core(CorePluginBase):
|
||||
totals = {} #class var to catch only updating this once per session in enable.
|
||||
|
||||
def enable(self):
|
||||
log.debug("Stats plugin enabled")
|
||||
self.core = component.get("Core")
|
||||
self.stats ={}
|
||||
self.count = {}
|
||||
self.intervals = [1, 5, 30, 300]
|
||||
|
||||
self.last_update = {}
|
||||
t = time.time()
|
||||
for i in self.intervals:
|
||||
self.stats[i] = {}
|
||||
self.last_update[i] = t
|
||||
self.count[i] = 0
|
||||
|
||||
|
||||
self.config = configmanager.ConfigManager("stats.conf", DEFAULT_PREFS)
|
||||
self.saved_stats = configmanager.ConfigManager("stats.totals", DEFAULT_TOTALS)
|
||||
if self.totals == {}:
|
||||
self.totals.update(self.saved_stats.config)
|
||||
|
||||
self.stats = self.saved_stats["stats"] or {}
|
||||
self.length = self.config["length"]
|
||||
|
||||
#self.stats = get_key(self.saved_stats, "stats") or {}
|
||||
self.stats_keys = []
|
||||
self.add_stats(
|
||||
'upload_rate',
|
||||
'download_rate',
|
||||
'num_connections',
|
||||
'dht_nodes',
|
||||
'dht_cache_nodes',
|
||||
'dht_torrents',
|
||||
'num_peers',
|
||||
)
|
||||
|
||||
self.stats_keys = [
|
||||
"payload_download_rate",
|
||||
"payload_upload_rate"
|
||||
]
|
||||
self.update_stats()
|
||||
|
||||
self.update_timer = LoopingCall(self.update_stats)
|
||||
|
@ -104,40 +122,94 @@ class Core(CorePluginBase):
|
|||
except:
|
||||
pass
|
||||
|
||||
def add_stats(self, *stats):
|
||||
for stat in stats:
|
||||
if stat not in self.stats_keys:
|
||||
self.stats_keys.append(stat)
|
||||
for i in self.intervals:
|
||||
if stat not in self.stats[i]:
|
||||
self.stats[i][stat] = []
|
||||
|
||||
def update_stats(self):
|
||||
try:
|
||||
status = self.core.get_session_status(self.stats_keys)
|
||||
for key, value in status.items():
|
||||
if key not in self.stats:
|
||||
self.stats[key] = []
|
||||
self.stats[key].insert(0, value)
|
||||
#Get all possible stats!
|
||||
stats = {}
|
||||
for key in self.stats_keys:
|
||||
#try all keys we have, very inefficient but saves having to
|
||||
#work out where a key comes from...
|
||||
try:
|
||||
stats.update(self.core.get_session_status([key]))
|
||||
except AttributeError:
|
||||
pass
|
||||
stats["num_connections"] = self.core.get_num_connections()
|
||||
stats.update(self.core.get_config_values(["max_download",
|
||||
"max_upload",
|
||||
"max_num_connections"]))
|
||||
# status = self.core.session.status()
|
||||
# for stat in dir(status):
|
||||
# if not stat.startswith('_') and stat not in stats:
|
||||
# stats[stat] = getattr(status, stat, None)
|
||||
|
||||
for stat_list in self.stats.values():
|
||||
if len(stat_list) > self.config["length"]:
|
||||
update_time = time.time()
|
||||
self.last_update[1] = update_time
|
||||
|
||||
#extract the ones we are interested in
|
||||
#adding them to the 1s array
|
||||
for stat, stat_list in self.stats[1].iteritems():
|
||||
if stat in stats:
|
||||
stat_list.insert(0, int(stats[stat]))
|
||||
else:
|
||||
stat_list.insert(0, 0)
|
||||
if len(stat_list) > self.length:
|
||||
stat_list.pop()
|
||||
self.last_update = time.time()
|
||||
|
||||
def update_interval(interval, base, multiplier):
|
||||
self.count[interval] = self.count[interval] + 1
|
||||
if self.count[interval] >= interval:
|
||||
self.last_update[interval] = update_time
|
||||
self.count[interval] = 0
|
||||
current_stats = self.stats[interval]
|
||||
for stat, stat_list in self.stats[base].iteritems():
|
||||
try:
|
||||
avg = mean(stat_list[0:multiplier])
|
||||
except ValueError:
|
||||
avg = 0
|
||||
current_stats[stat].insert(0, avg)
|
||||
if len(current_stats[stat]) > self.length:
|
||||
current_stats[stat].pop()
|
||||
|
||||
update_interval(5, 1, 5)
|
||||
update_interval(30, 5, 6)
|
||||
update_interval(300, 30, 10)
|
||||
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.error("Stats update error %s" % e)
|
||||
return True
|
||||
|
||||
def save_stats(self):
|
||||
try:
|
||||
self.saved_stats["stats"] = self.stats
|
||||
self.saved_stats.config.update(self.get_totals())
|
||||
self.saved_stats.save()
|
||||
except Exception,e:
|
||||
log.exception(e)
|
||||
except Exception, e:
|
||||
log.error("Stats save error", e)
|
||||
return True
|
||||
|
||||
|
||||
# export:
|
||||
@export
|
||||
def get_stats(self, keys):
|
||||
def get_stats(self, keys, interval):
|
||||
if interval not in self.intervals:
|
||||
return None
|
||||
|
||||
stats_dict = {}
|
||||
for key in keys:
|
||||
if key in self.stats:
|
||||
stats_dict[key] = self.stats[key]
|
||||
stats_dict["_last_update"] = self.last_update
|
||||
if key in self.stats[interval]:
|
||||
stats_dict[key] = self.stats[interval][key]
|
||||
|
||||
stats_dict["_last_update"] = self.last_update[interval]
|
||||
stats_dict["_length"] = self.config["length"]
|
||||
stats_dict["_update_interval"] = interval
|
||||
return stats_dict
|
||||
|
||||
@export
|
||||
|
@ -169,3 +241,8 @@ class Core(CorePluginBase):
|
|||
def get_config(self):
|
||||
"returns the config dictionary"
|
||||
return self.config.config
|
||||
|
||||
@export
|
||||
def get_intervals(self):
|
||||
"Returns the available resolutions"
|
||||
return self.intervals
|
||||
|
|
|
@ -1,24 +1,263 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Fri Aug 8 23:34:44 2008 -->
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.16 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkHBox" id="prefs_box">
|
||||
<widget class="GtkVBox" id="prefs_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label1">
|
||||
<widget class="GtkFrame" id="frame1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Test config value:</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkEntry" id="txt_test">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<child>
|
||||
<widget class="GtkAlignment" id="alignment4">
|
||||
<property name="visible">True</property>
|
||||
<property name="left_padding">15</property>
|
||||
<child>
|
||||
<widget class="GtkTable" id="table2">
|
||||
<property name="visible">True</property>
|
||||
<property name="n_rows">10</property>
|
||||
<property name="n_columns">2</property>
|
||||
<property name="column_spacing">15</property>
|
||||
<child>
|
||||
<widget class="GtkColorButton" id="bandwidth_graph_download_rate_color">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="color">#000000000000</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="x_options">GTK_EXPAND</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Download color:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Upload color:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkColorButton" id="bandwidth_graph_upload_rate_color">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="color">#000000000000</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="x_options">GTK_EXPAND</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label8">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes"><b>Connections Graph</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label9">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes"><b>Bandwidth Graph</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkColorButton" id="connections_graph_dht_nodes_color">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="color">#000000000000</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="x_options">GTK_EXPAND</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label10">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">DHT nodes:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkColorButton" id="connections_graph_dht_cache_nodes_color">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="color">#000000000000</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="x_options">GTK_EXPAND</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label11">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Cached DHT nodes:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label12">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">DHT torrents:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="top_attach">6</property>
|
||||
<property name="bottom_attach">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label13">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Connections:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="top_attach">7</property>
|
||||
<property name="bottom_attach">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkColorButton" id="connections_graph_dht_torrents_color">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="color">#000000000000</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">6</property>
|
||||
<property name="bottom_attach">7</property>
|
||||
<property name="x_options">GTK_EXPAND</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkColorButton" id="connections_graph_num_connections_color">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="color">#000000000000</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">7</property>
|
||||
<property name="bottom_attach">8</property>
|
||||
<property name="x_options">GTK_EXPAND</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label16">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes"><b>Seeds / Peers</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">8</property>
|
||||
<property name="bottom_attach">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkColorButton" id="seeds_graph_num_peers_color">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="color">#000000000000</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">9</property>
|
||||
<property name="bottom_attach">10</property>
|
||||
<property name="x_options">GTK_EXPAND</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label17">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Peers:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="top_attach">9</property>
|
||||
<property name="bottom_attach">10</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes"><b>Graph Colors</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">label_item</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Mon Oct 13 20:17:39 2008 -->
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.6 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox1">
|
||||
|
@ -14,6 +14,9 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="stock">gtk-page-setup</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="graph_label_text">
|
||||
|
@ -25,71 +28,121 @@
|
|||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="graph_tab">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<child>
|
||||
<widget class="GtkNotebook" id="graph_notebook">
|
||||
<widget class="GtkViewport" id="viewport1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tab_pos">GTK_POS_LEFT</property>
|
||||
<property name="resize_mode">queue</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="bandwidth_graph">
|
||||
<widget class="GtkVBox" id="vbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Resolution</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkComboBox" id="combo_intervals">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkNotebook" id="graph_notebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tab_pos">left</property>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="bandwidth_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="bandwidth_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Bandwidth</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
<property name="type">tab</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="connections_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="connections_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Connections</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
<property name="type">tab</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="seeds_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="seeds_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Seeds/Peers</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
<property name="tab_fill">False</property>
|
||||
<property name="type">tab</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="bandwidth_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Bandwidth</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="connections_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="connections_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Connections</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="seeds_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="seeds_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Seeds/Peers</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">2</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#
|
||||
# graph.py
|
||||
#
|
||||
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
|
||||
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) Marcos Pinto 2007 <markybob@gmail.com>
|
||||
|
@ -21,7 +22,7 @@
|
|||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
# 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
|
||||
|
@ -32,23 +33,14 @@
|
|||
# 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
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
import time
|
||||
import math
|
||||
import cairo
|
||||
import logging
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
|
||||
black = (0, 0, 0)
|
||||
|
@ -60,13 +52,18 @@ green = (0, 1.0, 0)
|
|||
blue = (0, 0, 1.0)
|
||||
orange = (1.0, 0.74, 0)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def default_formatter(value):
|
||||
return str(value)
|
||||
|
||||
def size_formatter_scale(value):
|
||||
scale = 1.0
|
||||
for i in range(0,3):
|
||||
scale = scale * 1024.0
|
||||
if value / scale < 1024:
|
||||
return scale
|
||||
|
||||
def change_opacity(color, opactiy):
|
||||
"""A method to assist in changing the opacity of a color in order to draw the
|
||||
"""A method to assist in changing the opactiy of a color inorder to draw the
|
||||
fills.
|
||||
"""
|
||||
color = list(color)
|
||||
|
@ -83,6 +80,7 @@ class Graph:
|
|||
self.length = 150
|
||||
self.stat_info = {}
|
||||
self.line_size = 2
|
||||
self.dash_length = [10]
|
||||
self.mean_selected = True
|
||||
self.legend_selected = True
|
||||
self.max_selected = True
|
||||
|
@ -105,125 +103,198 @@ class Graph:
|
|||
|
||||
def set_stats(self, stats):
|
||||
self.last_update = stats["_last_update"]
|
||||
log.debug("Last update: %s" % self.last_update)
|
||||
del stats["_last_update"]
|
||||
self.length = stats["_length"]
|
||||
del stats["_length"]
|
||||
self.interval = stats["_update_interval"]
|
||||
del stats["_update_interval"]
|
||||
self.stats = stats
|
||||
return
|
||||
|
||||
def set_config(self, config):
|
||||
self.length = config["length"]
|
||||
self.interval = config["update_interval"]
|
||||
# def set_config(self, config):
|
||||
# self.length = config["length"]
|
||||
# self.interval = config["update_interval"]
|
||||
|
||||
def set_interval(self, interval):
|
||||
self.interval = interval
|
||||
|
||||
def draw_to_context(self, context, width, height):
|
||||
self.ctx = context
|
||||
self.width, self.height = width, height
|
||||
try:
|
||||
self.draw_rect(white, 0, 0, self.width, self.height)
|
||||
self.draw_x_axis()
|
||||
self.draw_left_axis()
|
||||
|
||||
if self.legend_selected:
|
||||
self.draw_legend()
|
||||
except cairo.Error, e:
|
||||
log.exception(e)
|
||||
self.draw_rect(white, 0, 0, self.width, self.height)
|
||||
self.draw_graph()
|
||||
return self.ctx
|
||||
|
||||
def draw(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
|
||||
ctx = cairo.Context(surface)
|
||||
self.draw_to_context(ctx, width, height)
|
||||
return surface
|
||||
|
||||
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
|
||||
self.ctx = cairo.Context(self.surface)
|
||||
self.draw_rect(white, 0, 0, self.width, self.height)
|
||||
self.draw_x_axis()
|
||||
self.draw_left_axis()
|
||||
|
||||
if self.legend_selected:
|
||||
self.draw_legend()
|
||||
return self.surface
|
||||
|
||||
def draw_x_axis(self):
|
||||
duration = float(self.length * self.interval)
|
||||
def draw_x_axis(self, bounds):
|
||||
(left, top, right, bottom) = bounds
|
||||
duration = self.length * self.interval
|
||||
start = self.last_update - duration
|
||||
ratio = (self.width - 40) / duration
|
||||
seconds_to_minute = 60 - time.localtime(start)[5]
|
||||
ratio = (right - left) / float(duration)
|
||||
|
||||
if duration < 1800 * 10:
|
||||
#try rounding to nearest 1min, 5mins, 10mins, 30mins
|
||||
for step in [60, 300, 600, 1800]:
|
||||
if duration / step < 10:
|
||||
x_step = step
|
||||
break
|
||||
else:
|
||||
# if there wasnt anything useful find a nice fitting hourly divisor
|
||||
x_step = ((duration / 5) /3600 )* 3600
|
||||
|
||||
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)
|
||||
#this doesnt allow for dst and timezones...
|
||||
seconds_to_step = math.ceil(start/float(x_step)) * x_step - start
|
||||
|
||||
y = self.height - 22.5
|
||||
self.draw_dotted_line(gray, 60, y, int(self.width), y)
|
||||
for i in xrange(0, duration/x_step + 1):
|
||||
text = time.strftime('%H:%M', time.localtime(start + seconds_to_step + i*x_step))
|
||||
# + 0.5 to allign x to nearest pixel
|
||||
x = int(ratio * (seconds_to_step + i*x_step) + left) + 0.5
|
||||
self.draw_x_text(text, x, bottom)
|
||||
self.draw_dotted_line(gray, x, top-0.5, x, bottom+0.5)
|
||||
|
||||
def draw_left_axis(self):
|
||||
self.draw_line(gray, left, bottom+0.5, right, bottom+0.5)
|
||||
|
||||
def draw_graph(self):
|
||||
font_extents = self.ctx.font_extents()
|
||||
x_axis_space = font_extents[2] + 2 + self.line_size / 2.0
|
||||
plot_height = self.height - x_axis_space
|
||||
#lets say we need 2n-1*font height pixels to plot the y ticks
|
||||
tick_limit = (plot_height / font_extents[3] )# / 2.0
|
||||
|
||||
max_value = 0
|
||||
for stat in self.stat_info:
|
||||
if self.stat_info[stat]['axis'] == 'left':
|
||||
try:
|
||||
l_max = max(self.stats[stat])
|
||||
except ValueError:
|
||||
l_max = 0
|
||||
if l_max > max_value:
|
||||
max_value = l_max
|
||||
if max_value < self.left_axis['min']:
|
||||
max_value = self.left_axis['min']
|
||||
|
||||
y_ticks = self.intervalise(max_value, tick_limit)
|
||||
max_value = y_ticks[-1]
|
||||
#find the width of the y_ticks
|
||||
y_tick_text = [self.left_axis['formatter'](tick) for tick in y_ticks]
|
||||
def space_required(text):
|
||||
te = self.ctx.text_extents(text)
|
||||
return math.ceil(te[4] - te[0])
|
||||
y_tick_width = max((space_required(text) for text in y_tick_text))
|
||||
|
||||
top = font_extents[2] / 2.0
|
||||
#bounds(left, top, right, bottom)
|
||||
bounds = (y_tick_width + 4, top + 2, self.width, self.height - x_axis_space)
|
||||
|
||||
self.draw_x_axis(bounds)
|
||||
self.draw_left_axis(bounds, y_ticks, y_tick_text)
|
||||
|
||||
def intervalise(self, x, limit=None):
|
||||
"""Given a value x create an array of tick points to got with the graph
|
||||
The number of ticks returned can be constrained by limit, minimum of 3
|
||||
"""
|
||||
#Limit is the number of ticks which is 1 + the number of steps as we
|
||||
#count the 0 tick in limit
|
||||
if limit is not None:
|
||||
if limit <3:
|
||||
limit = 2
|
||||
else:
|
||||
limit = limit -1
|
||||
scale = 1
|
||||
if 'formatter_scale' in self.left_axis:
|
||||
scale = self.left_axis['formatter_scale'](x)
|
||||
x = x / float(scale)
|
||||
|
||||
#Find the largest power of 10 less than x
|
||||
log = math.log10(x)
|
||||
intbit = math.floor(log)
|
||||
|
||||
interval = math.pow(10, intbit)
|
||||
steps = int(math.ceil(x / interval))
|
||||
|
||||
if steps <= 1 and (limit is None or limit >= 10*steps):
|
||||
interval = interval * 0.1
|
||||
steps = steps * 10
|
||||
elif steps <= 2 and (limit is None or limit >= 5*steps):
|
||||
interval = interval * 0.2
|
||||
steps = steps * 5
|
||||
elif steps <=5 and (limit is None or limit >= 2*steps):
|
||||
interval = interval * 0.5
|
||||
steps = steps * 2
|
||||
|
||||
if limit is not None and steps > limit:
|
||||
multi = steps / float(limit)
|
||||
if multi > 2:
|
||||
interval = interval * 5
|
||||
else:
|
||||
interval = interval * 2
|
||||
|
||||
intervals = [i * interval * scale for i in xrange(1+int(math.ceil(x/ interval)))]
|
||||
return intervals
|
||||
|
||||
def draw_left_axis(self, bounds, y_ticks, y_tick_text):
|
||||
(left, top, right, bottom) = bounds
|
||||
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
|
||||
#max_value = float(round(max_value, len(str(max_value)) * -1))
|
||||
max_value = float(max_value)
|
||||
height = bottom - top
|
||||
max_value = y_ticks[-1]
|
||||
ratio = height / max_value
|
||||
|
||||
for i in xrange(1, 6):
|
||||
y = int(ratio * ((max_value / 5) * i)) - 0.5
|
||||
if i < 5:
|
||||
self.draw_dotted_line(gray, 60, y, self.width, y)
|
||||
text = self.left_axis['formatter']((max_value / 5) * (5 - i))
|
||||
self.draw_text(text, 0, y - 6)
|
||||
self.draw_dotted_line(gray, 60.5, 20, 60.5, self.height - 20)
|
||||
for i, y_val in enumerate(y_ticks):
|
||||
y = int(bottom - y_val * ratio ) - 0.5
|
||||
if i != 0:
|
||||
self.draw_dotted_line(gray, left, y, right, y)
|
||||
self.draw_y_text(y_tick_text[i], left, y)
|
||||
self.draw_line(gray, left, top, left, bottom)
|
||||
|
||||
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'])
|
||||
if len(info['values']) > 0:
|
||||
self.draw_value_poly(info['values'], info['color'], max_value, bounds)
|
||||
self.draw_value_poly(info['values'], info['fill_color'], max_value, bounds, info['fill'])
|
||||
|
||||
def draw_legend(self):
|
||||
pass
|
||||
|
||||
def trace_path(self, values, max_value):
|
||||
height = self.height - 24
|
||||
width = self.width
|
||||
|
||||
def trace_path(self, values, max_value, bounds):
|
||||
(left, top, right, bottom) = bounds
|
||||
ratio = (bottom - top) / max_value
|
||||
line_width = self.line_size
|
||||
|
||||
self.ctx.set_line_width(line_width)
|
||||
self.ctx.move_to(width, height)
|
||||
self.ctx.move_to(right, bottom)
|
||||
|
||||
self.ctx.line_to(width,
|
||||
int(height - ((height - 28) * values[0] / max_value)))
|
||||
self.ctx.line_to(right, int(bottom - values[0] * ratio ))
|
||||
|
||||
x = width
|
||||
step = (width - 60) / float(self.length)
|
||||
x = right
|
||||
step = (right - left) / float(self.length -1)
|
||||
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 = left
|
||||
|
||||
self.ctx.line_to(x, int(bottom - value * ratio))
|
||||
x -= step
|
||||
|
||||
self.ctx.line_to(
|
||||
int(width + 62 - (((len(values) - 1) * width) / (self.length - 1))),
|
||||
height)
|
||||
int(right - (len(values) - 1) * step),
|
||||
bottom)
|
||||
self.ctx.close_path()
|
||||
|
||||
def draw_value_poly(self, values, color, max_value, fill=False):
|
||||
self.trace_path(values, max_value)
|
||||
|
||||
def draw_value_poly(self, values, color, max_value, bounds, fill=False):
|
||||
self.trace_path(values, max_value, bounds)
|
||||
self.ctx.set_source_rgba(*color)
|
||||
|
||||
if fill:
|
||||
|
@ -231,9 +302,26 @@ class Graph:
|
|||
else:
|
||||
self.ctx.stroke()
|
||||
|
||||
def draw_text(self, text, x, y):
|
||||
self.ctx.set_font_size(9)
|
||||
self.ctx.move_to(x, y + 9)
|
||||
def draw_x_text(self, text, x, y):
|
||||
"""Draws text below and horizontally centered about x,y"""
|
||||
fe = self.ctx.font_extents()
|
||||
te = self.ctx.text_extents(text)
|
||||
height = fe[2]
|
||||
x_bearing = te[0]
|
||||
width = te[2]
|
||||
self.ctx.move_to(int(x - width/2.0 + x_bearing), int(y + height))
|
||||
self.ctx.set_source_rgba(*self.black)
|
||||
self.ctx.show_text(text)
|
||||
|
||||
def draw_y_text(self, text, x, y):
|
||||
"""Draws text left of and vertically centered about x,y"""
|
||||
fe = self.ctx.font_extents()
|
||||
te = self.ctx.text_extents(text)
|
||||
descent = fe[1]
|
||||
ascent = fe[0]
|
||||
x_bearing = te[0]
|
||||
width = te[4]
|
||||
self.ctx.move_to(int(x - width - x_bearing - 2), int(y + (ascent - descent)/2.0))
|
||||
self.ctx.set_source_rgba(*self.black)
|
||||
self.ctx.show_text(text)
|
||||
|
||||
|
@ -252,13 +340,12 @@ class Graph:
|
|||
def draw_dotted_line(self, color, x1, y1, x2, y2):
|
||||
self.ctx.set_source_rgba(*color)
|
||||
self.ctx.set_line_width(1)
|
||||
dash, offset = self.ctx.get_dash()
|
||||
self.ctx.set_dash(self.dash_length, 0)
|
||||
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)
|
||||
self.ctx.set_dash(dash, offset)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import test
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#
|
||||
# gtkui.py
|
||||
#
|
||||
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
|
@ -23,18 +24,7 @@
|
|||
# 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
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
# 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
|
||||
|
@ -45,89 +35,211 @@
|
|||
# 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
|
||||
|
||||
import os
|
||||
import gtk
|
||||
import logging
|
||||
import gobject
|
||||
from gtk.glade import XML
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
# Relative imports
|
||||
from . import common
|
||||
from . import graph
|
||||
|
||||
import graph
|
||||
import deluge
|
||||
from deluge import component
|
||||
from deluge.log import LOG as log
|
||||
from deluge.common import fspeed
|
||||
from deluge.ui.client import client
|
||||
from deluge.ui.gtkui.torrentdetails import Tab
|
||||
from deluge.plugins.pluginbase import GtkPluginBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
import common
|
||||
|
||||
DEFAULT_CONF = { 'version': 1,
|
||||
'colors' :{
|
||||
'bandwidth_graph': {'upload_rate': str(gtk.gdk.Color("blue")),
|
||||
'download_rate': str(gtk.gdk.Color("green")),
|
||||
},
|
||||
'connections_graph': { 'dht_nodes': str(gtk.gdk.Color("orange")),
|
||||
'dht_cache_nodes': str(gtk.gdk.Color("blue")),
|
||||
'dht_torrents': str(gtk.gdk.Color("green")),
|
||||
'num_connections': str(gtk.gdk.Color("darkred")),
|
||||
},
|
||||
'seeds_graph': { 'num_peers': str(gtk.gdk.Color("blue")),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def neat_time(column, cell, model, iter):
|
||||
"""Render seconds as seconds or minutes with label"""
|
||||
seconds = model.get_value(iter, 0)
|
||||
if seconds >60:
|
||||
text = "%d %s" % (seconds / 60, _("minutes"))
|
||||
elif seconds == 60:
|
||||
text = _("1 minute")
|
||||
elif seconds == 1:
|
||||
text = _("1 second")
|
||||
else:
|
||||
text = "%d %s" % (seconds, _("seconds"))
|
||||
cell.set_property('text', text)
|
||||
return
|
||||
|
||||
def int_str(number):
|
||||
return (str(int(number)))
|
||||
|
||||
def gtk_to_graph_color(color):
|
||||
"""Turns a gtk.gdk.Color into a tuple with range 0-1 as used by the graph"""
|
||||
MAX = float(65535)
|
||||
gtk_color = gtk.gdk.Color(color)
|
||||
red = gtk_color.red / MAX
|
||||
green = gtk_color.green / MAX
|
||||
blue = gtk_color.blue / MAX
|
||||
return (red, green, blue)
|
||||
|
||||
|
||||
class GraphsTab(Tab):
|
||||
def __init__(self, glade):
|
||||
def __init__(self, glade, colors):
|
||||
Tab.__init__(self)
|
||||
self._name = 'Graphs'
|
||||
self.glade = glade
|
||||
self.window = self.glade.get_widget('graph_tab')
|
||||
self._child_widget = self.window
|
||||
self.notebook = self.glade.get_widget('graph_notebook')
|
||||
self.label = self.glade.get_widget('graph_label')
|
||||
|
||||
self._name = 'Graphs'
|
||||
self._child_widget = self.window
|
||||
self._tab_label = self.label
|
||||
|
||||
self.colors = colors
|
||||
|
||||
self.bandwidth_graph = self.glade.get_widget('bandwidth_graph')
|
||||
self.bandwidth_graph.connect('expose_event', self.expose)
|
||||
self.bandwidth_graph.connect('expose_event', self.graph_expose)
|
||||
|
||||
self.connections_graph = self.glade.get_widget('connections_graph')
|
||||
self.connections_graph.connect('expose_event', self.graph_expose)
|
||||
|
||||
self.seeds_graph = self.glade.get_widget('seeds_graph')
|
||||
self.seeds_graph.connect('expose_event', self.graph_expose)
|
||||
|
||||
self.notebook.connect('switch-page', self._on_notebook_switch_page)
|
||||
|
||||
self.selected_interval = 1 #should come from config or similar
|
||||
self.select_bandwidth_graph()
|
||||
|
||||
self.window.unparent()
|
||||
self.label.unparent()
|
||||
|
||||
self.graph_widget = self.bandwidth_graph
|
||||
self.graph = graph.Graph()
|
||||
self.graph.add_stat('payload_download_rate', label='Download Rate', color=graph.green)
|
||||
self.graph.add_stat('payload_upload_rate', label='Upload Rate', color=graph.blue)
|
||||
self.graph.set_left_axis(formatter=fspeed, min=10240)
|
||||
self.intervals = None
|
||||
self.intervals_combo = self.glade.get_widget('combo_intervals')
|
||||
cell = gtk.CellRendererText()
|
||||
self.intervals_combo.pack_start(cell, True)
|
||||
self.intervals_combo.set_cell_data_func(cell, neat_time)
|
||||
self.intervals_combo.connect("changed", self._on_selected_interval_changed)
|
||||
self.update_intervals()
|
||||
|
||||
def expose(self, widget, event):
|
||||
"""Redraw"""
|
||||
|
||||
def graph_expose(self, widget, event):
|
||||
context = self.graph_widget.window.cairo_create()
|
||||
# set a clip region
|
||||
context.rectangle(event.area.x, event.area.y,
|
||||
event.area.width, event.area.height)
|
||||
context.clip()
|
||||
|
||||
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
|
||||
self.graph.draw_to_context(context, width, height)
|
||||
self.graph.draw_to_context(context,
|
||||
self.graph_widget.allocation.width,
|
||||
self.graph_widget.allocation.height)
|
||||
#Do not propagate the event
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
log.debug("getstat keys: %s", self.graph.stat_info.keys())
|
||||
d1 = client.stats.get_stats(self.graph.stat_info.keys())
|
||||
d1 = client.stats.get_stats(self.graph.stat_info.keys(), self.selected_interval)
|
||||
d1.addCallback(self.graph.set_stats)
|
||||
d2 = client.stats.get_config()
|
||||
d2.addCallback(self.graph.set_config)
|
||||
dl = defer.DeferredList([d1, d2])
|
||||
|
||||
def _on_update(result):
|
||||
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
|
||||
rect = gtk.gdk.Rectangle(0, 0, width, height)
|
||||
self.graph_widget.window.invalidate_rect(rect, True)
|
||||
|
||||
dl.addCallback(_on_update)
|
||||
def _update_complete(result):
|
||||
self.graph_widget.queue_draw()
|
||||
d1.addCallback(_update_complete)
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
pass
|
||||
|
||||
def update_intervals(self):
|
||||
client.stats.get_intervals().addCallback(self._on_intervals_changed)
|
||||
|
||||
def select_bandwidth_graph(self):
|
||||
log.debug("Selecting bandwidth graph")
|
||||
self.graph_widget = self.bandwidth_graph
|
||||
self.graph = graph.Graph()
|
||||
colors = self.colors['bandwidth_graph']
|
||||
self.graph.add_stat('download_rate', label='Download Rate',
|
||||
color=gtk_to_graph_color(colors['download_rate']))
|
||||
self.graph.add_stat('upload_rate', label='Upload Rate',
|
||||
color=gtk_to_graph_color(colors['upload_rate']))
|
||||
self.graph.set_left_axis(formatter=fspeed, min=10240,
|
||||
formatter_scale=graph.size_formatter_scale)
|
||||
|
||||
def select_connections_graph(self):
|
||||
log.debug("Selecting connections graph")
|
||||
self.graph_widget = self.connections_graph
|
||||
g = graph.Graph()
|
||||
self.graph = g
|
||||
colors = self.colors['connections_graph']
|
||||
g.add_stat('dht_nodes', color=gtk_to_graph_color(colors['dht_nodes']))
|
||||
g.add_stat('dht_cache_nodes', color=gtk_to_graph_color(colors['dht_cache_nodes']))
|
||||
g.add_stat('dht_torrents', color=gtk_to_graph_color(colors['dht_torrents']))
|
||||
g.add_stat('num_connections', color=gtk_to_graph_color(colors['num_connections']))
|
||||
g.set_left_axis(formatter=int_str, min=10)
|
||||
|
||||
def select_seeds_graph(self):
|
||||
log.debug("Selecting connections graph")
|
||||
self.graph_widget = self.seeds_graph
|
||||
self.graph = graph.Graph()
|
||||
colors = self.colors['seeds_graph']
|
||||
self.graph.add_stat('num_peers', color=gtk_to_graph_color(colors['num_peers']))
|
||||
self.graph.set_left_axis(formatter=int_str, min=10)
|
||||
|
||||
def set_colors(self, colors):
|
||||
self.colors = colors
|
||||
# Fake switch page to update the graph colors (HACKY)
|
||||
self._on_notebook_switch_page(self.notebook,
|
||||
None, #This is unused
|
||||
self.notebook.get_current_page())
|
||||
|
||||
def _on_intervals_changed(self, intervals):
|
||||
liststore = gtk.ListStore(int)
|
||||
for inter in intervals:
|
||||
liststore.append([inter])
|
||||
self.intervals_combo.set_model(liststore)
|
||||
try:
|
||||
current = intervals.index(self.selected_interval)
|
||||
except:
|
||||
current = 0
|
||||
#should select the value saved in config
|
||||
self.intervals_combo.set_active(current)
|
||||
|
||||
def _on_selected_interval_changed(self, combobox):
|
||||
model = combobox.get_model()
|
||||
iter = combobox.get_active_iter()
|
||||
self.selected_interval = model.get_value(iter, 0)
|
||||
self.update()
|
||||
return True
|
||||
|
||||
def _on_notebook_switch_page(self, notebook, page, page_num):
|
||||
p = notebook.get_nth_page(page_num)
|
||||
if p is self.bandwidth_graph:
|
||||
self.select_bandwidth_graph()
|
||||
self.update()
|
||||
elif p is self.connections_graph:
|
||||
self.select_connections_graph()
|
||||
self.update()
|
||||
elif p is self.seeds_graph:
|
||||
self.select_seeds_graph()
|
||||
self.update()
|
||||
return True
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
def enable(self):
|
||||
log.debug("Stats plugin enable called")
|
||||
self.config = deluge.configmanager.ConfigManager("stats.gtkui.conf", DEFAULT_CONF)
|
||||
self.glade = XML(common.get_resource("config.glade"))
|
||||
component.get("Preferences").add_page("Stats", self.glade.get_widget("prefs_box"))
|
||||
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
|
||||
self.on_show_prefs()
|
||||
|
||||
self.graphs_tab = GraphsTab(XML(common.get_resource("tabs.glade")))
|
||||
self.graphs_tab = GraphsTab(XML(common.get_resource("tabs.glade")), self.config['colors'])
|
||||
self.torrent_details = component.get('TorrentDetails')
|
||||
self.torrent_details.add_tab(self.graphs_tab)
|
||||
|
||||
|
@ -139,15 +251,31 @@ class GtkUI(GtkPluginBase):
|
|||
|
||||
def on_apply_prefs(self):
|
||||
log.debug("applying prefs for Stats")
|
||||
config = {
|
||||
"test":self.glade.get_widget("txt_test").get_text()
|
||||
}
|
||||
gtkconf = {}
|
||||
for graph, colors in self.config['colors'].items():
|
||||
gtkconf[graph] = {}
|
||||
for value, color in colors.items():
|
||||
try:
|
||||
color_btn = self.glade.get_widget("%s_%s_color" % (graph, value))
|
||||
gtkconf[graph][value] = str(color_btn.get_color())
|
||||
except:
|
||||
gtkconf[graph][value] = DEFAULT_CONF['colors'][graph][value]
|
||||
self.config['colors'] = gtkconf
|
||||
self.graphs_tab.set_colors(self.config['colors'])
|
||||
|
||||
config = { }
|
||||
client.stats.set_config(config)
|
||||
|
||||
def on_show_prefs(self):
|
||||
for graph, colors in self.config['colors'].items():
|
||||
for value, color in colors.items():
|
||||
try:
|
||||
color_btn = self.glade.get_widget("%s_%s_color" % (graph, value))
|
||||
color_btn.set_color(gtk.gdk.Color(color))
|
||||
except:
|
||||
log.debug("Unable to set %s %s %s" % (graph, value, color))
|
||||
client.stats.get_config().addCallback(self.cb_get_config)
|
||||
|
||||
def cb_get_config(self, config):
|
||||
"callback for on show_prefs"
|
||||
self.glade.get_widget("txt_test").set_text(config["test"])
|
||||
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
$:render.header(_("Network Graph"), 'graph')
|
||||
$:render.admin_toolbar('graph')
|
||||
|
||||
<div style="padding-left:20px">
|
||||
|
||||
<img src="$base/graph/network.png?height=300&width=1000"><br \>
|
||||
<img src="$base/graph/connections.png?height=300&width=1000"><br \>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
$:render.footer()
|
|
@ -1,7 +1,11 @@
|
|||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2009 Damien Churchill <mvoncken@gmail.com>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
|
@ -33,20 +37,18 @@
|
|||
#
|
||||
#
|
||||
|
||||
import logging
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge import component
|
||||
from deluge.plugins.pluginbase import WebPluginBase
|
||||
|
||||
from common import get_resource
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
|
||||
scripts = [get_resource("stats.js")]
|
||||
|
||||
# The enable and disable methods are not strictly required on the WebUI
|
||||
|
||||
# The enable and disable methods are not scrictly required on the WebUI
|
||||
# plugins. They are only here if you need to register images/stylesheets
|
||||
# with the webserver.
|
||||
def enable(self):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
|
@ -23,18 +23,7 @@
|
|||
# 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
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
# 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
|
||||
|
@ -48,13 +37,16 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
__plugin_name__ = "Stats"
|
||||
__author__ = "Martijn Voncken"
|
||||
__author_email__ = "mvoncken@gmail.com"
|
||||
__version__ = "0.1"
|
||||
__author__ = "Ian Martin"
|
||||
__author_email__ = "ianmartin@cantab.net"
|
||||
__version__ = "0.3.2"
|
||||
__url__ = "http://deluge-torrent.org"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = ""
|
||||
__long_description__ = """"""
|
||||
__description__ = "Display stats graphs"
|
||||
__long_description__ = """
|
||||
Records lots of extra stats
|
||||
and produces time series
|
||||
graphs"""
|
||||
__pkg_data__ = {"deluge.plugins."+__plugin_name__.lower(): ["template/*", "data/*"]}
|
||||
|
||||
setup(
|
||||
|
|
Loading…
Reference in New Issue