2012-10-14 11:56:57 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2013-07-06 09:42:26 +02:00
|
|
|
""" License
|
|
|
|
|
|
|
|
Copyright (C) 2013 YunoHost
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
|
|
along with this program; if not, see http://www.gnu.org/licenses
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
""" yunohost_monitor.py
|
2013-07-06 10:17:16 +02:00
|
|
|
|
|
|
|
Monitoring functions
|
2013-07-06 09:42:26 +02:00
|
|
|
"""
|
2013-12-02 18:02:20 +01:00
|
|
|
import re
|
2013-02-20 21:32:08 +01:00
|
|
|
import json
|
|
|
|
import psutil
|
2013-12-02 18:02:20 +01:00
|
|
|
import subprocess
|
|
|
|
import xmlrpclib
|
2012-11-01 10:42:17 +01:00
|
|
|
from urllib import urlopen
|
2012-10-14 13:42:22 +02:00
|
|
|
from datetime import datetime, timedelta
|
2013-12-05 00:00:57 +01:00
|
|
|
from yunohost import YunoHostError
|
2013-12-02 18:02:20 +01:00
|
|
|
|
|
|
|
glances_uri = 'http://127.0.0.1:61209'
|
2013-10-04 12:23:25 +02:00
|
|
|
|
2013-12-03 01:51:40 +01:00
|
|
|
def monitor_disk(units=None, mountpoint=None, human_readable=False):
|
2013-12-02 18:02:20 +01:00
|
|
|
"""
|
|
|
|
Monitor disk space and usage
|
|
|
|
|
|
|
|
Keyword argument:
|
2013-12-03 00:41:32 +01:00
|
|
|
units -- Unit(s) to monitor
|
2013-12-02 18:02:20 +01:00
|
|
|
mountpoint -- Device mountpoint
|
2013-12-03 01:51:40 +01:00
|
|
|
human_readable -- Print sizes in human readable format
|
2013-12-02 18:02:20 +01:00
|
|
|
|
|
|
|
"""
|
|
|
|
glances = _get_glances_api()
|
|
|
|
result_dname = None
|
|
|
|
result = {}
|
|
|
|
|
2013-12-03 00:41:32 +01:00
|
|
|
if units is None:
|
2013-12-02 18:02:20 +01:00
|
|
|
units = ['io', 'filesystem']
|
|
|
|
|
|
|
|
# Get mounted block devices
|
|
|
|
devices = {}
|
|
|
|
output = subprocess.check_output('lsblk -o NAME,MOUNTPOINT -l -n'.split())
|
|
|
|
for d in output.split('\n'):
|
2013-12-05 14:32:36 +01:00
|
|
|
m = re.search(r'([a-z]+[0-9]*)[ ]+(\/\S*)', d) # Extract device name (1) and its mountpoint (2)
|
2013-12-02 18:02:20 +01:00
|
|
|
if m and (mountpoint is None or m.group(2) == mountpoint):
|
|
|
|
(dn, dm) = (m.group(1), m.group(2))
|
|
|
|
devices[dn] = dm
|
2013-12-03 00:41:32 +01:00
|
|
|
result[dn] = {} if len(units) > 1 else []
|
2013-12-02 18:02:20 +01:00
|
|
|
result_dname = dn if mountpoint is not None else None
|
|
|
|
if len(devices) == 0:
|
2013-12-05 14:32:36 +01:00
|
|
|
if mountpoint is None:
|
|
|
|
raise YunoHostError(1, _("No mounted block device found"))
|
2013-12-02 18:02:20 +01:00
|
|
|
raise YunoHostError(1, _("Unknown mountpoint '%s'") % mountpoint)
|
|
|
|
|
|
|
|
# Retrieve monitoring for unit(s)
|
|
|
|
for u in units:
|
|
|
|
if u == 'io':
|
|
|
|
for d in json.loads(glances.getDiskIO()):
|
|
|
|
dname = d['disk_name']
|
|
|
|
if dname in devices.keys():
|
|
|
|
del d['disk_name']
|
2013-12-03 00:41:32 +01:00
|
|
|
if len(units) > 1:
|
2013-12-02 18:02:20 +01:00
|
|
|
result[dname][u] = d
|
|
|
|
else:
|
|
|
|
d['mnt_point'] = devices[dname]
|
|
|
|
result[dname] = d
|
2013-12-05 15:35:42 +01:00
|
|
|
for dname in devices.keys():
|
|
|
|
if len(units) > 1 and u not in result[dname]:
|
|
|
|
result[dname][u] = 'not-available'
|
|
|
|
elif len(result[dname]) == 0:
|
|
|
|
result[dname] = 'not-available'
|
2013-12-02 18:02:20 +01:00
|
|
|
elif u == 'filesystem':
|
|
|
|
for d in json.loads(glances.getFs()):
|
|
|
|
dmount = d['mnt_point']
|
|
|
|
for (dn, dm) in devices.items():
|
|
|
|
# TODO: Show non-block filesystems?
|
|
|
|
if dm != dmount:
|
|
|
|
continue
|
|
|
|
del d['device_name']
|
2013-12-03 01:51:40 +01:00
|
|
|
if human_readable:
|
|
|
|
for i in ['used', 'avail', 'size']:
|
2013-12-03 17:33:21 +01:00
|
|
|
d[i] = _binary_to_human(d[i]) + 'B'
|
2013-12-03 00:41:32 +01:00
|
|
|
if len(units) > 1:
|
2013-12-02 18:02:20 +01:00
|
|
|
result[dn][u] = d
|
|
|
|
else:
|
|
|
|
result[dn] = d
|
|
|
|
else:
|
|
|
|
raise YunoHostError(1, _("Unknown unit '%s'") % u)
|
|
|
|
|
|
|
|
if result_dname is not None:
|
|
|
|
return result[result_dname]
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2013-12-03 01:51:40 +01:00
|
|
|
def monitor_network(units=None, human_readable=False):
|
2013-12-02 18:02:20 +01:00
|
|
|
"""
|
|
|
|
Monitor network interfaces
|
|
|
|
|
|
|
|
Keyword argument:
|
2013-12-03 00:41:32 +01:00
|
|
|
units -- Unit(s) to monitor
|
2013-12-03 01:51:40 +01:00
|
|
|
human_readable -- Print sizes in human readable format
|
2013-12-02 18:02:20 +01:00
|
|
|
|
|
|
|
"""
|
|
|
|
glances = _get_glances_api()
|
|
|
|
result = {}
|
|
|
|
|
2013-12-03 00:41:32 +01:00
|
|
|
if units is None:
|
2013-12-02 18:02:20 +01:00
|
|
|
units = ['usage', 'infos']
|
|
|
|
|
|
|
|
# Get network devices and their addresses
|
|
|
|
devices = {}
|
|
|
|
output = subprocess.check_output('ip addr show'.split())
|
|
|
|
for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE):
|
|
|
|
d = re.sub('\n[ ]+', ' % ', d) # Replace new lines by %
|
|
|
|
m = re.match('([a-z]+[0-9]?): (.*)', d) # Extract device name (1) and its addresses (2)
|
|
|
|
if m:
|
|
|
|
devices[m.group(1)] = m.group(2)
|
|
|
|
|
|
|
|
# Retrieve monitoring for unit(s)
|
|
|
|
for u in units:
|
|
|
|
if u == 'usage':
|
|
|
|
result[u] = {}
|
|
|
|
for i in json.loads(glances.getNetwork()):
|
|
|
|
iname = i['interface_name']
|
|
|
|
if iname in devices.keys():
|
|
|
|
del i['interface_name']
|
2013-12-03 01:51:40 +01:00
|
|
|
if human_readable:
|
|
|
|
for k in i.keys():
|
|
|
|
if k != 'time_since_update':
|
2013-12-03 17:33:21 +01:00
|
|
|
i[k] = _binary_to_human(i[k]) + 'B'
|
2013-12-02 18:02:20 +01:00
|
|
|
result[u][iname] = i
|
|
|
|
elif u == 'infos':
|
|
|
|
try:
|
|
|
|
p_ip = str(urlopen('http://ip.yunohost.org').read())
|
|
|
|
except:
|
|
|
|
raise YunoHostError(1, _("Public IP resolution failed"))
|
|
|
|
|
|
|
|
l_ip = None
|
|
|
|
for name, addrs in devices.items():
|
|
|
|
if name == 'lo':
|
|
|
|
continue
|
|
|
|
if len(devices) == 2:
|
|
|
|
l_ip = _extract_inet(addrs)
|
|
|
|
else:
|
|
|
|
if l_ip is None:
|
|
|
|
l_ip = {}
|
|
|
|
l_ip[name] = _extract_inet(addrs)
|
|
|
|
|
2013-12-05 16:06:30 +01:00
|
|
|
gateway = None
|
|
|
|
output = subprocess.check_output('ip route show'.split())
|
|
|
|
m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output)
|
|
|
|
if m:
|
|
|
|
gateway = _extract_inet(m.group(1), True)
|
|
|
|
|
2013-12-02 18:02:20 +01:00
|
|
|
result[u] = {
|
|
|
|
'public_ip': p_ip,
|
|
|
|
'local_ip': l_ip,
|
2013-12-05 16:06:30 +01:00
|
|
|
'gateway': gateway
|
2013-12-02 18:02:20 +01:00
|
|
|
}
|
|
|
|
else:
|
|
|
|
raise YunoHostError(1, _("Unknown unit '%s'") % u)
|
|
|
|
|
|
|
|
if len(units) == 1:
|
2013-12-03 00:41:32 +01:00
|
|
|
return result[units[0]]
|
2013-12-02 18:02:20 +01:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
2013-12-03 01:51:40 +01:00
|
|
|
def monitor_system(units=None, human_readable=False):
|
2013-07-06 09:53:43 +02:00
|
|
|
"""
|
2013-12-02 18:02:20 +01:00
|
|
|
Monitor system informations and usage
|
2013-07-06 10:17:16 +02:00
|
|
|
|
|
|
|
Keyword argument:
|
2013-12-03 00:41:32 +01:00
|
|
|
units -- Unit(s) to monitor
|
2013-12-03 01:51:40 +01:00
|
|
|
human_readable -- Print sizes in human readable format
|
2013-07-06 10:17:16 +02:00
|
|
|
|
2013-07-06 09:53:43 +02:00
|
|
|
"""
|
2013-12-02 18:02:20 +01:00
|
|
|
glances = _get_glances_api()
|
|
|
|
result = {}
|
2013-02-20 21:32:08 +01:00
|
|
|
|
2013-12-03 00:41:32 +01:00
|
|
|
if units is None:
|
2013-12-02 18:02:20 +01:00
|
|
|
units = ['memory', 'cpu', 'process', 'uptime', 'infos']
|
|
|
|
|
|
|
|
# Retrieve monitoring for unit(s)
|
|
|
|
for u in units:
|
|
|
|
if u == 'memory':
|
2013-12-03 01:51:40 +01:00
|
|
|
ram = json.loads(glances.getMem())
|
|
|
|
swap = json.loads(glances.getMemSwap())
|
|
|
|
if human_readable:
|
|
|
|
for i in ram.keys():
|
|
|
|
if i != 'percent':
|
2013-12-03 17:33:21 +01:00
|
|
|
ram[i] = _binary_to_human(ram[i]) + 'B'
|
2013-12-03 01:51:40 +01:00
|
|
|
for i in swap.keys():
|
|
|
|
if i != 'percent':
|
2013-12-03 17:33:21 +01:00
|
|
|
swap[i] = _binary_to_human(swap[i]) + 'B'
|
2013-12-02 18:02:20 +01:00
|
|
|
result[u] = {
|
2013-12-03 01:51:40 +01:00
|
|
|
'ram': ram,
|
|
|
|
'swap': swap
|
2013-12-02 18:02:20 +01:00
|
|
|
}
|
|
|
|
elif u == 'cpu':
|
|
|
|
result[u] = {
|
|
|
|
'load': json.loads(glances.getLoad()),
|
|
|
|
'usage': json.loads(glances.getCpu())
|
|
|
|
}
|
|
|
|
elif u == 'process':
|
|
|
|
result[u] = json.loads(glances.getProcessCount())
|
|
|
|
elif u == 'uptime':
|
|
|
|
result[u] = (str(datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME)).split('.')[0])
|
|
|
|
elif u == 'infos':
|
|
|
|
result[u] = json.loads(glances.getSystem())
|
|
|
|
else:
|
|
|
|
raise YunoHostError(1, _("Unknown unit '%s'") % u)
|
|
|
|
|
2013-12-03 00:41:32 +01:00
|
|
|
if len(units) == 1 and type(result[units[0]]) is not str:
|
|
|
|
return result[units[0]]
|
2013-12-02 18:02:20 +01:00
|
|
|
return result
|
|
|
|
|
2012-10-28 15:55:35 +01:00
|
|
|
|
2013-12-02 18:02:20 +01:00
|
|
|
def _get_glances_api():
|
|
|
|
"""
|
|
|
|
Retrieve Glances API running on the local server
|
|
|
|
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
p = xmlrpclib.ServerProxy(glances_uri)
|
|
|
|
p.system.methodHelp('getAll')
|
|
|
|
except (xmlrpclib.ProtocolError, IOError):
|
|
|
|
# TODO: Try to start Glances service
|
|
|
|
raise YunoHostError(1, _("Connection to Glances server failed"))
|
|
|
|
|
|
|
|
return p
|
|
|
|
|
|
|
|
|
2013-12-05 16:06:30 +01:00
|
|
|
def _extract_inet(string, skip_netmask=False):
|
2013-12-02 18:02:20 +01:00
|
|
|
"""
|
|
|
|
Extract IP address (v4 or v6) from a string
|
|
|
|
|
|
|
|
Keyword argument:
|
|
|
|
string -- String to search in
|
2013-12-03 01:51:40 +01:00
|
|
|
|
2013-12-02 18:02:20 +01:00
|
|
|
"""
|
|
|
|
# TODO: Return IPv4 and IPv6?
|
2013-12-05 16:06:30 +01:00
|
|
|
ip4 = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
|
|
|
|
ip6 = '((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?'
|
|
|
|
ip4 += '/[0-9]{1,2})' if not skip_netmask else ')'
|
|
|
|
ip6 += '/[0-9]{1,2})' if not skip_netmask else ')'
|
|
|
|
|
|
|
|
ip4_prog = re.compile(ip4)
|
|
|
|
ip6_prog = re.compile(ip6)
|
2013-12-02 18:02:20 +01:00
|
|
|
|
|
|
|
m = ip4_prog.search(string)
|
|
|
|
if m:
|
|
|
|
return m.group(1)
|
|
|
|
|
|
|
|
m = ip6_prog.search(string)
|
|
|
|
if m:
|
|
|
|
return m.group(1)
|
|
|
|
|
|
|
|
return None
|
2013-12-03 01:51:40 +01:00
|
|
|
|
|
|
|
|
2013-12-03 17:33:21 +01:00
|
|
|
def _binary_to_human(n, customary=False):
|
2013-12-03 01:51:40 +01:00
|
|
|
"""
|
2013-12-03 17:33:21 +01:00
|
|
|
Convert bytes or bits into human readable format with binary prefix
|
2013-12-03 01:51:40 +01:00
|
|
|
|
|
|
|
Keyword argument:
|
|
|
|
n -- Number to convert
|
2013-12-03 17:33:21 +01:00
|
|
|
customary -- Use customary symbol instead of IEC standard
|
2013-12-03 01:51:40 +01:00
|
|
|
|
|
|
|
"""
|
2013-12-03 17:33:21 +01:00
|
|
|
symbols = ('Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
|
|
|
|
if customary:
|
|
|
|
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
|
2013-12-03 01:51:40 +01:00
|
|
|
prefix = {}
|
|
|
|
for i, s in enumerate(symbols):
|
2013-12-03 17:33:21 +01:00
|
|
|
prefix[s] = 1 << (i+1)*10
|
2013-12-03 01:51:40 +01:00
|
|
|
for s in reversed(symbols):
|
|
|
|
if n >= prefix[s]:
|
|
|
|
value = float(n) / prefix[s]
|
|
|
|
return '%.1f%s' % (value, s)
|
2013-12-03 17:33:21 +01:00
|
|
|
return "%s" % n
|