From f80bac434cd518f28aa2dcf1e9799b26bceb6ca4 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 5 Mar 2015 15:14:29 +0300 Subject: [PATCH 1/7] Windows doesn't have os.getloadavg() function --- psdash/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/psdash/node.py b/psdash/node.py index 40f1aff..7ec6b4c 100644 --- a/psdash/node.py +++ b/psdash/node.py @@ -76,10 +76,11 @@ def get_sysinfo(self): 'uptime': uptime, 'hostname': socket.gethostname(), 'os': platform.platform(), - 'load_avg': os.getloadavg(), + 'load_avg': (0.0, 0.0, 0.0), 'num_cpus': psutil.cpu_count() } - + if getattr(os, "getloadavg", None) != None: + sysinfo['load_avg'] = os.getloadavg() return sysinfo def get_memory(self): From be120b331ae23efdfaa311e93c87c3502af081b9 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 5 Mar 2015 19:51:07 +0300 Subject: [PATCH 2/7] Make templates Python 3 compatible This assumes that data sent to Jinja2 is unicode or ascii. Jinja2 uses unicode internally, so it makes sense to just feed it unicode objects instead of 'utf-8' binary strings. --- psdash/templates/base.html | 2 +- psdash/templates/index.html | 6 +++--- psdash/templates/process/environment.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/psdash/templates/base.html b/psdash/templates/base.html index 1967d64..350f976 100644 --- a/psdash/templates/base.html +++ b/psdash/templates/base.html @@ -39,7 +39,7 @@ diff --git a/psdash/templates/index.html b/psdash/templates/index.html index ca9c80a..65740da 100644 --- a/psdash/templates/index.html +++ b/psdash/templates/index.html @@ -80,7 +80,7 @@ {% for ni in net_interfaces %} - {{ ni.name.decode("utf-8") }} + {{ ni.name }} {{ ni.ip }} {{ ni.send_rate|default(0)|filesizeformat }} {{ ni.recv_rate|default(0)|filesizeformat }} @@ -109,7 +109,7 @@ {% for d in disks %} {{ d.device }} - {{ d.mountpoint.decode("utf-8") }} + {{ d.mountpoint }} {{ d.space_total|filesizeformat }} {{ d.space_used|filesizeformat }} ({{ d.space_used_percent }} %) {{ d.space_free|filesizeformat }} @@ -164,7 +164,7 @@ {% for u in users %} - {{ u.name.decode("utf-8") }} + {{ u.name }} {{ u.started|fromtimestamp }} {{ u.host }} diff --git a/psdash/templates/process/environment.html b/psdash/templates/process/environment.html index 4d3413a..33ee1b8 100644 --- a/psdash/templates/process/environment.html +++ b/psdash/templates/process/environment.html @@ -8,7 +8,7 @@ - {% for var, value in process_environ.iteritems() %} + {% for var, value in process_environ.items() %} {{ var.decode("utf-8") }} {{ value.decode("utf-8") }} From 8b790601ac003ec4b13813139cd8986d3b578097 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 5 Mar 2015 20:36:26 +0300 Subject: [PATCH 3/7] More Python 3 fixes to make disks menu rendered ok iteritems() is gone, and iter() returns view object that can not be sorted - need to convert to list. --- psdash/run.py | 2 +- psdash/templates/disks.html | 2 +- psdash/web.py | 13 +++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/psdash/run.py b/psdash/run.py index 32e54bb..d0b8bb2 100644 --- a/psdash/run.py +++ b/psdash/run.py @@ -106,7 +106,7 @@ def _get_args(cls, args): def _load_args_config(self, args): config = {} - for k, v in vars(self._get_args(args)).iteritems(): + for k, v in vars(self._get_args(args)).items(): if v: key = 'PSDASH_%s' % k.upper() if k != 'debug' else 'DEBUG' config[key] = v diff --git a/psdash/templates/disks.html b/psdash/templates/disks.html index f6f1253..36e5331 100644 --- a/psdash/templates/disks.html +++ b/psdash/templates/disks.html @@ -22,7 +22,7 @@ {% for d in disks %} {{ d.device }} - {{ d.mountpoint.decode("utf-8") }} + {{ d.mountpoint }} {{ d.type }} {{ d.options }} {{ d.space_total|filesizeformat }} diff --git a/psdash/web.py b/psdash/web.py index 2ba70ce..c288c8c 100755 --- a/psdash/web.py +++ b/psdash/web.py @@ -37,12 +37,13 @@ def inject_nodes(): @webapp.context_processor def inject_header_data(): + # automatic variables for all Jinja2 templates sysinfo = current_service.get_sysinfo() uptime = timedelta(seconds=sysinfo['uptime']) uptime = str(uptime).split('.')[0] return { - 'os': sysinfo['os'].decode('utf-8'), - 'hostname': sysinfo['hostname'].decode('utf-8'), + 'os': sysinfo['os'], + 'hostname': sysinfo['hostname'], 'uptime': uptime } @@ -107,7 +108,7 @@ def access_denied(e): def index(): sysinfo = current_service.get_sysinfo() - netifs = current_service.get_network_interfaces().values() + netifs = list(current_service.get_network_interfaces().values()) netifs.sort(key=lambda x: x.get('bytes_sent'), reverse=True) data = { @@ -188,7 +189,7 @@ def process(pid, section): whitelist = current_app.config.get('PSDASH_ENVIRON_WHITELIST') if whitelist: penviron = dict((k, v if k in whitelist else '*hidden by whitelist*') - for k, v in penviron.iteritems()) + for k, v in penviron.items()) context['process_environ'] = penviron elif section == 'threads': @@ -224,7 +225,7 @@ def view_networks(): 'state': 'LISTEN' } - form_values = dict((k, request.args.get(k, default_val)) for k, default_val in form_keys.iteritems()) + form_values = dict((k, request.args.get(k, default_val)) for k, default_val in form_keys.items()) for k in ('local_addr', 'remote_addr'): val = request.args.get(k, '') @@ -262,7 +263,7 @@ def view_networks(): @webapp.route('/disks') def view_disks(): disks = current_service.get_disks(all_partitions=True) - io_counters = current_service.get_disks_counters().items() + io_counters = list(current_service.get_disks_counters().items()) io_counters.sort(key=lambda x: x[1]['read_count'], reverse=True) return render_template( 'disks.html', From e2a6426cceaf4d25ca81dad6fe76b46826eddcc9 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 5 Mar 2015 21:21:02 +0300 Subject: [PATCH 4/7] Python 3 compatibility - dashboard and disks pages are rendering --- psdash/net.py | 6 +++--- psdash/node.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/psdash/net.py b/psdash/net.py index 7d75abd..b5480a6 100644 --- a/psdash/net.py +++ b/psdash/net.py @@ -19,7 +19,7 @@ def _get_net_io_counters(self): counters = psutil.net_io_counters(pernic=self.pernic) res = {} - for name, io in counters.iteritems(): + for name, io in counters.items(): res[name] = io._asdict() res[name].update({'tx_per_sec': 0, 'rx_per_sec': 0}) @@ -43,7 +43,7 @@ def update(self): if not time_delta: return counters - for name, io in counters.iteritems(): + for name, io in counters.items(): last_io = self.last_req.get(name) if not last_io: continue @@ -70,7 +70,7 @@ def get_interface_addresses(): ifaces = netifaces.interfaces() for iface in ifaces: addrs = netifaces.ifaddresses(iface) - families = addrs.keys() + families = list(addrs.keys()) # put IPv4 to the end so it lists as the main iface address if netifaces.AF_INET in families: diff --git a/psdash/node.py b/psdash/node.py index 7ec6b4c..ebb275b 100644 --- a/psdash/node.py +++ b/psdash/node.py @@ -123,7 +123,7 @@ def get_disks(self, all_partitions=False): return disks def get_disks_counters(self, perdisk=True): - return dict((dev, c._asdict()) for dev, c in psutil.disk_io_counters(perdisk=perdisk).iteritems()) + return dict((dev, c._asdict()) for dev, c in psutil.disk_io_counters(perdisk=perdisk).items()) def get_users(self): return [u._asdict() for u in psutil.users()] @@ -326,7 +326,7 @@ def get_connections(self, filters=None): 'state': c.status } - for k, v in filters.iteritems(): + for k, v in filters.items(): if v and conn.get(k) != v: break else: From ae7d29c3959a07d50c9c18e8a0404da937747e96 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 5 Mar 2015 22:06:56 +0300 Subject: [PATCH 5/7] Python 3 fixes - all pages, except logs are working --- psdash/templates/network.html | 4 ++-- psdash/web.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/psdash/templates/network.html b/psdash/templates/network.html index 2155f27..876f5a4 100644 --- a/psdash/templates/network.html +++ b/psdash/templates/network.html @@ -68,7 +68,7 @@ @@ -76,7 +76,7 @@ diff --git a/psdash/web.py b/psdash/web.py index c288c8c..88bbb99 100755 --- a/psdash/web.py +++ b/psdash/web.py @@ -213,7 +213,7 @@ def process(pid, section): @webapp.route('/network') def view_networks(): - netifs = current_service.get_network_interfaces().values() + netifs = list(current_service.get_network_interfaces().values()) netifs.sort(key=lambda x: x.get('bytes_sent'), reverse=True) # {'key', 'default_value'} From d77c1a40e07d704dee5f88a864c284c603dbeb68 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 5 Mar 2015 22:19:35 +0300 Subject: [PATCH 6/7] Python 3 - all pages are rendered without stacktraces --- psdash/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psdash/web.py b/psdash/web.py index 88bbb99..8ef1fae 100755 --- a/psdash/web.py +++ b/psdash/web.py @@ -277,7 +277,7 @@ def view_disks(): @webapp.route('/logs') def view_logs(): available_logs = current_service.get_logs() - available_logs.sort(cmp=lambda x1, x2: locale.strcoll(x1['path'], x2['path'])) + available_logs.sort(key=locale.strxfrm) return render_template( 'logs.html', From 2638ddc882156df82f1a127bd6821e615d1d82bc Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 5 Mar 2015 22:21:22 +0300 Subject: [PATCH 7/7] Run psdash without gevent on asyncio --- psdash/node.py | 2 +- psdash/run.py | 56 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/psdash/node.py b/psdash/node.py index ebb275b..8db4356 100644 --- a/psdash/node.py +++ b/psdash/node.py @@ -5,7 +5,7 @@ import psutil import socket import time -import zerorpc +#import zerorpc from psdash.log import Logs from psdash.helpers import socket_families, socket_types from psdash.net import get_interface_addresses, NetIOCounters diff --git a/psdash/run.py b/psdash/run.py index d0b8bb2..1e97fff 100644 --- a/psdash/run.py +++ b/psdash/run.py @@ -1,17 +1,31 @@ -import gevent -from gevent.monkey import patch_all -patch_all() -from gevent.pywsgi import WSGIServer +# define Python 3 compatible eventloop API + +class eventloop(object): + call_later = None + +try: + import gevent + from gevent.monkey import patch_all + patch_all() + + from gevent.pywsgi import WSGIServer + eventloop.call_later = gevent.spawn_later +except ImportError: + gevent = False + import asyncio + eventloop = asyncio.get_event_loop() + + import locale import argparse import logging import socket import urllib -import urllib2 +#import urllib2 from logging import getLogger from flask import Flask -import zerorpc +#import zerorpc from psdash import __version__ from psdash.node import LocalNode, RemoteNode from psdash.web import fromtimestamp @@ -189,15 +203,15 @@ def _setup_logging(self): def _setup_workers(self): net_io_interval = self.app.config.get('PSDASH_NET_IO_COUNTER_INTERVAL', self.DEFAULT_NET_IO_COUNTER_INTERVAL) - gevent.spawn_later(net_io_interval, self._net_io_counters_worker, net_io_interval) + eventloop.call_later(net_io_interval, self._net_io_counters_worker, net_io_interval) if 'PSDASH_LOGS' in self.app.config: logs_interval = self.app.config.get('PSDASH_LOGS_INTERVAL', self.DEFAULT_LOG_INTERVAL) - gevent.spawn_later(logs_interval, self._logs_worker, logs_interval) + eventloop.call_later(logs_interval, self._logs_worker, logs_interval) if self.app.config.get('PSDASH_AGENT'): register_interval = self.app.config.get('PSDASH_REGISTER_INTERVAL', self.DEFAULT_REGISTER_INTERVAL) - gevent.spawn_later(register_interval, self._register_agent_worker, register_interval) + eventloop.call_later(register_interval, self._register_agent_worker, register_interval) def _setup_locale(self): # This set locale to the user default (usually controlled by the LANG env var) @@ -276,16 +290,20 @@ def _run_web(self): 'certfile': self.app.config.get('PSDASH_HTTPS_CERTFILE') } - listen_to = ( - self.app.config.get('PSDASH_BIND_HOST', self.DEFAULT_BIND_HOST), - self.app.config.get('PSDASH_PORT', self.DEFAULT_PORT) - ) - self.server = WSGIServer( - listen_to, - application=self.app, - log=log, - **ssl_args - ) + host = self.app.config.get('PSDASH_BIND_HOST', self.DEFAULT_BIND_HOST) + port = self.app.config.get('PSDASH_PORT', self.DEFAULT_PORT) + if gevent: + listen_to = (host, port) + self.server = WSGIServer( + listen_to, + application=self.app, + log=log, + **ssl_args + ) + else: + from wsgiref.simple_server import make_server + self.server = make_server(host, port, self.app) + print("Serving with wsgiref on http://%s:%s ..." % (host, port)) self.server.serve_forever() def run(self):