2017-07-18 07:37:58 +02:00
|
|
|
#! /usr/bin/env python2
|
2017-07-21 10:28:19 +02:00
|
|
|
|
|
|
|
"""=cut
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
deluge_ - Munin wildcard plugin to monitor Deluge torrent client
|
|
|
|
|
|
|
|
=head1 REQUIREMENTS
|
|
|
|
|
|
|
|
- Python2.5+ (Deluge itself won't work with python3)
|
|
|
|
- Deluge
|
|
|
|
|
|
|
|
This plugin also uses
|
|
|
|
- deluge.ui.client
|
|
|
|
- deluge.log
|
|
|
|
- twisted
|
|
|
|
These modules are required by Deluge itself.
|
|
|
|
|
|
|
|
=head1 INSTALLATION
|
|
|
|
|
|
|
|
This plugin has 3 modes :
|
|
|
|
- connections : monitors the number of connections
|
|
|
|
- bandwidth : monitors the bandwidth (up, up overhead, down, down overhead)
|
|
|
|
- states : monitors the torrents' states
|
|
|
|
|
|
|
|
To use one of these modes, link the this plugin as 'deluge_<mode>'
|
|
|
|
For example :
|
|
|
|
ln -s /path/to/deluge_ /etc/munin/plugins/deluge_connections
|
|
|
|
|
|
|
|
=head1 CONFIGURATION
|
|
|
|
|
|
|
|
Use your "/etc/munin/plugin-conf.d/munin-node" to configure this plugin.
|
|
|
|
You must at least add :
|
|
|
|
[deluge_*]
|
|
|
|
user <user_with_access_to_deluge>
|
|
|
|
env.HOME <path_to_deluge_user_home>
|
|
|
|
|
|
|
|
By default, this plugin will try to access the deluge daemon with the following
|
|
|
|
settings :
|
|
|
|
host 127.0.0.1
|
|
|
|
port 58846
|
|
|
|
no username
|
|
|
|
no password
|
|
|
|
|
|
|
|
You can change these settings in "plugin-conf.d/munin-node" :
|
|
|
|
[deluge_*]
|
|
|
|
user <user_with_access_to_deluge>
|
|
|
|
env.HOME <path_to_deluge_user_home>
|
|
|
|
env.host 127.0.0.1
|
|
|
|
env.port 58846
|
|
|
|
env.username user
|
|
|
|
env.password pass
|
|
|
|
|
|
|
|
By default, deluge configuration files will be searched under $XDG_CONFIG_HOME,
|
|
|
|
which is by default set to $HOME/.config
|
|
|
|
Setting env.HOME allows this default to work. However, you can also explicitly
|
|
|
|
set the env.XDG_CONFIG_HOME if needed.
|
|
|
|
|
|
|
|
=head1 INTERPRETATION
|
|
|
|
|
|
|
|
=head2 connections
|
|
|
|
|
|
|
|
In the "connections" mode, this plugin shows a graph with the number of
|
|
|
|
connections
|
|
|
|
|
|
|
|
=head2 bandwidth
|
|
|
|
|
|
|
|
In the "bandwidth" mode, this plugin show graphs for the download and upload
|
2017-07-21 10:57:09 +02:00
|
|
|
bandwidths.
|
2017-07-21 10:28:19 +02:00
|
|
|
Each of them has "payload" and "overhead" value.
|
2017-07-21 10:57:09 +02:00
|
|
|
- with positive values : the upload values
|
|
|
|
- with negative values : the download values
|
2017-07-21 10:28:19 +02:00
|
|
|
|
|
|
|
=head2 states
|
|
|
|
|
|
|
|
In the "states" mode, this plugin shows the number of torrents in each state :
|
|
|
|
Downloading, Seeding, Paused, Error, Queued, Checking, Other
|
|
|
|
|
|
|
|
=head1 MAGIC MARKERS
|
|
|
|
|
|
|
|
#%# family=auto
|
|
|
|
#%# capabilities=autoconf suggest
|
|
|
|
|
|
|
|
=head1 VERSION
|
|
|
|
|
|
|
|
1.0.0
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
|
|
|
Neraud (https://github.com/Neraud)
|
|
|
|
|
|
|
|
=head1 LICENSE
|
|
|
|
|
|
|
|
GPLv2
|
|
|
|
|
|
|
|
=cut"""
|
2017-07-08 19:15:01 +02:00
|
|
|
|
|
|
|
|
2017-07-18 07:36:17 +02:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import string
|
|
|
|
import sys
|
2017-07-18 07:37:58 +02:00
|
|
|
|
2017-07-21 10:24:09 +02:00
|
|
|
try:
|
|
|
|
from deluge.log import setupLogger
|
|
|
|
from deluge.ui.client import client
|
|
|
|
from twisted.internet import reactor, defer
|
|
|
|
setupLogger()
|
|
|
|
except (ImportError, NameError):
|
|
|
|
successful_import = False
|
|
|
|
else:
|
|
|
|
successful_import = True
|
2017-07-18 07:37:58 +02:00
|
|
|
|
2017-07-08 19:15:01 +02:00
|
|
|
|
|
|
|
plugin_version = "1.0.0"
|
|
|
|
|
|
|
|
log = logging.getLogger("delugeStats")
|
|
|
|
log.setLevel(logging.WARNING)
|
|
|
|
|
|
|
|
conf = {
|
2017-07-18 07:36:17 +02:00
|
|
|
'host': os.getenv('host', '127.0.0.1'),
|
|
|
|
'port': int(os.getenv('port', '58846')),
|
|
|
|
'username': os.getenv('username', ''),
|
|
|
|
'password': os.getenv('password', '')
|
2017-07-08 19:15:01 +02:00
|
|
|
}
|
|
|
|
|
2017-07-18 07:36:17 +02:00
|
|
|
names_for_munin = {
|
|
|
|
'numConnections': 'numConnections',
|
|
|
|
'payloadUploadRate': 'payloadUploadRate',
|
|
|
|
'overheadUploadRate': 'overheadUploadRate',
|
|
|
|
'payloadDownloadRate': 'payloadDownloadRate',
|
|
|
|
'overheadDownloadRate': 'overheadDownloadRate',
|
|
|
|
'state.Seeding': 'seeding',
|
|
|
|
'state.Downloading': 'downloading',
|
|
|
|
'state.Paused': 'paused',
|
|
|
|
'state.Error': 'error',
|
|
|
|
'state.Queued': 'queued',
|
|
|
|
'state.Checking': 'checking',
|
|
|
|
'state.Other': 'other'
|
2017-07-08 19:15:01 +02:00
|
|
|
}
|
|
|
|
|
2017-07-18 07:36:17 +02:00
|
|
|
torrent_states = ["Downloading",
|
|
|
|
"Seeding",
|
|
|
|
"Paused",
|
|
|
|
"Error",
|
|
|
|
"Queued",
|
|
|
|
"Checking",
|
|
|
|
"Other"]
|
|
|
|
|
|
|
|
modes = ["bandwidth", "connections", "states"]
|
2017-07-08 19:15:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
class StatClient:
|
2017-07-18 07:36:17 +02:00
|
|
|
|
|
|
|
def __init__(self, conf, mode):
|
|
|
|
self.conf = conf
|
|
|
|
self.mode = mode
|
|
|
|
self.connected = False
|
|
|
|
|
|
|
|
def end_session(self, msg):
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("end_session : %s", msg)
|
2017-07-18 07:36:17 +02:00
|
|
|
|
|
|
|
if self.connected:
|
|
|
|
log.debug("Disconnecting")
|
|
|
|
client.disconnect()
|
|
|
|
|
|
|
|
reactor.stop()
|
|
|
|
|
|
|
|
def fetch_info(self):
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("Connecting to %s:%d ...",
|
|
|
|
self.conf['host'], self.conf['port'])
|
2017-07-18 07:36:17 +02:00
|
|
|
client.connect(
|
|
|
|
self.conf['host'],
|
|
|
|
self.conf['port'],
|
|
|
|
self.conf['username'],
|
|
|
|
self.conf['password']).addCallbacks(
|
|
|
|
self.on_connect_success,
|
|
|
|
self.end_session,
|
|
|
|
errbackArgs=("Connection failed: check settings and try again."))
|
|
|
|
reactor.run()
|
|
|
|
|
|
|
|
def on_connect_success(self, result):
|
|
|
|
log.debug("Connection was successful")
|
|
|
|
self.connected = True
|
|
|
|
|
|
|
|
if self.mode == "connections":
|
|
|
|
log.debug("Calling get_num_connections")
|
|
|
|
client.core.get_num_connections().addCallbacks(
|
|
|
|
self.on_num_connections,
|
|
|
|
self.end_session, errbackArgs=("get_num_connections failed"))
|
|
|
|
elif self.mode == "bandwidth":
|
|
|
|
log.debug("Calling get_session_status")
|
|
|
|
interesting_status = [
|
|
|
|
'upload_rate', 'payload_upload_rate',
|
|
|
|
'download_rate', 'payload_download_rate']
|
|
|
|
client.core.get_session_status(interesting_status).addCallbacks(
|
|
|
|
self.on_bandwidth,
|
|
|
|
self.end_session,
|
|
|
|
errbackArgs=("get_session_status failed"))
|
|
|
|
elif self.mode == "states":
|
|
|
|
log.debug("Calling get_session_state")
|
|
|
|
client.core.get_session_state().addCallbacks(
|
|
|
|
self.on_session_state,
|
|
|
|
self.end_session,
|
|
|
|
errbackArgs=("get_session_state failed"))
|
|
|
|
|
|
|
|
def on_num_connections(self, num_connections):
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("Got num_connections from the daemon : %s", num_connections)
|
2017-07-18 07:37:58 +02:00
|
|
|
print("{0}.value {1}".format(
|
|
|
|
names_for_munin["numConnections"], num_connections))
|
2017-07-18 07:36:17 +02:00
|
|
|
self.end_session("Done")
|
|
|
|
|
|
|
|
def on_bandwidth(self, values):
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("Got bandwidth info from the daemon : %s", values)
|
2017-07-18 07:36:17 +02:00
|
|
|
|
|
|
|
download_rate = values['download_rate']
|
|
|
|
payload_download_rate = values['payload_download_rate']
|
|
|
|
overhead_download_rate = download_rate - payload_download_rate
|
|
|
|
upload_rate = values['upload_rate']
|
|
|
|
payload_upload_rate = values['payload_upload_rate']
|
|
|
|
overhead_upload_rate = upload_rate - payload_upload_rate
|
|
|
|
|
2017-07-18 07:37:58 +02:00
|
|
|
print("{0}.value {1}".format(
|
|
|
|
names_for_munin["payloadDownloadRate"], payload_download_rate))
|
|
|
|
print("{0}.value {1}".format(
|
|
|
|
names_for_munin["overheadDownloadRate"], overhead_download_rate))
|
|
|
|
print("{0}.value {1}".format(
|
|
|
|
names_for_munin["payloadUploadRate"], payload_upload_rate))
|
|
|
|
print("{0}.value {1}".format(
|
|
|
|
names_for_munin["overheadUploadRate"], overhead_upload_rate))
|
2017-07-18 07:36:17 +02:00
|
|
|
self.end_session("Done")
|
|
|
|
|
|
|
|
def on_session_state(self, torrent_ids):
|
|
|
|
log.debug("Got session state from the daemon")
|
|
|
|
|
|
|
|
self.states = {}
|
|
|
|
for state_name in torrent_states:
|
|
|
|
self.states[state_name] = 0
|
|
|
|
|
|
|
|
deferred_list = []
|
|
|
|
for torrent_id in torrent_ids:
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug(" - TorrentId : %s", torrent_id)
|
2017-07-18 07:36:17 +02:00
|
|
|
d = client.core.get_torrent_status(torrent_id, ['state'])
|
|
|
|
d.addCallback(self.on_one_torrent_info, torrent_id)
|
|
|
|
d.addErrback(self.on_one_torrent_info_failed, torrent_id)
|
|
|
|
deferred_list.append(d)
|
|
|
|
|
|
|
|
defer.DeferredList(deferred_list).addCallback(
|
|
|
|
self.on_all_torrent_info_fetched)
|
|
|
|
|
|
|
|
def on_one_torrent_info_failed(self, torrent_id):
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("Failed fetching torrent info %s", torrent_id)
|
2017-07-18 07:36:17 +02:00
|
|
|
self.state["Error"] = self.state["Error"] + 1
|
|
|
|
|
|
|
|
def on_one_torrent_info(self, value, torrent_id):
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("Got torrent info : %s -> %s", torrent_id, value)
|
2017-07-30 09:32:02 +02:00
|
|
|
state = value.get("state", "Error")
|
2017-07-18 07:36:17 +02:00
|
|
|
|
2017-07-30 09:32:02 +02:00
|
|
|
if state not in self.states:
|
2017-07-30 09:38:26 +02:00
|
|
|
log.warn("State '%s' is unknown !", state)
|
2017-07-18 07:36:17 +02:00
|
|
|
state = "Other"
|
|
|
|
|
2017-07-30 09:32:02 +02:00
|
|
|
self.states[state] += 1
|
2017-07-18 07:36:17 +02:00
|
|
|
|
|
|
|
def on_all_torrent_info_fetched(self, res):
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("on_all_torrent_info_fetched : %s", self.states)
|
2017-07-18 07:36:17 +02:00
|
|
|
|
|
|
|
for state in self.states:
|
2017-07-18 07:37:58 +02:00
|
|
|
print("{0}.value {1}".format(
|
|
|
|
names_for_munin["state." + state], self.states[state]))
|
2017-07-18 07:36:17 +02:00
|
|
|
|
|
|
|
self.end_session("Done")
|
|
|
|
|
|
|
|
|
|
|
|
def get_mode():
|
2017-07-18 07:37:58 +02:00
|
|
|
script_name = os.path.basename(sys.argv[0])
|
2017-07-18 07:36:17 +02:00
|
|
|
mode = script_name[string.rindex(script_name, '_') + 1:]
|
|
|
|
|
2017-07-30 09:38:26 +02:00
|
|
|
log.debug("Mode : %s", mode)
|
2017-07-18 07:36:17 +02:00
|
|
|
|
2017-07-18 07:37:58 +02:00
|
|
|
if mode not in modes:
|
2017-07-30 09:38:26 +02:00
|
|
|
log.error("Unknown mode '%s'", mode)
|
|
|
|
log.info("Available modes are : %s", modes)
|
2017-07-18 07:36:17 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
return mode
|
|
|
|
|
|
|
|
|
|
|
|
def print_config(mode):
|
|
|
|
if mode == "connections":
|
|
|
|
print("graph_title Number of connections")
|
|
|
|
print("graph_args --base 1000 -l 0")
|
|
|
|
print("graph_vlabel connections")
|
|
|
|
print("graph_scale yes")
|
2017-07-21 10:35:56 +02:00
|
|
|
print("graph_category filetransfer")
|
2017-07-18 07:36:17 +02:00
|
|
|
print(
|
|
|
|
"graph_info This graph shows the number of connections used by Deluge Torrent")
|
|
|
|
print(names_for_munin["numConnections"] + ".label connections")
|
|
|
|
print(names_for_munin["numConnections"] + ".min 0")
|
|
|
|
print(names_for_munin["numConnections"] +
|
|
|
|
".info The number of connections used by Deluge Torrent")
|
|
|
|
elif mode == "bandwidth":
|
|
|
|
print("graph_title Bandwidth usage")
|
2017-07-21 10:57:09 +02:00
|
|
|
print("graph_order payloadDownloadRate overheadDownloadRate payloadUploadRate overheadUploadRate")
|
2017-07-18 07:36:17 +02:00
|
|
|
print("graph_args --base 1024 -r")
|
2017-07-21 10:57:09 +02:00
|
|
|
print("graph_vlabel bytes/s : down(-) and up(+)")
|
2017-07-18 07:36:17 +02:00
|
|
|
print("graph_scale yes")
|
|
|
|
print("graph_info This graph shows the bandwidth used by Deluge Torrent")
|
2017-07-21 10:35:56 +02:00
|
|
|
print("graph_category filetransfer")
|
2017-07-18 07:36:17 +02:00
|
|
|
print("graph_period second")
|
|
|
|
|
|
|
|
print("payloadDownloadRate.label payload")
|
|
|
|
print("payloadDownloadRate.draw AREA")
|
|
|
|
print("payloadDownloadRate.min 0")
|
2017-07-21 10:57:09 +02:00
|
|
|
print("payloadDownloadRate.graph no")
|
2017-07-18 07:36:17 +02:00
|
|
|
print("payloadDownloadRate.info Bandwidth used to download / upload torrents")
|
|
|
|
|
|
|
|
print("overheadDownloadRate.label overhead")
|
|
|
|
print("overheadDownloadRate.draw STACK")
|
|
|
|
print("overheadDownloadRate.min 0")
|
2017-07-21 10:57:09 +02:00
|
|
|
print("overheadDownloadRate.graph no")
|
2017-07-18 07:36:17 +02:00
|
|
|
print("overheadDownloadRate.info Bandwidth 'lost' due to overhead while downloading and uploading torrents")
|
2017-07-21 10:57:09 +02:00
|
|
|
|
|
|
|
print("payloadUploadRate.label payload")
|
|
|
|
print("payloadUploadRate.draw AREA")
|
|
|
|
print("payloadUploadRate.min 0")
|
|
|
|
print("payloadUploadRate.negative payloadDownloadRate")
|
|
|
|
print("payloadUploadRate.info Bandwidth used to upload torrents")
|
|
|
|
|
|
|
|
print("overheadUploadRate.label overhead")
|
|
|
|
print("overheadUploadRate.draw STACK")
|
|
|
|
print("overheadUploadRate.min 0")
|
|
|
|
print("overheadUploadRate.negative overheadDownloadRate")
|
|
|
|
print("overheadUploadRate.info Bandwidth 'lost' due to overhead while downloading and uploading torrents")
|
2017-07-18 07:36:17 +02:00
|
|
|
elif mode == "states":
|
|
|
|
print("graph_title Torrents states")
|
|
|
|
|
2017-07-18 07:37:58 +02:00
|
|
|
graph_order = " ".join(
|
|
|
|
[names_for_munin["state.{}".format(name)] for name in torrent_states])
|
2017-07-18 07:36:17 +02:00
|
|
|
|
|
|
|
print("graph_order " + graph_order)
|
|
|
|
print("graph_args --base 1000 -r --lower-limit 0")
|
|
|
|
print("graph_vlabel number of torrents")
|
|
|
|
print("graph_scale yes")
|
|
|
|
print("graph_info This graph shows the states of the torrents in Deluge Torrent")
|
2017-07-21 10:35:56 +02:00
|
|
|
print("graph_category filetransfer")
|
2017-07-18 07:36:17 +02:00
|
|
|
print("graph_period second")
|
|
|
|
|
|
|
|
for state_name in torrent_states:
|
|
|
|
print(names_for_munin["state." +
|
|
|
|
state_name] + ".label " + state_name)
|
2017-07-21 10:57:09 +02:00
|
|
|
print(names_for_munin["state." + state_name] + ".draw AREASTACK")
|
2017-07-18 07:36:17 +02:00
|
|
|
print(names_for_munin["state." + state_name] + ".type GAUGE")
|
|
|
|
print(names_for_munin["state." + state_name] + ".min 0")
|
|
|
|
print(names_for_munin["state." + state_name] +
|
|
|
|
".info Number of torrents in the '" + state_name + "' state")
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_info(mode):
|
2017-07-21 10:24:09 +02:00
|
|
|
if not successful_import:
|
|
|
|
print('Missing imports, cannot run !')
|
|
|
|
sys.exit(1)
|
|
|
|
|
2017-07-18 07:36:17 +02:00
|
|
|
log.debug("Launching tests")
|
|
|
|
c = StatClient(conf, mode)
|
|
|
|
c.fetch_info()
|
|
|
|
|
2017-07-08 19:15:01 +02:00
|
|
|
|
|
|
|
# Parse arguments
|
2017-07-18 07:36:17 +02:00
|
|
|
if len(sys.argv) > 1:
|
|
|
|
action = sys.argv[1]
|
|
|
|
if action == "config":
|
|
|
|
print_config(get_mode())
|
|
|
|
sys.exit(0)
|
|
|
|
elif action == "autoconf":
|
2017-07-21 10:24:09 +02:00
|
|
|
if not successful_import:
|
|
|
|
print('no (required modules not found)')
|
2017-07-18 07:36:17 +02:00
|
|
|
sys.exit(1)
|
2017-07-21 10:24:09 +02:00
|
|
|
print('yes')
|
2017-07-18 07:36:17 +02:00
|
|
|
elif action == "suggest":
|
|
|
|
for mode in modes:
|
|
|
|
print(mode)
|
|
|
|
sys.exit(0)
|
|
|
|
elif action == "version":
|
|
|
|
print('Deluge Torrent Munin plugin, version {0}'.format(
|
|
|
|
plugin_version))
|
|
|
|
sys.exit(0)
|
2017-07-30 09:32:02 +02:00
|
|
|
elif action:
|
2017-07-30 09:38:26 +02:00
|
|
|
log.warn("Unknown argument '%s'", action)
|
2017-07-18 07:36:17 +02:00
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
fetch_info(get_mode())
|
|
|
|
else:
|
|
|
|
fetch_info(get_mode())
|