Repository
Munin (contrib)
Last change
2020-10-24
Graph Categories
Family
contrib
Capabilities
Keywords
Language
Python (2.x)
License
GPL-3.0-or-later

nanopool_

Name

nanopool_ - Munin plugin to monitor nanopool ethereum user data.

Configuration

[nanopool_*]

    - Copy to /usr/share/munin/plugins
    - Create symlinks in /etc/munin/plugins to nanopool_<graph type>_<account_address>,
      e.g. ln -s /usr/share/munin/plugins/nanopool_ /etc/munin/plugins/nanopool_hashrate_0x1234567890abcdef1234567890
      Please use the account address in the format "0x<address>".

    - To enable graphs, link to one or more of the graph types available
    - Then restart munin-node

Graph types and their links: balance: link to nanopool_balance Show current balance hashrate: link to nanopool_hashrate Show current calculated hashrate avghashrate: link to nanopool_avghashrate Show average hashrate of last hour worker: link to nanopool_worker Show worker data

Thanks to the authors of other Python munin plugins. I’ve used some of them as inspiring example.

Version

1.0.1

Author

Ralf Geschke

License

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.

SPDX-License-Identifier: GPL-3.0-or-later

Magic Markers

#%# family=contrib
#%# capabilities=suggest
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: set fileencoding=utf-8

"""
=head1 NAME

nanopool_ - Munin plugin to monitor nanopool ethereum user data.


=head1 CONFIGURATION

[nanopool_*]

        - Copy to /usr/share/munin/plugins
        - Create symlinks in /etc/munin/plugins to nanopool_<graph type>_<account_address>,
          e.g. ln -s /usr/share/munin/plugins/nanopool_ /etc/munin/plugins/nanopool_hashrate_0x1234567890abcdef1234567890
          Please use the account address in the format "0x<address>".

        - To enable graphs, link to one or more of the graph types available
        - Then restart munin-node

Graph types and their links:
        balance: link to nanopool_balance           Show current balance
        hashrate: link to nanopool_hashrate         Show current calculated hashrate
        avghashrate: link to nanopool_avghashrate   Show average hashrate of last hour
        worker: link to nanopool_worker             Show worker data


Thanks to the authors of other Python munin plugins. I've used some of
them as inspiring example.

=head1 VERSION
1.0.1

=head1 AUTHOR
L<Ralf Geschke|https://github.com/geschke>

=head1 LICENSE

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

SPDX-License-Identifier: GPL-3.0-or-later

=head1 MAGIC MARKERS

 #%# family=contrib
 #%# capabilities=suggest

=cut
"""

from __future__ import print_function

import os
import sys
import json

try:
    # Python 3
    from urllib.request import Request
    from urllib.request import urlopen
    from urllib.request import URLError
except:
    # Python 2
    from urllib2 import Request
    from urllib2 import urlopen
    from urllib2 import URLError



def define_graph_types():
    graph_types = {
        "balance" : [
            {
                "title" : "Balance of {0}".format(account_address),
                "type" : "GAUGE",
                "args" : "--base 1000 -l 0",
                "fields" : ["ETH"],
                "scale": "no",
                "info": "Balance in ETH"
            }
        ],
        "hashrate" : [
            {
                "title" : "Hashrate of {0}".format(account_address),
                "type" : "GAUGE",
                "args" : "--base 1000 -l 0",
                "fields" : ["hashrate"],
                "info": "Current Calculated Hashrate in Mh/s"
            }
        ],
        "avghashrate" : [
            {
                "title" : "Average Hashrate of {0}".format(account_address),
                "type" : "GAUGE",
                "args" : "--base 1000 -l 0",
                "fields" : ["avg_hashrate"],
                "info": "Average Hashrate of last hour in Mh/s"

            }
        ],
        "worker" : [
            {
                "title" : "Worker"
            }
        ]
    }
    return graph_types


def request_data():

    url = "https://api.nanopool.org/v1/eth/user/{0}".format(account_address)

    req = Request(url)
    # this fake is necessary to get values, otherwise the request ends in a 403 error
    req.add_header('User-Agent', 'Nozilla/5.0')
    try:
        txt = urlopen(req).read()
    except URLError as err:
        print("API request error: {0}". format(err), file=sys.stderr)
        exit(1)
    except:
        print("Unhandled error:", sys.exc_info()[0])
        exit(1)
    try:
        result = json.loads(txt.decode("utf-8"))
    except ValueError as err:
        print("Could not decode JSON error: {0}". format(err), file=sys.stderr)
        exit(1)
    return result

def write_config_worker():
    data = request_data()
    worker_data = sorted(data["data"]["workers"], key=lambda element: element["id"])

    print("multigraph worker_hashrate_{0}".format(account_address))
    print("graph_title Hashrate in Mh/s per worker ({0})".format(account_address))
    print("graph_args --base 1000 -l 0")
    print("graph_vlabel Mh/s")
    print("graph_category htc")
    print("graph_scale no")

    for val in worker_data:
        worker_name = "_".join(val["id"].split())
        print("worker_{0}_hashrate.label {1}".format(worker_name, val["id"]))
        print("worker_{0}_hashrate.type GAUGE".format(worker_name))
        print("worker_{0}_hashrate.info Hashrate of worker '{1}'".format(worker_name, val["id"]))
        print("worker_{0}_hashrate.min 0".format(worker_name))
        print("worker_{0}_hashrate.draw LINE1".format(worker_name))

    for val in worker_data:
        print("")
        worker_name = "_".join(val["id"].split())
        print("multigraph worker_hashrate_{0}.worker_{1}".format(account_address, worker_name))
        print("graph_title Hashrate in Mh/s of worker {0}".format(worker_name))
        print("graph_args --base 1000 -l 0")
        print("graph_vlabel Mh/s")
        print("graph_category htc")
        print("graph_scale no")
        print("whashrate.label hashrate")
        print("whashrate.type GAUGE")
        print("whashrate.info Hashrate of worker {0}".format(val["id"]))
        print("whashrate.min 0")
        print("whashrate.draw LINE1")

    print("")
    print("multigraph worker_shares_{0}".format(account_address))
    print("graph_title Number of accepted shares ({0})".format(account_address))
    print("graph_args --base 1000 -l 0")
    print("graph_vlabel Shares per ${graph_period}")
    print("graph_category htc")
    print("graph_scale no")
    print("graph_period minute")

    for val in worker_data:
        worker_name = "_".join(val["id"].split())
        print("worker_{0}_shares.label {1} ".format(worker_name, val["id"]))
        print("worker_{0}_shares.type COUNTER".format(worker_name))
        print("worker_{0}_shares.info Accepted shares of worker '{1}'".format(worker_name, val["id"]))
        print("worker_{0}_shares.min 0".format(worker_name))
        print("worker_{0}_shares.draw LINE1".format(worker_name))

    for val in worker_data:
        worker_name = "_".join(val["id"].split())
        print("")
        print("multigraph worker_shares_{0}.worker_{1}".format(account_address, worker_name))
        print("graph_title Number of accepted shares {0}".format(worker_name))
        print("graph_args --base 1000 -l 0")
        print("graph_vlabel Shares per ${graph_period}")
        print("graph_category htc")
        print("graph_scale no")
        print("graph_period minute")
        print("wshares.label shares")
        print("wshares.type COUNTER")
        print("wshares.info Accepted shares of worker '{0}'".format(val["id"]))
        print("wshares.min 0")
        print("wshares.draw LINE1")


def write_data_worker(data):
    worker_data = sorted(data["data"]["workers"], key=lambda element: element["id"])

    print("multigraph worker_hashrate_{0}".format(account_address))

    for val in worker_data:
        worker_name = "_".join(val["id"].split())
        print("worker_{0}_hashrate.value {1}".format(worker_name, val["hashrate"]))

    for val in worker_data:
        print("")
        worker_name = "_".join(val["id"].split())
        print("multigraph worker_hashrate_{0}.worker_{1}".format(account_address, worker_name))
        print("whashrate.value {0}".format(val["hashrate"]))

    print("")
    print("multigraph worker_shares_{0}".format(account_address))
    for val in worker_data:
        worker_name = "_".join(val["id"].split())
        print("worker_{0}_shares.value {1}".format(worker_name, val["rating"]))

    for val in worker_data:
        worker_name = "_".join(val["id"].split())
        print("")
        print("multigraph worker_shares_{0}.worker_{1}".format(account_address, worker_name))
        print("wshares.value {0} ".format(val["rating"]))



def write_config():
    if graph_type not in GRAPH_TYPES.keys():
        print("Unknown graph type '{0}'".format(graph_type), file=sys.stderr)
        exit(1)
    if graph_type == "worker":
        write_config_worker()
        return
    params = GRAPH_TYPES[graph_type]
    for item in params:
        print("graph_title {0}".format(item["title"]))
        print("graph_category htc")
        if "info" in item:
            print("graph_info {0}".format(item["info"]))
        if "scale" in item:
            print("graph_scale {0}".format(item["scale"]))
        if "args" in item:
            print("graph_args {0}".format(item["args"]))
        for field in item["fields"]:
            print("{0}.label {1}".format(field, field))
            print("{0}.type {1}".format(field, item["type"]))

def write_suggest():
    for item in GRAPH_TYPES.keys():
        print(item)

def write_data():
    data = request_data()

    if graph_type == "balance":
        print("ETH.value {0}".format(data['data']['balance']))
    elif graph_type == "hashrate":
        print("hashrate.value {0}".format(data["data"]["hashrate"]))
    elif graph_type == "avghashrate":
        print("avg_hashrate.value {0}".format(data["data"]["avgHashrate"]["h1"]))
    elif graph_type == "worker":
        write_data_worker(data)
    else:
        pass


if __name__ == "__main__":

    program = sys.argv[0]
    try:
        graph_type, account_address = program.split("_")[1:]
    except ValueError:
        print("Please configure the plugin as described in the configuration section.")
        exit(1)

    GRAPH_TYPES = define_graph_types()
    if len(sys.argv) > 1 and sys.argv[1] == "config":
        write_config()
    elif len(sys.argv) > 1 and sys.argv[1] == "suggest":
        write_suggest()
    else:
        write_data()