Repository
Munin (contrib)
Last change
2021-08-17
Graph Categories
Family
auto
Capabilities
Keywords
Language
Python (3.x)
License
GPL-2.0-only
Authors

deluge_

Name

deluge_ - Munin wildcard plugin to monitor the Deluge torrent client

Requirements

- Python3+
- Deluge2+

This plugin also uses

  • deluge.ui.client
  • deluge.log
  • twisted These modules are required by Deluge itself.

Installation

This plugin has 3 modes :

  • peers : monitors the number of peers
  • 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_peers

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.

Interpretation

Peers

In the “peers” mode, this plugin shows a graph with the number of total peers, half open peers, and peers interested in upload and download slots

Bandwidth

In the “bandwidth” mode, this plugin show graphs for the download and upload bandwidths. Each of them has “payload” and “overhead” value.

  • with positive values : the upload values
  • with negative values : the download values

States

In the “states” mode, this plugin shows the number of torrents in the states: Seeding, Uploading, Downloading, Checking, Stopped, Queued seeding, Queued downloads, Errors

Magic Markers

#%# family=auto
#%# capabilities=autoconf suggest

Version

2.0.0

Author

Neraud (https://github.com/Neraud) kmkr (https://github.com/kmkr)

License

GPLv2

#! /usr/bin/env python3

"""=cut
=head1 NAME

deluge_ - Munin wildcard plugin to monitor the Deluge torrent client

=head1 REQUIREMENTS

 - Python3+
 - Deluge2+

This plugin also uses
 - deluge.ui.client
 - deluge.log
 - twisted
These modules are required by Deluge itself.

=head1 INSTALLATION

This plugin has 3 modes :
 - peers : monitors the number of peers
 - 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_peers

=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 peers

In the "peers" mode, this plugin shows a graph with the number of
total peers, half open peers, and peers interested in upload and download slots

=head2 bandwidth

In the "bandwidth" mode, this plugin show graphs for the download and upload
bandwidths.
Each of them has "payload" and "overhead" value.
 - with positive values : the upload values
 - with negative values : the download values

=head2 states

In the "states" mode, this plugin shows the number of torrents in the states:
Seeding, Uploading, Downloading, Checking, Stopped, Queued seeding, Queued downloads, Errors

=head1 MAGIC MARKERS

  #%# family=auto
  #%# capabilities=autoconf suggest

=head1 VERSION

2.0.0

=head1 AUTHOR

Neraud (https://github.com/Neraud)
kmkr (https://github.com/kmkr)

=head1 LICENSE

GPLv2

=cut"""


from __future__ import print_function

import logging
import os
import sys


try:
    from deluge.log import setup_logger
    from deluge.ui.client import client
    from twisted.internet import reactor

    setup_logger()
except (ImportError, NameError):
    successful_import = False
else:
    successful_import = True


plugin_version = "2.0.0"

log = logging.getLogger("delugeStats")
log.setLevel(logging.WARNING)

conf = {
    "host": os.getenv("host", "127.0.0.1"),
    "port": int(os.getenv("port", "58846")),
    "username": os.getenv("username", ""),
    "password": os.getenv("password", ""),
}

modes = ["bandwidth", "peers", "states"]

connection_keys = [
    {"id": "peer.num_peers_connected", "label": "Total"},
    {"id": "peer.num_peers_half_open", "label": "Half open"},
    {"id": "peer.num_peers_up_interested", "label": "Interested (upload)"},
    {"id": "peer.num_peers_down_interested", "label": "Interested (download)"},
]

torrent_keys = [
    {"id": "ses.num_seeding_torrents", "label": "Seeding"},
    {"id": "ses.num_upload_only_torrents", "label": "Uploading"},
    {"id": "ses.num_downloading_torrents", "label": "Downloading"},
    {"id": "ses.num_checking_torrents", "label": "Checking"},
    {"id": "ses.num_stopped_torrents", "label": "Stopped"},
    {"id": "ses.num_queued_seeding_torrents", "label": "Queued seeding"},
    {"id": "ses.num_queued_download_torrents", "label": "Queued downloads"},
    {"id": "ses.num_error_torrents", "label": "Error"},
]


def for_munin(value):
    return value.replace(".", "").replace("_", "")


class StatClient:
    def __init__(self, conf, mode):
        self.conf = conf
        self.mode = mode
        self.connected = False

    def end_session(self, msg):
        log.debug("end_session : %s", msg)

        if self.connected:
            log.debug("Disconnecting")
            client.disconnect()

        reactor.stop()

    def fetch_info(self):
        log.debug("Connecting to %s:%d ...", self.conf["host"], self.conf["port"])
        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 == "peers":
            log.debug("Calling get_session_status")
            keys = []
            for connection_key in connection_keys:
                keys.append(connection_key["id"])
            client.core.get_session_status(keys=keys).addCallbacks(
                self.on_peer_session_status,
                self.end_session,
                errbackArgs=("get_session_status 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")
            keys = []
            for torrent_key in torrent_keys:
                keys.append(torrent_key["id"])
            client.core.get_session_status(keys=keys).addCallbacks(
                self.on_torrent_session_state,
                self.end_session,
                errbackArgs=("get_session_state failed"),
            )
        else:
            log.error("Unknown mode '%s'", mode)
            sys.exit(1)

    def on_peer_session_status(self, result):
        log.debug("Got result from the daemon : %s", result)
        for connection_key in connection_keys:
            print(
                f"{for_munin(connection_key['id'])}.value "
                f"{result[connection_key['id']]}"
            )
        self.end_session("Done")

    def on_bandwidth(self, values):
        log.debug("Got bandwidth info from the daemon : %s", values)

        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

        print(f"payloadDownloadRate.value {payload_download_rate}")
        print(f"overheadDownloadRate.value {overhead_download_rate}")
        print(f"payloadUploadRate.value {payload_upload_rate}")
        print(f"overheadUploadRate.value {overhead_upload_rate}")
        self.end_session("Done")

    def on_torrent_session_state(self, result):
        log.debug("Got torrent session state from the daemon", result)

        for torrent_key in torrent_keys:
            print(
                f"{for_munin(torrent_key['id'])}.value " f"{result[torrent_key['id']]}"
            )
        self.end_session("Done")


def get_mode():
    script_name = os.path.basename(sys.argv[0])
    mode = script_name[script_name.rindex("_") + 1 :]

    log.debug("Mode : %s", mode)

    if mode not in modes:
        log.error("Unknown mode '%s'", mode)
        log.info("Available modes are : %s", modes)
        sys.exit(1)

    return mode


def print_config(mode):
    if mode == "peers":
        print("graph_title Number of peers")
        print("graph_args --base 1000 -l 0")
        print("graph_vlabel peers")
        print("graph_scale yes")
        print("graph_category filetransfer")
        print(
            "graph_info This graph shows the number of peers for the Deluge Torrent client"
        )
        for connection_key in connection_keys:
            print(f"{for_munin(connection_key['id'])}.label {connection_key['label']}")
            print(f"{for_munin(connection_key['id'])}.min 0")
    elif mode == "bandwidth":
        print("graph_title Bandwidth usage")
        print(
            "graph_order payloadDownloadRate overheadDownloadRate payloadUploadRate "
            "overheadUploadRate"
        )
        print("graph_args --base 1024 -r")
        print("graph_vlabel bytes/s : down(-) and up(+)")
        print("graph_scale yes")
        print("graph_info This graph shows the bandwidth used by Deluge Torrent")
        print("graph_category filetransfer")
        print("graph_period second")

        print("payloadDownloadRate.label payload")
        print("payloadDownloadRate.draw AREA")
        print("payloadDownloadRate.min 0")
        print("payloadDownloadRate.graph no")
        print("payloadDownloadRate.info Bandwidth used to download / upload torrents")

        print("overheadDownloadRate.label overhead")
        print("overheadDownloadRate.draw STACK")
        print("overheadDownloadRate.min 0")
        print("overheadDownloadRate.graph no")
        print(
            "overheadDownloadRate.info Bandwidth 'lost' due to overhead while downloading and "
            "uploading torrents"
        )

        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"
        )
    elif mode == "states":
        print("graph_title Torrent states")
        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"
        )
        print("graph_category filetransfer")
        print("graph_period second")

        for torrent_key in torrent_keys:
            print(f"{for_munin(torrent_key['id'])}.label {torrent_key['label']}")
            print(f"{for_munin(torrent_key['id'])}.min 0")
            print(f"{for_munin(torrent_key['id'])}.draw AREASTACK")
            print(f"{for_munin(torrent_key['id'])}.type GAUGE")


def fetch_info(mode):
    if not successful_import:
        print("Missing imports, cannot run !", file=sys.stderr)
        sys.exit(1)

    log.debug("Launching tests")
    c = StatClient(conf, mode)
    c.fetch_info()


# Parse arguments
if len(sys.argv) > 1:
    action = sys.argv[1]
    if action == "config":
        print_config(get_mode())
        sys.exit(0)
    elif action == "autoconf":
        if not successful_import:
            print("no (required modules not found)")
            sys.exit(0)
        print("yes")
    elif action == "suggest":
        for mode in modes:
            print(mode)
        sys.exit(0)
    elif action == "version":
        print(f"Deluge Torrent Munin plugin, version {plugin_version}")
        sys.exit(0)
    elif action:
        log.warn("Unknown argument '%s'", action)
        sys.exit(1)
    else:
        fetch_info(get_mode())
else:
    fetch_info(get_mode())