Source code for powerpool.monitor

from flask import Flask, jsonify, abort, send_from_directory, url_for
from cryptokit.block import BlockTemplate
from cryptokit.transaction import Transaction
from gevent.wsgi import WSGIServer, WSGIHandler
from collections import deque

from .utils import time_format
from .lib import Component

import decimal
import os


class Logger(object):
    """ A dummy file object to allow using a logger to log requests instead
    of sending to stderr like the default WSGI logger """
    logger = None

    def write(self, s):
        self.logger.info(s.strip())


class CustomWSGIHandler(WSGIHandler):
    """ A simple custom handler allows us to provide more helpful request
    logging format. Format designed for easy profiling """
    def format_request(self):
        length = self.response_length or '-'
        delta = time_format(self.time_finish - self.time_start)
        client_address = self.client_address[0] if isinstance(self.client_address, tuple) else self.client_address
        return '%s "%s" %s %s %s' % (
            client_address or '-',
            getattr(self, 'requestline', ''),
            (getattr(self, 'status', None) or '000').split()[0],
            length,
            delta)


class ReverseProxied(object):
    '''Wrap the application in this middleware and configure the
    front-end server to add these headers, to let you quietly bind
    this to a URL other than / and to an HTTP scheme that is
    different than what is used locally.

    In nginx:
    location /myprefix {
        proxy_pass http://192.168.0.1:5001;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Script-Name /myprefix;
        }

    :param app: the WSGI application
    '''
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
        if script_name:
            environ['SCRIPT_NAME'] = script_name
            path_info = environ['PATH_INFO']
            if path_info.startswith(script_name):
                environ['PATH_INFO'] = path_info[len(script_name):]

        scheme = environ.get('HTTP_X_SCHEME', '')
        if scheme:
            environ['wsgi.url_scheme'] = scheme
        return self.app(environ, start_response)


[docs]class ServerMonitor(Component, WSGIServer): """ Provides a few useful json endpoints for viewing server health and performance. """ # Use our custom wsgi handler handler_class = CustomWSGIHandler defaults = dict(address="127.0.0.1", port=3855, JSON_SORT_KEYS=False, JSONIFY_PRETTYPRINT_REGULAR=False, DEBUG=False) def __init__(self, config): self._configure(config) app = Flask(__name__) app.wsgi_app = ReverseProxied(app.wsgi_app) app.config.update(self.config) app.add_url_rule('/', 'general', self.general) app.add_url_rule('/debug/', 'debug', self.debug) app.add_url_rule('/counters/', 'counters', self.counters) app.add_url_rule('/<comp_key>/clients/', 'clients_comp', self.clients_comp) app.add_url_rule('/<comp_key>/client/<username>', 'client', self.client) app.add_url_rule('/<comp_key>/', 'comp', self.comp) app.add_url_rule('/<comp_key>/config', 'comp_config', self.comp_config) # Legacy app.add_url_rule('/05/clients/', 'clients', self.clients_0_5) app.add_url_rule('/05/', 'general_0_5', self.general_0_5) self.viewer_dir = os.path.join(os.path.abspath( os.path.dirname(__file__) + '/../'), 'viewer') self.app = app
[docs] def start(self, *args, **kwargs): listener = (self.config['address'], self.config['port'] + self.manager.config['server_number']) WSGIServer.__init__(self, listener, self.app, spawn=100, log=Logger()) self.logger.info("Monitoring port listening on {}".format(listener)) # Monkey patch the wsgi logger Logger.logger = self.logger WSGIServer.start(self, *args, **kwargs) Component.start(self)
[docs] def stop(self, *args, **kwargs): WSGIServer.stop(self) Component.stop(self) self.logger.info("Exit")
[docs] def debug(self): data = {} for key, comp in self.manager.components.iteritems(): data[key] = jsonize(comp.__dict__) return jsonify(data)
[docs] def general(self): from .stratum_server import StratumServer data = {} for key, comp in self.manager.components.iteritems(): dict_key = "{}_{}".format(comp.__class__.__name__, key) try: data[dict_key] = comp.status data[dict_key]['config_view'] = url_for( 'comp_config', comp_key=key, _external=True) if isinstance(comp, StratumServer): data[dict_key]['clients'] = url_for( 'clients_comp', comp_key=key, _external=True) except Exception as e: err = "Component {} status call raised {}".format(key, e) data[dict_key] = err self.logger.error(err, exc_info=True) data['debug_view'] = url_for('debug', _external=True) data['counter_view'] = url_for('counters', _external=True) return jsonify(jsonize(data))
[docs] def client(self, comp_key, username): try: component = self.manager.components[comp_key] except KeyError: abort(404) return jsonify(username=[client.details for client in component.address_lut.get(username, [])])
[docs] def comp_config(self, comp_key): try: return jsonify(**jsonize(self.manager.components[comp_key].config)) except KeyError: abort(404)
[docs] def comp(self, comp_key): try: return jsonify(**jsonize(self.manager.components[comp_key].status)) except KeyError: abort(404)
[docs] def clients_comp(self, comp_key): try: lut = self.manager.components[comp_key].address_lut except KeyError: abort(404) clients = {} for username, client_list in lut.iteritems(): clients[username] = {client._id: client.summary for client in client_list} clients[username]['details_view'] = url_for( 'client', comp_key=comp_key, username=username, _external=True) return jsonify(clients=clients)
[docs] def counters(self): counters = [] counters.extend(c.summary() for c in self.manager._min_stat_counters) counters.extend(c.summary() for c in self.manager._sec_stat_counters) return jsonify(counters=counters)
[docs] def clients_0_5(self): """ Legacy client view emulating version 0.5 support """ lut = self.manager.component_types['StratumServer'][0].address_lut clients = {key: [item.summary for item in value] for key, value in lut.iteritems()} return jsonify(clients=clients)
[docs] def general_0_5(self): """ Legacy 0.5 emulating view """ return jsonify(server={}, stratum_manager=self.manager.component_types['StratumServer'][0].status)
def jsonize(item): """ Recursive function that converts a lot of non-serializable content to something json.dumps will like better """ if isinstance(item, dict): new = {} for k, v in item.iteritems(): k = str(k) if isinstance(v, deque): new[k] = jsonize(list(v)) else: new[k] = jsonize(v) return new elif isinstance(item, list) or isinstance(item, tuple): new = [] for part in item: new.append(jsonize(part)) return new else: if isinstance(item, Transaction): item.disassemble() return item.to_dict() elif isinstance(item, str): return item.encode('string_escape') elif isinstance(item, set): return list(item) elif isinstance(item, decimal.Decimal): return float(item) elif isinstance(item, (int, long, bool, float)) or item is None: return item elif hasattr(item, "__dict__"): return {str(k).encode('string_escape'): str(v).encode('string_escape') for k, v in item.__dict__.iteritems()} else: return str(item)