diff --git a/deluge/core/core.py b/deluge/core/core.py
index 7142314fc..b46a57d18 100644
--- a/deluge/core/core.py
+++ b/deluge/core/core.py
@@ -42,6 +42,54 @@ from deluge.httpdownloader import download_file
log = logging.getLogger(__name__)
+OLD_SESSION_STATUS_KEYS = {
+ # 'active_requests': None, # In dht_stats_alert, if required.
+ 'allowed_upload_slots': 'ses.num_unchoke_slots',
+ # 'dht_global_nodes': None,
+ 'dht_node_cache': 'dht.dht_node_cache',
+ 'dht_nodes': 'dht.dht_nodes',
+ 'dht_torrents': 'dht.dht_torrents',
+ # 'dht_total_allocations': None,
+ 'down_bandwidth_bytes_queue': 'net.limiter_down_bytes',
+ 'down_bandwidth_queue': 'net.limiter_down_queue',
+ 'has_incoming_connections': 'net.has_incoming_connections',
+ 'num_peers': 'peer.num_peers_connected',
+ 'num_unchoked': 'peer.num_peers_up_unchoked',
+ # 'optimistic_unchoke_counter': None, # lt.settings_pack
+ 'total_dht_download': 'dht.dht_bytes_in',
+ 'total_dht_upload': 'dht.dht_bytes_out',
+ 'total_download': 'net.recv_bytes',
+ 'total_failed_bytes': 'net.recv_failed_bytes',
+ 'total_ip_overhead_download': 'net.recv_ip_overhead_bytes',
+ 'total_ip_overhead_upload': 'net.sent_ip_overhead_bytes',
+ 'total_payload_download': 'net.recv_payload_bytes',
+ 'total_payload_upload': 'net.sent_payload_bytes',
+ 'total_redundant_bytes': 'net.recv_redundant_bytes',
+ 'total_tracker_download': 'net.recv_tracker_bytes',
+ 'total_tracker_upload': 'net.sent_tracker_bytes',
+ 'total_upload': 'net.sent_bytes',
+ # 'unchoke_counter': None, # lt.settings_pack
+ 'up_bandwidth_bytes_queue': 'net.limiter_up_bytes',
+ 'up_bandwidth_queue': 'net.limiter_up_queue',
+ # 'utp_stats': None
+}
+
+# TODO: replace with dynamic rate e.g.
+# 'dht.dht_bytes_in'.replace('_bytes', '') + '_rate'
+# would become 'dht.dht_in_rate'
+SESSION_RATES_MAPPING = {
+ 'dht_download_rate': 'dht.dht_bytes_in',
+ 'dht_upload_rate': 'dht.dht_bytes_out',
+ 'ip_overhead_download_rate': 'net.recv_ip_overhead_bytes',
+ 'ip_overhead_upload_rate': 'net.sent_ip_overhead_bytes',
+ 'payload_download_rate': 'net.recv_payload_bytes',
+ 'payload_upload_rate': 'net.sent_payload_bytes',
+ 'tracker_download_rate': 'net.recv_tracker_bytes',
+ 'tracker_upload_rate': 'net.sent_tracker_bytes',
+ 'download_rate': 'net.recv_bytes',
+ 'upload_rate': 'net.sent_bytes',
+}
+
class Core(component.Component):
def __init__(self, listen_interface=None, read_only_config_keys=None):
@@ -106,13 +154,29 @@ class Core(component.Component):
# New release check information
self.__new_release = None
+ # Session status timer
+ self.session_status = {}
+ self.session_status_timer_interval = 0.5
+ self.session_status_timer = task.LoopingCall(self.session.post_session_stats)
+ self.alertmanager.register_handler('session_stats_alert', self._on_alert_session_stats)
+ self._session_rates = {(k_rate, k_bytes): 0 for k_rate, k_bytes in SESSION_RATES_MAPPING.items()}
+ self.session_rates_timer_interval = 2
+ self.session_rates_timer = task.LoopingCall(self._update_session_rates)
+
def start(self):
"""Starts the core"""
- pass
+ self.session_status_timer.start(self.session_status_timer_interval)
+ self.session_rates_timer.start(self.session_rates_timer_interval, now=False)
def stop(self):
log.debug('Core stopping...')
+ if self.session_status_timer.running:
+ self.session_status_timer.stop()
+
+ if self.session_rates_timer.running:
+ self.session_rates_timer.stop()
+
# Save the libtorrent session state
self.__save_session_state()
@@ -187,6 +251,41 @@ class Core(component.Component):
log.info('Successfully loaded %s: %s', filename, _filepath)
self.session.load_state(state)
+ def _on_alert_session_stats(self, alert):
+ """The handler for libtorrent session stats alert"""
+ if not self.session_status:
+ # Empty dict on startup so needs populated with session rate keys and default value.
+ self.session_status.update({key: 0 for key in list(SESSION_RATES_MAPPING)})
+ self.session_status.update(alert.values)
+ self._update_session_cache_hit_ratio()
+
+ def _update_session_cache_hit_ratio(self):
+ """Calculates the cache read/write hit ratios and updates session_status"""
+ try:
+ self.session_status['write_hit_ratio'] = ((self.session_status['disk.num_blocks_written'] -
+ self.session_status['disk.num_write_ops']) /
+ self.session_status['disk.num_blocks_written'])
+ except ZeroDivisionError:
+ self.session_status['write_hit_ratio'] = 0.0
+
+ try:
+ self.session_status['read_hit_ratio'] = (self.session_status['disk.num_blocks_cache_hits'] /
+ self.session_status['disk.num_blocks_read'])
+ except ZeroDivisionError:
+ self.session_status['read_hit_ratio'] = 0.0
+
+ def _update_session_rates(self):
+ """Calculates status rates based on interval and value difference for session_status"""
+ if not self.session_status:
+ return
+
+ for (rate_key, status_key), prev_bytes in list(self._session_rates.items()):
+ new_bytes = self.session_status[status_key]
+ byte_rate = (new_bytes - prev_bytes) / self.session_rates_timer_interval
+ self.session_status[rate_key] = byte_rate
+ # Store current value for next update.
+ self._session_rates[(rate_key, status_key)] = new_bytes
+
def get_new_release(self):
log.debug('get_new_release')
from urllib2 import urlopen, URLError
@@ -381,8 +480,7 @@ class Core(component.Component):
@export
def get_session_status(self, keys):
- """
- Gets the session status values for 'keys', these keys are taking
+ """Gets the session status values for 'keys', these keys are taking
from libtorrent's session status.
See: http://www.rasterbar.com/products/libtorrent/manual.html#status
@@ -393,44 +491,26 @@ class Core(component.Component):
:rtype: dict
"""
+
+ if not self.session_status:
+ return {key: 0 for key in keys}
+
+ if not keys:
+ return self.session_status
+
status = {}
- # TODO: libtorrent DEPRECATED for session_stats http://libtorrent.org/manual-ref.html#session-statistics
- session_status = self.session.status()
for key in keys:
- status[key] = getattr(session_status, key)
-
+ if key in OLD_SESSION_STATUS_KEYS:
+ new_key = OLD_SESSION_STATUS_KEYS[key]
+ log.warning('Using deprecated session status key %s, please use %s', key, new_key)
+ status[key] = self.session_status[new_key]
+ else:
+ try:
+ status[key] = self.session_status[key]
+ except KeyError:
+ log.warning('Session status key does not exist: %s', key)
return status
- @export
- def get_cache_status(self):
- """
- Returns a dictionary of the session's cache status.
-
- :returns: the cache status
- :rtype: dict
-
- """
- # TODO: libtorrent DEPRECATED for session_stats: disk.num_blocks_cache_hits etc...
- status = self.session.get_cache_status()
- cache = {}
- for attr in dir(status):
- if attr.startswith('_'):
- continue
- cache[attr] = getattr(status, attr)
-
- # Add in a couple ratios
- try:
- cache['write_hit_ratio'] = (cache['blocks_written'] - cache['writes']) / cache['blocks_written']
- except ZeroDivisionError:
- cache['write_hit_ratio'] = 0.0
-
- try:
- cache['read_hit_ratio'] = cache['blocks_read_hit'] / cache['blocks_read']
- except ZeroDivisionError:
- cache['read_hit_ratio'] = 0.0
-
- return cache
-
@export
def force_reannounce(self, torrent_ids):
log.debug('Forcing reannouncment to: %s', torrent_ids)
diff --git a/deluge/plugins/Stats/deluge/plugins/stats/core.py b/deluge/plugins/Stats/deluge/plugins/stats/core.py
index 94beade1a..08fe402ec 100644
--- a/deluge/plugins/Stats/deluge/plugins/stats/core.py
+++ b/deluge/plugins/Stats/deluge/plugins/stats/core.py
@@ -204,13 +204,8 @@ class Core(CorePluginBase):
@export
def get_session_totals(self):
- status = self.core.session.status()
- return {
- 'total_upload': status.total_upload,
- 'total_download': status.total_download,
- 'total_payload_upload': status.total_payload_upload,
- 'total_payload_download': status.total_payload_download
- }
+ return self.core.get_session_status(
+ ['total_upload', 'total_download', 'total_payload_upload', 'total_payload_download'])
@export
def set_config(self, config):
diff --git a/deluge/tests/test_core.py b/deluge/tests/test_core.py
index 8e204db9b..97b5e5ab9 100644
--- a/deluge/tests/test_core.py
+++ b/deluge/tests/test_core.py
@@ -252,8 +252,8 @@ class CoreTestCase(BaseTestCase):
self.assertEquals(type(status), dict)
self.assertEquals(status['upload_rate'], 0.0)
- def test_get_cache_status(self):
- status = self.core.get_cache_status()
+ def test_get_session_status_ratio(self):
+ status = self.core.get_session_status(['write_hit_ratio', 'read_hit_ratio'])
self.assertEquals(type(status), dict)
self.assertEquals(status['write_hit_ratio'], 0.0)
self.assertEquals(status['read_hit_ratio'], 0.0)
diff --git a/deluge/ui/common.py b/deluge/ui/common.py
index 7f33b7b20..56ac5dc23 100644
--- a/deluge/ui/common.py
+++ b/deluge/ui/common.py
@@ -120,6 +120,13 @@ DEFAULT_HOSTS = {
'hosts': [(sha(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, '', '')]
}
+# The keys from session statistics for cache status.
+DISK_CACHE_KEYS = [
+ 'disk.num_blocks_read', 'disk.num_blocks_written', 'disk.num_read_ops', 'disk.num_write_ops',
+ 'disk.num_blocks_cache_hits', 'read_hit_ratio', 'write_hit_ratio', 'disk.disk_blocks_in_use',
+ 'disk.read_cache_blocks'
+]
+
class TorrentInfo(object):
"""
diff --git a/deluge/ui/console/cmdline/commands/cache.py b/deluge/ui/console/cmdline/commands/cache.py
index abcce523c..8a8cc15c1 100644
--- a/deluge/ui/console/cmdline/commands/cache.py
+++ b/deluge/ui/console/cmdline/commands/cache.py
@@ -9,6 +9,7 @@
import deluge.component as component
from deluge.ui.client import client
+from deluge.ui.common import DISK_CACHE_KEYS
from . import BaseCommand
@@ -20,9 +21,7 @@ class Command(BaseCommand):
self.console = component.get('ConsoleUI')
def on_cache_status(status):
- for key, value in status.items():
+ for key, value in sorted(status.items()):
self.console.write('{!info!}%s: {!input!}%s' % (key, value))
- d = client.core.get_cache_status()
- d.addCallback(on_cache_status)
- return d
+ return client.core.get_session_status(DISK_CACHE_KEYS).addCallback(on_cache_status)
diff --git a/deluge/ui/console/modes/preferences/preference_panes.py b/deluge/ui/console/modes/preferences/preference_panes.py
index 2f00ed284..6497a42c7 100644
--- a/deluge/ui/console/modes/preferences/preference_panes.py
+++ b/deluge/ui/console/modes/preferences/preference_panes.py
@@ -12,6 +12,7 @@ import logging
from deluge.common import is_ip
from deluge.decorators import overrides
from deluge.ui.client import client
+from deluge.ui.common import DISK_CACHE_KEYS
from deluge.ui.console.widgets import BaseInputPane, BaseWindow
from deluge.ui.console.widgets.fields import FloatSpinInput, TextInput
from deluge.ui.console.widgets.popup import PopupsHandler
@@ -412,29 +413,29 @@ class CachePane(BasePreferencePane):
'%s:' % _('Cache Expiry (seconds)'), core_conf['cache_expiry'],
min_val=1, max_val=32000)
self.add_header(' %s' % _('Write'), space_above=True)
- self.add_info_field('blocks_written', ' %s:' % _('Blocks Written'), status['blocks_written'])
- self.add_info_field('writes', ' %s:' % _('Writes'), status['writes'])
+ self.add_info_field('blocks_written', ' %s:' % _('Blocks Written'), status['disk.num_blocks_written'])
+ self.add_info_field('writes', ' %s:' % _('Writes'), status['disk.num_write_ops'])
self.add_info_field('write_hit_ratio',
' %s:' % _('Write Cache Hit Ratio'), '%.2f' % status['write_hit_ratio'])
self.add_header(' %s' % _('Read'))
self.add_info_field('blocks_read',
- ' %s:' % _('Blocks Read'), status['blocks_read'])
+ ' %s:' % _('Blocks Read'), status['disk.num_blocks_read'])
self.add_info_field('blocks_read_hit',
- ' %s:' % _('Blocks Read hit'), status['blocks_read_hit'])
+ ' %s:' % _('Blocks Read hit'), status['disk.num_blocks_cache_hits'])
self.add_info_field('reads',
- ' %s:' % _('Reads'), status['reads'])
+ ' %s:' % _('Reads'), status['disk.num_read_ops'])
self.add_info_field('read_hit_ratio',
' %s:' % _('Read Cache Hit Ratio'), '%.2f' % status['read_hit_ratio'])
self.add_header(' %s' % _('Size'))
self.add_info_field('cache_size_info',
- ' %s:' % _('Cache Size'), status['cache_size'])
+ ' %s:' % _('Cache Size'), status['disk.disk_blocks_in_use'])
self.add_info_field('read_cache_size',
- ' %s:' % _('Read Cache Size'), status['read_cache_size'])
+ ' %s:' % _('Read Cache Size'), status['disk.read_cache_blocks'])
@overrides(BasePreferencePane)
def update(self, active):
if active:
- client.core.get_cache_status().addCallback(self.update_cache_status_fields)
+ client.core.get_session_status(DISK_CACHE_KEYS).addCallback(self.update_cache_status_fields)
def update_cache_status_fields(self, status):
if not self.created:
diff --git a/deluge/ui/gtkui/glade/preferences_dialog.ui b/deluge/ui/gtkui/glade/preferences_dialog.ui
index c8d732357..b88751521 100644
--- a/deluge/ui/gtkui/glade/preferences_dialog.ui
+++ b/deluge/ui/gtkui/glade/preferences_dialog.ui
@@ -4058,7 +4058,7 @@ the proxy instead of using the local DNS service
-
-
+
True
False
1
@@ -4177,7 +4177,7 @@ the proxy instead of using the local DNS service
-
+
True
False
1
@@ -4189,7 +4189,7 @@ the proxy instead of using the local DNS service
-
+
True
False
1
@@ -4232,7 +4232,7 @@ the proxy instead of using the local DNS service
-
+
True
False
@@ -4309,7 +4309,7 @@ the proxy instead of using the local DNS service
-
+
True
False
1
@@ -4321,7 +4321,7 @@ the proxy instead of using the local DNS service
-
+
True
False
1
diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py
index cdcc1716c..0d275d025 100644
--- a/deluge/ui/gtkui/preferences.py
+++ b/deluge/ui/gtkui/preferences.py
@@ -20,6 +20,7 @@ import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.error import AuthManagerError, NotAuthorizedError
from deluge.ui.client import client
+from deluge.ui.common import DISK_CACHE_KEYS
from deluge.ui.gtkui.common import associate_magnet_links, get_deluge_icon
from deluge.ui.gtkui.dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog
from deluge.ui.gtkui.path_chooser import PathChooser
@@ -292,9 +293,9 @@ class Preferences(component.Component):
def _on_get_listen_port(port):
self.active_port = port
- client.core.get_cache_status().addCallback(_on_get_cache_status)
+ client.core.get_session_status(DISK_CACHE_KEYS).addCallback(_on_get_session_status)
- def _on_get_cache_status(status):
+ def _on_get_session_status(status):
self.cache_status = status
self._show()
@@ -719,12 +720,16 @@ class Preferences(component.Component):
def __update_cache_status(self):
# Updates the cache status labels with the info in the dict
- for widget_name in ('label_cache_blocks_written', 'label_cache_writes', 'label_cache_write_hit_ratio',
- 'label_cache_blocks_read', 'label_cache_blocks_read_hit', 'label_cache_read_hit_ratio',
- 'label_cache_reads', 'label_cache_cache_size', 'label_cache_read_cache_size'):
+ cache_labels = ('label_cache_read_ops', 'label_cache_write_ops',
+ 'label_cache_num_blocks_read', 'label_cache_num_blocks_written',
+ 'label_cache_read_hit_ratio', 'label_cache_write_hit_ratio',
+ 'label_cache_num_blocks_cache_hits', 'label_cache_disk_blocks_in_use',
+ 'label_cache_read_cache_blocks')
+
+ for widget_name in cache_labels:
widget = self.builder.get_object(widget_name)
- key = widget_name[len('label_cache_'):]
- value = self.cache_status[key]
+ key = 'disk.' + widget_name[len('label_cache_'):]
+ value = self.cache_status.get(key, 0)
if isinstance(value, float):
value = '%.2f' % value
else:
@@ -733,11 +738,11 @@ class Preferences(component.Component):
widget.set_text(value)
def _on_button_cache_refresh_clicked(self, widget):
- def on_get_cache_status(status):
+ def on_get_session_status(status):
self.cache_status = status
self.__update_cache_status()
- client.core.get_cache_status().addCallback(on_get_cache_status)
+ client.core.get_session_status(DISK_CACHE_KEYS).addCallback(on_get_session_status)
def on_pref_dialog_delete_event(self, widget, event):
self.hide()