[UI] [Core] Convert to session_stats_alert for session status

* Use session disk stats for cache status
This commit is contained in:
Calum Lind 2016-12-03 22:07:01 +00:00
commit 0f2083db62
8 changed files with 161 additions and 74 deletions

View file

@ -42,6 +42,54 @@ from deluge.httpdownloader import download_file
log = logging.getLogger(__name__) 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): class Core(component.Component):
def __init__(self, listen_interface=None, read_only_config_keys=None): def __init__(self, listen_interface=None, read_only_config_keys=None):
@ -106,13 +154,29 @@ class Core(component.Component):
# New release check information # New release check information
self.__new_release = None 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): def start(self):
"""Starts the core""" """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): def stop(self):
log.debug('Core stopping...') 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 # Save the libtorrent session state
self.__save_session_state() self.__save_session_state()
@ -187,6 +251,41 @@ class Core(component.Component):
log.info('Successfully loaded %s: %s', filename, _filepath) log.info('Successfully loaded %s: %s', filename, _filepath)
self.session.load_state(state) 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): def get_new_release(self):
log.debug('get_new_release') log.debug('get_new_release')
from urllib2 import urlopen, URLError from urllib2 import urlopen, URLError
@ -381,8 +480,7 @@ class Core(component.Component):
@export @export
def get_session_status(self, keys): 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. from libtorrent's session status.
See: http://www.rasterbar.com/products/libtorrent/manual.html#status See: http://www.rasterbar.com/products/libtorrent/manual.html#status
@ -393,44 +491,26 @@ class Core(component.Component):
:rtype: dict :rtype: dict
""" """
if not self.session_status:
return {key: 0 for key in keys}
if not keys:
return self.session_status
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: 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 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 @export
def force_reannounce(self, torrent_ids): def force_reannounce(self, torrent_ids):
log.debug('Forcing reannouncment to: %s', torrent_ids) log.debug('Forcing reannouncment to: %s', torrent_ids)

View file

@ -204,13 +204,8 @@ class Core(CorePluginBase):
@export @export
def get_session_totals(self): def get_session_totals(self):
status = self.core.session.status() return self.core.get_session_status(
return { ['total_upload', 'total_download', 'total_payload_upload', 'total_payload_download'])
'total_upload': status.total_upload,
'total_download': status.total_download,
'total_payload_upload': status.total_payload_upload,
'total_payload_download': status.total_payload_download
}
@export @export
def set_config(self, config): def set_config(self, config):

View file

@ -252,8 +252,8 @@ class CoreTestCase(BaseTestCase):
self.assertEquals(type(status), dict) self.assertEquals(type(status), dict)
self.assertEquals(status['upload_rate'], 0.0) self.assertEquals(status['upload_rate'], 0.0)
def test_get_cache_status(self): def test_get_session_status_ratio(self):
status = self.core.get_cache_status() status = self.core.get_session_status(['write_hit_ratio', 'read_hit_ratio'])
self.assertEquals(type(status), dict) self.assertEquals(type(status), dict)
self.assertEquals(status['write_hit_ratio'], 0.0) self.assertEquals(status['write_hit_ratio'], 0.0)
self.assertEquals(status['read_hit_ratio'], 0.0) self.assertEquals(status['read_hit_ratio'], 0.0)

View file

@ -120,6 +120,13 @@ DEFAULT_HOSTS = {
'hosts': [(sha(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, '', '')] '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): class TorrentInfo(object):
""" """

View file

@ -9,6 +9,7 @@
import deluge.component as component import deluge.component as component
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.common import DISK_CACHE_KEYS
from . import BaseCommand from . import BaseCommand
@ -20,9 +21,7 @@ class Command(BaseCommand):
self.console = component.get('ConsoleUI') self.console = component.get('ConsoleUI')
def on_cache_status(status): 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)) self.console.write('{!info!}%s: {!input!}%s' % (key, value))
d = client.core.get_cache_status() return client.core.get_session_status(DISK_CACHE_KEYS).addCallback(on_cache_status)
d.addCallback(on_cache_status)
return d

View file

@ -12,6 +12,7 @@ import logging
from deluge.common import is_ip from deluge.common import is_ip
from deluge.decorators import overrides from deluge.decorators import overrides
from deluge.ui.client import client 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 import BaseInputPane, BaseWindow
from deluge.ui.console.widgets.fields import FloatSpinInput, TextInput from deluge.ui.console.widgets.fields import FloatSpinInput, TextInput
from deluge.ui.console.widgets.popup import PopupsHandler from deluge.ui.console.widgets.popup import PopupsHandler
@ -412,29 +413,29 @@ class CachePane(BasePreferencePane):
'%s:' % _('Cache Expiry (seconds)'), core_conf['cache_expiry'], '%s:' % _('Cache Expiry (seconds)'), core_conf['cache_expiry'],
min_val=1, max_val=32000) min_val=1, max_val=32000)
self.add_header(' %s' % _('Write'), space_above=True) self.add_header(' %s' % _('Write'), space_above=True)
self.add_info_field('blocks_written', ' %s:' % _('Blocks Written'), status['blocks_written']) self.add_info_field('blocks_written', ' %s:' % _('Blocks Written'), status['disk.num_blocks_written'])
self.add_info_field('writes', ' %s:' % _('Writes'), status['writes']) self.add_info_field('writes', ' %s:' % _('Writes'), status['disk.num_write_ops'])
self.add_info_field('write_hit_ratio', self.add_info_field('write_hit_ratio',
' %s:' % _('Write Cache Hit Ratio'), '%.2f' % status['write_hit_ratio']) ' %s:' % _('Write Cache Hit Ratio'), '%.2f' % status['write_hit_ratio'])
self.add_header(' %s' % _('Read')) self.add_header(' %s' % _('Read'))
self.add_info_field('blocks_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', 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', self.add_info_field('reads',
' %s:' % _('Reads'), status['reads']) ' %s:' % _('Reads'), status['disk.num_read_ops'])
self.add_info_field('read_hit_ratio', self.add_info_field('read_hit_ratio',
' %s:' % _('Read Cache Hit Ratio'), '%.2f' % status['read_hit_ratio']) ' %s:' % _('Read Cache Hit Ratio'), '%.2f' % status['read_hit_ratio'])
self.add_header(' %s' % _('Size')) self.add_header(' %s' % _('Size'))
self.add_info_field('cache_size_info', 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', 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) @overrides(BasePreferencePane)
def update(self, active): def update(self, active):
if 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): def update_cache_status_fields(self, status):
if not self.created: if not self.created:

View file

@ -4058,7 +4058,7 @@ the proxy instead of using the local DNS service</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_cache_blocks_written"> <object class="GtkLabel" id="label_cache_num_blocks_written">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">1</property> <property name="xalign">1</property>
@ -4070,7 +4070,7 @@ the proxy instead of using the local DNS service</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_cache_writes"> <object class="GtkLabel" id="label_cache_write_ops">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">1</property> <property name="xalign">1</property>
@ -4177,7 +4177,7 @@ the proxy instead of using the local DNS service</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_cache_blocks_read"> <object class="GtkLabel" id="label_cache_num_blocks_read">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">1</property> <property name="xalign">1</property>
@ -4189,7 +4189,7 @@ the proxy instead of using the local DNS service</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_cache_blocks_read_hit"> <object class="GtkLabel" id="label_cache_num_blocks_cache_hits">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">1</property> <property name="xalign">1</property>
@ -4232,7 +4232,7 @@ the proxy instead of using the local DNS service</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_cache_reads"> <object class="GtkLabel" id="label_cache_read_ops">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
</object> </object>
@ -4309,7 +4309,7 @@ the proxy instead of using the local DNS service</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_cache_cache_size"> <object class="GtkLabel" id="label_cache_disk_blocks_in_use">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">1</property> <property name="xalign">1</property>
@ -4321,7 +4321,7 @@ the proxy instead of using the local DNS service</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label_cache_read_cache_size"> <object class="GtkLabel" id="label_cache_read_cache_blocks">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">1</property> <property name="xalign">1</property>

View file

@ -20,6 +20,7 @@ import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir from deluge.configmanager import ConfigManager, get_config_dir
from deluge.error import AuthManagerError, NotAuthorizedError from deluge.error import AuthManagerError, NotAuthorizedError
from deluge.ui.client import client 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.common import associate_magnet_links, get_deluge_icon
from deluge.ui.gtkui.dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog from deluge.ui.gtkui.dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog
from deluge.ui.gtkui.path_chooser import PathChooser from deluge.ui.gtkui.path_chooser import PathChooser
@ -292,9 +293,9 @@ class Preferences(component.Component):
def _on_get_listen_port(port): def _on_get_listen_port(port):
self.active_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.cache_status = status
self._show() self._show()
@ -719,12 +720,16 @@ class Preferences(component.Component):
def __update_cache_status(self): def __update_cache_status(self):
# Updates the cache status labels with the info in the dict # 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', cache_labels = ('label_cache_read_ops', 'label_cache_write_ops',
'label_cache_blocks_read', 'label_cache_blocks_read_hit', 'label_cache_read_hit_ratio', 'label_cache_num_blocks_read', 'label_cache_num_blocks_written',
'label_cache_reads', 'label_cache_cache_size', 'label_cache_read_cache_size'): '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) widget = self.builder.get_object(widget_name)
key = widget_name[len('label_cache_'):] key = 'disk.' + widget_name[len('label_cache_'):]
value = self.cache_status[key] value = self.cache_status.get(key, 0)
if isinstance(value, float): if isinstance(value, float):
value = '%.2f' % value value = '%.2f' % value
else: else:
@ -733,11 +738,11 @@ class Preferences(component.Component):
widget.set_text(value) widget.set_text(value)
def _on_button_cache_refresh_clicked(self, widget): 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.cache_status = status
self.__update_cache_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): def on_pref_dialog_delete_event(self, widget, event):
self.hide() self.hide()