From b61f70ae99c00db6a05cd5fc9ef1dc02b9483683 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 17 Sep 2016 10:14:52 +0200 Subject: [PATCH 01/89] Grab the stats, but not display it in the UI... --- glances/plugins/glances_network.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index f2a1ef34..eb2c6300 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -90,7 +90,9 @@ class Plugin(GlancesPlugin): except UnicodeDecodeError: return self.stats - # New in PsUtil 3.0: optionaly import the interface's status (issue #765) + # New in PsUtil 3.0 + # - import the interface's status (issue #765) + # - import the interface's speed (issue #718) netstatus = {} try: netstatus = psutil.net_if_stats() @@ -136,11 +138,19 @@ class Plugin(GlancesPlugin): continue else: # Optional stats (only compliant with PsUtil 3.0+) + # Interface status try: netstat['is_up'] = netstatus[net].isup except (KeyError, AttributeError): pass - # Set the key + # Interface speed in Mbps, convert it to bps + # Can be always 0 on some OS + try: + netstat['speed'] = netstatus[net].speed * 1048576 + except (KeyError, AttributeError): + pass + + # Finaly, set the key netstat['key'] = self.get_key() self.stats.append(netstat) From 91495a3352d9a35174c3ec711161010c14be1952 Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Mon, 26 Sep 2016 08:42:19 +0200 Subject: [PATCH 02/89] Remove print in __init__.py --- glances/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/glances/__init__.py b/glances/__init__.py index 0a3e54fa..d614f059 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -168,7 +168,6 @@ def main(): from glances.server import GlancesServer args = core.get_args() - print args.cached_time server = GlancesServer(cached_time=args.cached_time, config=core.get_config(), From b2ea5104476c4ee7b8b77681f32576cc147d62f0 Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Mon, 26 Sep 2016 09:00:30 +0200 Subject: [PATCH 03/89] Correct a critical issue on client/server mode --- glances/plugins/glances_processlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 11aeeb46..07b0755c 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -80,7 +80,7 @@ class Plugin(GlancesPlugin): self.nb_log_core = 0 # Get the max values (dict) - max_values = glances_processes.max_values() + self.max_values = glances_processes.max_values() # Note: 'glances_processes' is already init in the processes.py script From 3a4bf77b79548000d6d2e7e284a1815958d2b9b8 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 27 Sep 2016 21:30:22 +0200 Subject: [PATCH 04/89] Correct another fucking issue about Python 3 bytes string to regular string (issue #933) --- NEWS | 1 + glances/compat.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 8adeddbd..93a162dd 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,7 @@ Enhancements and new features: Bugs corrected: * Glances RAID plugin Traceback (issue #927) + * Default AMP crashes when 'command' given (issue #933) Version 2.7.1 ============= diff --git a/glances/compat.py b/glances/compat.py index cbc74a06..5a1f7b83 100644 --- a/glances/compat.py +++ b/glances/compat.py @@ -28,12 +28,6 @@ import types PY3 = sys.version_info[0] == 3 - -def to_ascii(s): - """Convert the unicode 's' to a ASCII string - Usefull to remove accent (diacritics)""" - return unicodedata.normalize('NFKD', s).encode('ASCII', 'ignore') - if PY3: import queue from configparser import ConfigParser, NoOptionError, NoSectionError @@ -54,6 +48,11 @@ if PY3: viewvalues = operator.methodcaller('values') viewitems = operator.methodcaller('items') + def to_ascii(s): + """Convert the bytes string to a ASCII string + Usefull to remove accent (diacritics)""" + return str(s, 'utf-8') + def listitems(d): return list(d.items()) @@ -104,6 +103,11 @@ else: viewvalues = operator.methodcaller('viewvalues') viewitems = operator.methodcaller('viewitems') + def to_ascii(s): + """Convert the unicode 's' to a ASCII string + Usefull to remove accent (diacritics)""" + return unicodedata.normalize('NFKD', s).encode('ASCII', 'ignore') + def listitems(d): return d.items() From 2acdbe9cf6b404c17f4e6bf5b2538d5ae21ec3f6 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 1 Oct 2016 09:56:48 +0200 Subject: [PATCH 05/89] Default AMP ignores enable setting #932 --- NEWS | 1 + glances/amps/glances_default.py | 9 ++++++--- glances/amps_list.py | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 93a162dd..870784b9 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,7 @@ Bugs corrected: * Glances RAID plugin Traceback (issue #927) * Default AMP crashes when 'command' given (issue #933) + * Default AMP ignores `enable` setting (issue #932) Version 2.7.1 ============= diff --git a/glances/amps/glances_default.py b/glances/amps/glances_default.py index 7392d796..8b388c46 100644 --- a/glances/amps/glances_default.py +++ b/glances/amps/glances_default.py @@ -35,7 +35,7 @@ one_line=false command=foo status """ -from subprocess import check_output, STDOUT +from subprocess import check_output, STDOUT, CalledProcessError from glances.compat import u, to_ascii from glances.logger import logger @@ -66,8 +66,11 @@ class Amp(GlancesAmp): logger.debug('{}: Error while executing service ({})'.format(self.NAME, e)) else: if res is not None: - msg = u(check_output(res.split(), stderr=STDOUT)) - self.set_result(to_ascii(msg.rstrip())) + try: + msg = u(check_output(res.split(), stderr=STDOUT)) + self.set_result(to_ascii(msg.rstrip())) + except CalledProcessError as e: + self.set_result(e.output) else: # Set the default message if command return None # Default sum of CPU and MEM for the matching regex diff --git a/glances/amps_list.py b/glances/amps_list.py index c9020717..cbb0fe22 100644 --- a/glances/amps_list.py +++ b/glances/amps_list.py @@ -106,9 +106,11 @@ class AmpsList(object): """Update the command result attributed.""" # Search application monitored processes by a regular expression processlist = glances_processes.getalllist() - # Iter upon the AMPs dict for k, v in iteritems(self.get()): + if not v.enable(): + # Do not update if the enable tag is set + continue try: amps_list = [p for p in processlist for c in p['cmdline'] if re.search(v.regex(), c) is not None] except TypeError: From b1d41bcf41002f656eaa877b756273c348d88542 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Thu, 6 Oct 2016 21:43:21 +0200 Subject: [PATCH 06/89] Workarround for the issue931. Not solve the issue but implement correctly the --disable- option for all plugins --- glances/plugins/glances_amps.py | 1 + glances/plugins/glances_batpercent.py | 1 + glances/plugins/glances_cpu.py | 1 + glances/plugins/glances_diskio.py | 1 + glances/plugins/glances_docker.py | 4 ++++ glances/plugins/glances_folders.py | 2 ++ glances/plugins/glances_fs.py | 1 + glances/plugins/glances_hddtemp.py | 2 ++ glances/plugins/glances_ip.py | 1 + glances/plugins/glances_irq.py | 1 + glances/plugins/glances_load.py | 1 + glances/plugins/glances_mem.py | 1 + glances/plugins/glances_memswap.py | 1 + glances/plugins/glances_network.py | 1 + glances/plugins/glances_percpu.py | 2 ++ glances/plugins/glances_plugin.py | 20 ++++++++++++++++++++ glances/plugins/glances_psutilversion.py | 2 ++ glances/plugins/glances_quicklook.py | 1 + glances/plugins/glances_raid.py | 1 + glances/plugins/glances_sensors.py | 1 + glances/plugins/glances_system.py | 2 ++ glances/plugins/glances_uptime.py | 2 ++ 22 files changed, 50 insertions(+) diff --git a/glances/plugins/glances_amps.py b/glances/plugins/glances_amps.py index 4d8748eb..5a8583d5 100644 --- a/glances/plugins/glances_amps.py +++ b/glances/plugins/glances_amps.py @@ -48,6 +48,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update the AMP list.""" diff --git a/glances/plugins/glances_batpercent.py b/glances/plugins/glances_batpercent.py index 0d33a30c..3083d99d 100644 --- a/glances/plugins/glances_batpercent.py +++ b/glances/plugins/glances_batpercent.py @@ -54,6 +54,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update battery capacity stats using the input method.""" diff --git a/glances/plugins/glances_cpu.py b/glances/plugins/glances_cpu.py index 8986caea..c25e4416 100644 --- a/glances/plugins/glances_cpu.py +++ b/glances/plugins/glances_cpu.py @@ -84,6 +84,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update CPU stats using the input method.""" diff --git a/glances/plugins/glances_diskio.py b/glances/plugins/glances_diskio.py index 4947c3ac..8e3376eb 100644 --- a/glances/plugins/glances_diskio.py +++ b/glances/plugins/glances_diskio.py @@ -65,6 +65,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update disk I/O stats using the input method.""" diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py index d0df6689..7df04414 100644 --- a/glances/plugins/glances_docker.py +++ b/glances/plugins/glances_docker.py @@ -66,6 +66,9 @@ class Plugin(GlancesPlugin): # value: instance of ThreadDockerGrabber self.thread_list = {} + # Init the stats + self.reset() + def exit(self): """Overwrite the exit method to close threads""" for t in itervalues(self.thread_list): @@ -141,6 +144,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update Docker stats using the input method.""" diff --git a/glances/plugins/glances_folders.py b/glances/plugins/glances_folders.py index 8e8f947d..d19f232a 100644 --- a/glances/plugins/glances_folders.py +++ b/glances/plugins/glances_folders.py @@ -52,6 +52,8 @@ class Plugin(GlancesPlugin): """Load the foldered list from the config file, if it exists.""" self.glances_folders = glancesFolderList(config) + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator def update(self): """Update the foldered list.""" # Reset the list diff --git a/glances/plugins/glances_fs.py b/glances/plugins/glances_fs.py index e5128555..593ecab9 100644 --- a/glances/plugins/glances_fs.py +++ b/glances/plugins/glances_fs.py @@ -89,6 +89,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update the FS stats using the input method.""" diff --git a/glances/plugins/glances_hddtemp.py b/glances/plugins/glances_hddtemp.py index 07813f3f..04409fd1 100644 --- a/glances/plugins/glances_hddtemp.py +++ b/glances/plugins/glances_hddtemp.py @@ -52,6 +52,8 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator def update(self): """Update HDD stats using the input method.""" # Reset stats diff --git a/glances/plugins/glances_ip.py b/glances/plugins/glances_ip.py index bdacf867..c859b8b4 100644 --- a/glances/plugins/glances_ip.py +++ b/glances/plugins/glances_ip.py @@ -75,6 +75,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update IP stats using the input method. diff --git a/glances/plugins/glances_irq.py b/glances/plugins/glances_irq.py index e1c55b8b..ce4fad71 100644 --- a/glances/plugins/glances_irq.py +++ b/glances/plugins/glances_irq.py @@ -52,6 +52,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update the IRQ stats""" diff --git a/glances/plugins/glances_load.py b/glances/plugins/glances_load.py index 9333b30d..374b3326 100644 --- a/glances/plugins/glances_load.py +++ b/glances/plugins/glances_load.py @@ -74,6 +74,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update load stats.""" diff --git a/glances/plugins/glances_mem.py b/glances/plugins/glances_mem.py index acaeffb6..bc2be1eb 100644 --- a/glances/plugins/glances_mem.py +++ b/glances/plugins/glances_mem.py @@ -76,6 +76,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update RAM memory stats using the input method.""" diff --git a/glances/plugins/glances_memswap.py b/glances/plugins/glances_memswap.py index 58ce847f..2ae64fa6 100644 --- a/glances/plugins/glances_memswap.py +++ b/glances/plugins/glances_memswap.py @@ -64,6 +64,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update swap memory stats using the input method.""" diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index f2a1ef34..f69ee5bb 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -72,6 +72,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update network stats using the input method. diff --git a/glances/plugins/glances_percpu.py b/glances/plugins/glances_percpu.py index 3b2dbb3d..64500b22 100644 --- a/glances/plugins/glances_percpu.py +++ b/glances/plugins/glances_percpu.py @@ -49,6 +49,8 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator def update(self): """Update per-CPU stats using the input method.""" # Reset stats diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 87b6e99f..45b559e9 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -86,6 +86,15 @@ class GlancesPlugin(object): """Return the key of the list.""" return None + def is_enable(self): + """Return true if plugin is enable""" + try: + d = getattr(self.args, 'disable_' + self.plugin_name) + except AttributeError: + return True + else: + return d is False + def _json_dumps(self, d): """Return the object 'd' in a JSON format Manage the issue #815 for Windows OS""" @@ -746,6 +755,16 @@ class GlancesPlugin(object): value, decimal=decimal_precision, symbol=symbol) return '{!s}'.format(number) + def _check_decorator(fct): + """Check if the plugin is enabled.""" + def wrapper(self, *args, **kw): + if self.is_enable(): + ret = fct(self, *args, **kw) + else: + ret = self.stats + return ret + return wrapper + def _log_result_decorator(fct): """Log (DEBUG) the result of the function fct.""" def wrapper(*args, **kw): @@ -758,4 +777,5 @@ class GlancesPlugin(object): return wrapper # Mandatory to call the decorator in childs' classes + _check_decorator = staticmethod(_check_decorator) _log_result_decorator = staticmethod(_log_result_decorator) diff --git a/glances/plugins/glances_psutilversion.py b/glances/plugins/glances_psutilversion.py index e660f284..1c6f367f 100644 --- a/glances/plugins/glances_psutilversion.py +++ b/glances/plugins/glances_psutilversion.py @@ -37,6 +37,8 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = None + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator def update(self): """Update the stats.""" # Reset stats diff --git a/glances/plugins/glances_quicklook.py b/glances/plugins/glances_quicklook.py index 407fd74b..58efc746 100644 --- a/glances/plugins/glances_quicklook.py +++ b/glances/plugins/glances_quicklook.py @@ -58,6 +58,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update quicklook stats using the input method.""" diff --git a/glances/plugins/glances_raid.py b/glances/plugins/glances_raid.py index d69ed08f..a22d8978 100644 --- a/glances/plugins/glances_raid.py +++ b/glances/plugins/glances_raid.py @@ -51,6 +51,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update RAID stats using the input method.""" diff --git a/glances/plugins/glances_sensors.py b/glances/plugins/glances_sensors.py index 27619eab..cc8f993e 100644 --- a/glances/plugins/glances_sensors.py +++ b/glances/plugins/glances_sensors.py @@ -78,6 +78,7 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = [] + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): """Update sensors stats using the input method.""" diff --git a/glances/plugins/glances_system.py b/glances/plugins/glances_system.py index b12f9524..684851cb 100644 --- a/glances/plugins/glances_system.py +++ b/glances/plugins/glances_system.py @@ -96,6 +96,8 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator def update(self): """Update the host/system info using the input method. diff --git a/glances/plugins/glances_uptime.py b/glances/plugins/glances_uptime.py index c76dcf6f..25217e87 100644 --- a/glances/plugins/glances_uptime.py +++ b/glances/plugins/glances_uptime.py @@ -53,6 +53,8 @@ class Plugin(GlancesPlugin): """Reset/init the stats.""" self.stats = {} + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator def update(self): """Update uptime stat using the input method.""" # Reset stats From b3ab2f04db4edb9b3b625793853f16f74eb75427 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 9 Oct 2016 17:37:56 +0200 Subject: [PATCH 07/89] Implement default limits thresholds for Containers (part 1 of the issue) --- conf/glances.conf | 9 ++++++++ glances/plugins/glances_docker.py | 35 +++++++++++++++++++++++++++++-- glances/plugins/glances_plugin.py | 5 ++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index a89a6815..0c820777 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -207,6 +207,15 @@ port_default_gateway=True #port_4_port=80 #port_4_rtt_warning=1000 +[docker] +# Thresthold for CPU and MEM (in %) +cpu_careful=50 +cpu_warning=70 +cpu_critical=90 +mem_careful=20 +mem_warning=50 +mem_critical=70 + ############################################################################## # Client/server ############################################################################## diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py index 7df04414..fa8d2bb0 100644 --- a/glances/plugins/glances_docker.py +++ b/glances/plugins/glances_docker.py @@ -237,6 +237,9 @@ class Plugin(GlancesPlugin): # Not available pass + # Update the view + self.update_views() + return self.stats def get_docker_cpu(self, container_id, all_stats): @@ -438,6 +441,30 @@ class Plugin(GlancesPlugin): """Return the user ticks by reading the environment variable.""" return os.sysconf(os.sysconf_names['SC_CLK_TCK']) + def update_views(self): + """Update stats views.""" + # Call the father's method + super(Plugin, self).update_views() + + if 'containers' not in self.stats: + return False + + # Add specifics informations + # Alert + for i in self.stats['containers']: + # Init the views for the current container (key = container name) + self.views[i[self.get_key()]] = {'cpu': {}, 'mem': {}} + # CPU alert + if 'cpu' in i and 'total' in i['cpu']: + self.views[i[self.get_key()]]['cpu']['decoration'] = self.get_alert( + i['cpu']['total'], header='cpu') + # MEM alert + if 'memory' in i and 'usage' in i['memory']: + self.views[i[self.get_key()]]['mem']['decoration'] = self.get_alert( + i['memory']['usage'], maximum=i['memory']['limit'], header='mem') + + return True + def msg_curse(self, args=None): """Return the dict to display in the curse interface.""" # Init the return message @@ -506,13 +533,17 @@ class Plugin(GlancesPlugin): msg = '{:>6.1f}'.format(container['cpu']['total']) except KeyError: msg = '{:>6}'.format('?') - ret.append(self.curse_add_line(msg)) + ret.append(self.curse_add_line(msg, self.get_views(item=container['name'], + key='cpu', + option='decoration'))) # MEM try: msg = '{:>7}'.format(self.auto_unit(container['memory']['usage'])) except KeyError: msg = '{:>7}'.format('?') - ret.append(self.curse_add_line(msg)) + ret.append(self.curse_add_line(msg, self.get_views(item=container['name'], + key='mem', + option='decoration'))) try: msg = '{:>7}'.format(self.auto_unit(container['memory']['limit'])) except KeyError: diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 45b559e9..9a6361be 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -402,7 +402,10 @@ class GlancesPlugin(object): if option is None: return item_views[key] else: - return item_views[key][option] + if option in item_views[key]: + return item_views[key][option] + else: + return 'DEFAULT' def load_limits(self, config): """Load limits from the configuration file, if it exists.""" From b4a0b834a9a0e544b9afcef6d918a884e58a3524 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 9 Oct 2016 18:08:18 +0200 Subject: [PATCH 08/89] Implement per containers threasolds (part 2 of the issue) --- conf/glances.conf | 6 +++++- glances/plugins/glances_docker.py | 21 +++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index 0c820777..2b851d4e 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -208,13 +208,17 @@ port_default_gateway=True #port_4_rtt_warning=1000 [docker] -# Thresthold for CPU and MEM (in %) +# Thresholds for CPU and MEM (in %) cpu_careful=50 cpu_warning=70 cpu_critical=90 mem_careful=20 mem_warning=50 mem_critical=70 +# Per container thresholds +#containername_cpu_careful=10 +#containername_cpu_warning=20 +#containername_cpu_critical=30 ############################################################################## # Client/server diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py index fa8d2bb0..9f901209 100644 --- a/glances/plugins/glances_docker.py +++ b/glances/plugins/glances_docker.py @@ -456,12 +456,25 @@ class Plugin(GlancesPlugin): self.views[i[self.get_key()]] = {'cpu': {}, 'mem': {}} # CPU alert if 'cpu' in i and 'total' in i['cpu']: - self.views[i[self.get_key()]]['cpu']['decoration'] = self.get_alert( - i['cpu']['total'], header='cpu') + # Looking for specific CPU container threasold in the conf file + alert = self.get_alert(i['cpu']['total'], + header=i['name'] + '_cpu') + if alert == 'DEFAULT': + # Not found ? Get back to default CPU threasold value + alert = self.get_alert(i['cpu']['total'], header='cpu') + self.views[i[self.get_key()]]['cpu']['decoration'] = alert # MEM alert if 'memory' in i and 'usage' in i['memory']: - self.views[i[self.get_key()]]['mem']['decoration'] = self.get_alert( - i['memory']['usage'], maximum=i['memory']['limit'], header='mem') + # Looking for specific MEM container threasold in the conf file + alert = self.get_alert(i['memory']['usage'], + maximum=i['memory']['limit'], + header=i['name'] + '_mem') + if alert == 'DEFAULT': + # Not found ? Get back to default MEM threasold value + alert = self.get_alert(i['memory']['usage'], + maximum=i['memory']['limit'], + header='mem') + self.views[i[self.get_key()]]['mem']['decoration'] = alert return True From 4396634e600b3b4e5e2ef78f2fbcc3eec2058e2c Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 9 Oct 2016 22:17:32 +0200 Subject: [PATCH 09/89] Implement action (part 3 of the issue) but the get_alert is now ugly... --- glances/plugins/glances_docker.py | 11 +++++++++-- glances/plugins/glances_plugin.py | 24 ++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py index 9f901209..0413dea5 100644 --- a/glances/plugins/glances_docker.py +++ b/glances/plugins/glances_docker.py @@ -441,6 +441,11 @@ class Plugin(GlancesPlugin): """Return the user ticks by reading the environment variable.""" return os.sysconf(os.sysconf_names['SC_CLK_TCK']) + def get_stats_action(self): + """Return stats for the action + Docker will return self.stats['containers']""" + return self.stats['containers'] + def update_views(self): """Update stats views.""" # Call the father's method @@ -458,7 +463,8 @@ class Plugin(GlancesPlugin): if 'cpu' in i and 'total' in i['cpu']: # Looking for specific CPU container threasold in the conf file alert = self.get_alert(i['cpu']['total'], - header=i['name'] + '_cpu') + header=i['name'] + '_cpu', + action_key=i['name']) if alert == 'DEFAULT': # Not found ? Get back to default CPU threasold value alert = self.get_alert(i['cpu']['total'], header='cpu') @@ -468,7 +474,8 @@ class Plugin(GlancesPlugin): # Looking for specific MEM container threasold in the conf file alert = self.get_alert(i['memory']['usage'], maximum=i['memory']['limit'], - header=i['name'] + '_mem') + header=i['name'] + '_mem', + action_key=i['name']) if alert == 'DEFAULT': # Not found ? Get back to default MEM threasold value alert = self.get_alert(i['memory']['usage'], diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 9a6361be..d936a921 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -444,6 +444,13 @@ class GlancesPlugin(object): """Set the limits to input_limits.""" self._limits = input_limits + def get_stats_action(self): + """Return stats for the action + By default return all the stats. + Can be overwrite by plugins implementation. + For example, Docker will return self.stats['containers']""" + return self.stats + def get_alert(self, current=0, minimum=0, @@ -451,6 +458,7 @@ class GlancesPlugin(object): highlight_zero=True, is_max=False, header="", + action_key=None, log=False): """Return the alert status relative to a current value. @@ -466,6 +474,9 @@ class GlancesPlugin(object): If defined 'header' is added between the plugin name and the status. Only useful for stats with several alert status. + If defined, 'action_key' define the key for the actions. + By default, the action_key is equal to the header. + If log=True than add log if necessary elif log=False than do not log elif log=None than apply the config given in the conf file @@ -520,19 +531,24 @@ class GlancesPlugin(object): # Reset the trigger self.actions.set(stat_name, ret.lower()) else: + # Define the action key for the stats dict + # If not define, then it sets to header + if action_key is None: + action_key = header + # A command line is available for the current alert, run it # Build the {{mustache}} dictionnary - if isinstance(self.stats, list): + if isinstance(self.get_stats_action(), list): # If the stats are stored in a list of dict (fs plugin for exemple) # Return the dict for the current header mustache_dict = {} - for item in self.stats: - if item[self.get_key()] == header: + for item in self.get_stats_action(): + if item[self.get_key()] == action_key: mustache_dict = item break else: # Use the stats dict - mustache_dict = self.stats + mustache_dict = self.get_stats_action() # Run the action self.actions.run( stat_name, ret.lower(), command, mustache_dict=mustache_dict) From 92ec33c272ad5d30c4cc6865ff78eebbc4723143 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 10 Oct 2016 21:10:10 +0200 Subject: [PATCH 10/89] Update docs for Docker limits and actions (issue #875) --- NEWS | 1 + conf/glances.conf | 12 +++++------ docs/aoa/docker.rst | 21 +++++++++++++++++++ docs/man/glances.1 | 2 +- glances/plugins/glances_plugin.py | 34 ++++++++++++++++++++++--------- 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index 870784b9..45ff9bb6 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ Enhancements and new features: * Add CouchDB exporter (issue #928) * Highlight max stats in the processes list (issue #878) + * Docker alerts and actions (issue #875) * Glances API returns the processes PPID (issue #926) * Configure server cached time from the command line --cached-time (issue #901) diff --git a/conf/glances.conf b/conf/glances.conf index 2b851d4e..e55741f1 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -209,12 +209,12 @@ port_default_gateway=True [docker] # Thresholds for CPU and MEM (in %) -cpu_careful=50 -cpu_warning=70 -cpu_critical=90 -mem_careful=20 -mem_warning=50 -mem_critical=70 +#cpu_careful=50 +#cpu_warning=70 +#cpu_critical=90 +#mem_careful=20 +#mem_warning=50 +#mem_critical=70 # Per container thresholds #containername_cpu_careful=10 #containername_cpu_warning=20 diff --git a/docs/aoa/docker.rst b/docs/aoa/docker.rst index b5bcd047..c4585e75 100644 --- a/docs/aoa/docker.rst +++ b/docs/aoa/docker.rst @@ -8,4 +8,25 @@ Glances uses the Docker API through the `docker-py`_ library. .. image:: ../_static/docker.png +It is possible to define limits and actions from the configuration file +under the ``[docker]`` section: + +.. code-block:: ini + + [docker] + # Global containers' thresholds for CPU and MEM (in %) + cpu_careful=50 + cpu_warning=70 + cpu_critical=90 + mem_careful=20 + mem_warning=50 + mem_critical=70 + # Per container thresholds + containername_cpu_careful=10 + containername_cpu_warning=20 + containername_cpu_critical=30 + containername_cpu_critical_action=echo {{Image}} {{Id}} {{cpu}} > /tmp/container_{{name}}.alert + +You can use all the variables ({{foo}}) available in the Docker plugin. + .. _docker-py: https://github.com/docker/docker-py diff --git a/docs/man/glances.1 b/docs/man/glances.1 index 4e65242d..310c2031 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GLANCES" "1" "Sep 24, 2016" "2.8_DEVELOP" "Glances" +.TH "GLANCES" "1" "Oct 10, 2016" "2.8_DEVELOP" "Glances" .SH NAME glances \- An eye on your system . diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index d936a921..63b80742 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -524,20 +524,31 @@ class GlancesPlugin(object): glances_logs.add(ret, stat_name.upper(), value) # Manage action + self.manage_action(stat_name, ret.lower(), header, action_key) + + # Default is ok + return ret + log_str + + def manage_action(self, + stat_name, + trigger, + header, + action_key): + """Manage the action for the current stat""" # Here is a command line for the current trigger ? try: - command = self.__get_limit_action(ret.lower(), stat_name=stat_name) + command = self.__get_limit_action(trigger, stat_name=stat_name) except KeyError: # Reset the trigger - self.actions.set(stat_name, ret.lower()) + self.actions.set(stat_name, trigger) else: # Define the action key for the stats dict # If not define, then it sets to header if action_key is None: action_key = header - # A command line is available for the current alert, run it - # Build the {{mustache}} dictionnary + # A command line is available for the current alert + # 1) Build the {{mustache}} dictionnary if isinstance(self.get_stats_action(), list): # If the stats are stored in a list of dict (fs plugin for exemple) # Return the dict for the current header @@ -549,19 +560,22 @@ class GlancesPlugin(object): else: # Use the stats dict mustache_dict = self.get_stats_action() - # Run the action + # 2) Run the action self.actions.run( - stat_name, ret.lower(), command, mustache_dict=mustache_dict) + stat_name, trigger, command, mustache_dict=mustache_dict) - # Default is ok - return ret + log_str - - def get_alert_log(self, current=0, minimum=0, maximum=100, header=""): + def get_alert_log(self, + current=0, + minimum=0, + maximum=100, + header="", + action_key=None): """Get the alert log.""" return self.get_alert(current=current, minimum=minimum, maximum=maximum, header=header, + action_key=action_key, log=True) def __get_limit(self, criticity, stat_name=""): From 74593f13ebfcbba47d9475a98354aa3d17e35051 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 15 Oct 2016 15:24:01 +0200 Subject: [PATCH 11/89] Add a ZeroMQ export module #939 --- NEWS | 1 + README.rst | 4 +- conf/glances.conf | 11 +++ docs/gw/zeromq.rst | 53 +++++++++++++ docs/install.rst | 2 +- docs/man/glances.1 | 2 +- glances/exports/glances_zeromq.py | 120 ++++++++++++++++++++++++++++++ glances/main.py | 4 +- 8 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 docs/gw/zeromq.rst create mode 100644 glances/exports/glances_zeromq.py diff --git a/NEWS b/NEWS index 45ff9bb6..def52859 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ Version 2.8 Enhancements and new features: + * Add ZeroMQ exporter (issue #939) * Add CouchDB exporter (issue #928) * Highlight max stats in the processes list (issue #878) * Docker alerts and actions (issue #875) diff --git a/README.rst b/README.rst index fdbdf568..febce7de 100644 --- a/README.rst +++ b/README.rst @@ -109,14 +109,14 @@ features (like the Web interface, exports modules, sensors...): .. code-block:: console - pip install bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir + pip install bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir pyzmq To upgrade Glances to the latest version: .. code-block:: console pip install --upgrade glances - pip install --upgrade bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir + pip install --upgrade bottle requests batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra-driver scandir pyzmq If you need to install Glances in a specific user location, use: diff --git a/conf/glances.conf b/conf/glances.conf index e55741f1..8e30d07e 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -319,6 +319,17 @@ db=glances #user=root #password=root +[zeromq] +# Configuration for the --export-zeromq option +# http://www.zeromq.org +host=127.0.0.1 +port=5678 +# Glances envelopes the stats in a publish message with two frames: +# - First frame containing the following prefix (STRING) +# - Second frame with the Glances plugin name (STRING) +# - Third frame with the Glances plugin stats (JSON) +prefix=G + ############################################################################## # AMPS # * enable: Enable (true) or disable (false) the AMP diff --git a/docs/gw/zeromq.rst b/docs/gw/zeromq.rst new file mode 100644 index 00000000..9e4cdbef --- /dev/null +++ b/docs/gw/zeromq.rst @@ -0,0 +1,53 @@ +.. _zeromq: + +ZeroMQ +====== + +You can export statistics to a ``ZeroMQ`` server. +The connection should be defined in the Glances configuration file as +following: + +.. code-block:: ini + + [zeromq] + host=127.0.0.1 + port=5678 + prefix=G + +Note: Glances envelopes the stats in a publish message with two frames. + +- first frame containing the following prefix (as STRING) +- second frame with the Glances plugin name (as STRING) +- third frame with the Glances plugin stats (as JSON) + +Run Glances with: + +.. code-block:: console + + $ glances --export-zeromq + +Following is a simple Python client to subscribe to the Glances stats: + +.. code-block:: python + + # -*- coding: utf-8 -*- + # + # ZeroMQ subscriber for Glances + # + + import json + import zmq + + context = zmq.Context() + + subscriber = context.socket(zmq.SUB) + subscriber.setsockopt(zmq.SUBSCRIBE, 'G') + subscriber.connect("tcp://127.0.0.1:5678") + + while True: + _, plugin, data_raw = subscriber.recv_multipart() + data = json.loads(data_raw) + print('{} => {}'.format(plugin, data)) + + subscriber.close() + context.term() diff --git a/docs/install.rst b/docs/install.rst index e67fc01a..a1f373c4 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -22,7 +22,7 @@ features (like the Web interface, exports modules, sensors...): .. code-block:: console - pip install bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb + pip install bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb pyzmq To upgrade Glances to the latest version: diff --git a/docs/man/glances.1 b/docs/man/glances.1 index 310c2031..8e360848 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GLANCES" "1" "Oct 10, 2016" "2.8_DEVELOP" "Glances" +.TH "GLANCES" "1" "Oct 15, 2016" "2.8_DEVELOP" "Glances" .SH NAME glances \- An eye on your system . diff --git a/glances/exports/glances_zeromq.py b/glances/exports/glances_zeromq.py new file mode 100644 index 00000000..64037626 --- /dev/null +++ b/glances/exports/glances_zeromq.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2016 Nicolargo +# +# Glances is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Glances 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""ZeroMQ interface class.""" + +import sys +from datetime import datetime +import time +import json + +from glances.compat import NoOptionError, NoSectionError +from glances.logger import logger +from glances.exports.glances_export import GlancesExport + +import zmq + + +class Export(GlancesExport): + + """This class manages the ZeroMQ export module.""" + + def __init__(self, config=None, args=None): + """Init the ZeroMQ export IF.""" + super(Export, self).__init__(config=config, args=args) + + # Load the ZeroMQ configuration file section ([export_zeromq]) + self.host = None + self.port = None + self.export_enable = self.load_conf() + if not self.export_enable: + sys.exit(2) + + # Init the ZeroMQ context + self.client = self.init() + + def load_conf(self, section="zeromq"): + """Load the ZeroMQ configuration in the Glances configuration file.""" + if self.config is None: + return False + try: + self.host = self.config.get_value(section, 'host') + self.port = self.config.get_value(section, 'port') + self.prefix = self.config.get_value(section, 'prefix') + except NoSectionError: + logger.critical("No ZeroMQ configuration found") + return False + except NoOptionError as e: + logger.critical("Error in the ZeroMQ configuration (%s)" % e) + return False + else: + logger.debug("Load ZeroMQ from the Glances configuration file") + + return True + + def init(self): + """Init the connection to the CouchDB server.""" + if not self.export_enable: + return None + + server_uri = 'tcp://{}:{}'.format(self.host, self.port) + + try: + context = zmq.Context() + publisher = context.socket(zmq.PUB) + publisher.bind(server_uri) + except Exception as e: + logger.critical("Cannot connect to ZeroMQ server %s (%s)" % (server_uri, e)) + sys.exit(2) + else: + logger.info("Connected to the ZeroMQ server %s" % server_uri) + + return publisher + + def exit(self): + """Close the socket""" + self.client.close() + + def export(self, name, columns, points): + """Write the points to the ZeroMQ server.""" + logger.debug("Export {} stats to ZeroMQ".format(name)) + + # Create DB input + data = dict(zip(columns, points)) + + # Do not publish empty stats + if data == {}: + return False + + # Glances envelopes the stats in a publish message with two frames: + # - First frame containing the following prefix (STRING) + # - Second frame with the Glances plugin name (STRING) + # - Third frame with the Glances plugin stats (JSON) + message = [str(self.prefix), + name, + json.dumps(data)] + + # Write data to the ZeroMQ bus + # Result can be view: tcp://host:port + try: + self.client.send_multipart(message) + except Exception as e: + logger.error("Cannot export {} stats to ZeroMQ ({})".format(name, e)) + + return True diff --git a/glances/main.py b/glances/main.py index d710e740..57a0ce77 100644 --- a/glances/main.py +++ b/glances/main.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# Copyright (C) 2015 Nicolargo +# Copyright (C) 2016 Nicolargo # # Glances is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by @@ -176,6 +176,8 @@ Start the client browser (browser mode):\n\ dest='export_riemann', help='export stats to riemann broker (bernhard lib needed)') parser.add_argument('--export-couchdb', action='store_true', default=False, dest='export_couchdb', help='export stats to a CouchDB server (couch lib needed)') + parser.add_argument('--export-zeromq', action='store_true', default=False, + dest='export_zeromq', help='export stats to a ZeroMQ server (pyzmq lib needed)') # Client/Server option parser.add_argument('-c', '--client', dest='client', help='connect to a Glances server by IPv4/IPv6 address or hostname') From 2b0d33889a62edd9a1d9099c54975956a5aa2b45 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 15 Oct 2016 15:57:44 +0200 Subject: [PATCH 12/89] ZeroMQ plugin is now compatible with Python3 --- docs/gw/index.rst | 9 +++++---- glances/exports/glances_zeromq.py | 15 +++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/gw/index.rst b/docs/gw/index.rst index b868863e..fb372ccc 100644 --- a/docs/gw/index.rst +++ b/docs/gw/index.rst @@ -10,10 +10,11 @@ to providing stats to multiple services (see list below). :maxdepth: 2 csv - influxdb cassandra - opentsdb - statsd - rabbitmq elastic + influxdb + opentsdb + rabbitmq riemann + statsd + zeromq diff --git a/glances/exports/glances_zeromq.py b/glances/exports/glances_zeromq.py index 64037626..b8ee760b 100644 --- a/glances/exports/glances_zeromq.py +++ b/glances/exports/glances_zeromq.py @@ -23,14 +23,13 @@ import sys from datetime import datetime import time import json +import zmq +from zmq.utils.strtypes import asbytes -from glances.compat import NoOptionError, NoSectionError +from glances.compat import NoOptionError, NoSectionError, u, b, nativestr from glances.logger import logger from glances.exports.glances_export import GlancesExport -import zmq - - class Export(GlancesExport): """This class manages the ZeroMQ export module.""" @@ -56,7 +55,7 @@ class Export(GlancesExport): try: self.host = self.config.get_value(section, 'host') self.port = self.config.get_value(section, 'port') - self.prefix = self.config.get_value(section, 'prefix') + self.prefix = str(self.config.get_value(section, 'prefix')) except NoSectionError: logger.critical("No ZeroMQ configuration found") return False @@ -106,9 +105,9 @@ class Export(GlancesExport): # - First frame containing the following prefix (STRING) # - Second frame with the Glances plugin name (STRING) # - Third frame with the Glances plugin stats (JSON) - message = [str(self.prefix), - name, - json.dumps(data)] + message = [b(self.prefix), + b(name), + asbytes(json.dumps(data))] # Write data to the ZeroMQ bus # Result can be view: tcp://host:port From 6c502baf7aebd527a4e0ecf3489d4ebe04cd14ce Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 15 Oct 2016 19:08:07 +0200 Subject: [PATCH 13/89] Correct somes PEP8 errors --- glances/exports/glances_zeromq.py | 5 ++--- glances/main.py | 2 +- glances/plugins/glances_amps.py | 1 - glances/plugins/glances_docker.py | 8 ++++---- glances/plugins/glances_ip.py | 2 +- glances/plugins/glances_ports.py | 1 - glances/plugins/glances_quicklook.py | 1 - glances/plugins/glances_sensors.py | 4 ++-- glances/processes.py | 1 - 9 files changed, 10 insertions(+), 15 deletions(-) diff --git a/glances/exports/glances_zeromq.py b/glances/exports/glances_zeromq.py index b8ee760b..30f18765 100644 --- a/glances/exports/glances_zeromq.py +++ b/glances/exports/glances_zeromq.py @@ -20,16 +20,15 @@ """ZeroMQ interface class.""" import sys -from datetime import datetime -import time import json import zmq from zmq.utils.strtypes import asbytes -from glances.compat import NoOptionError, NoSectionError, u, b, nativestr +from glances.compat import NoOptionError, NoSectionError, b from glances.logger import logger from glances.exports.glances_export import GlancesExport + class Export(GlancesExport): """This class manages the ZeroMQ export module.""" diff --git a/glances/main.py b/glances/main.py index 57a0ce77..0d222c43 100644 --- a/glances/main.py +++ b/glances/main.py @@ -25,7 +25,7 @@ import sys import tempfile from glances import __appname__, __version__, psutil_version -from glances.compat import input, NoOptionError, NoSectionError +from glances.compat import input from glances.config import Config from glances.globals import LINUX, WINDOWS from glances.logger import logger diff --git a/glances/plugins/glances_amps.py b/glances/plugins/glances_amps.py index 5a8583d5..87b4cd15 100644 --- a/glances/plugins/glances_amps.py +++ b/glances/plugins/glances_amps.py @@ -21,7 +21,6 @@ from glances.compat import iteritems from glances.amps_list import AmpsList as glancesAmpsList -from glances.logger import logger from glances.plugins.glances_plugin import GlancesPlugin diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py index 0413dea5..f541f590 100644 --- a/glances/plugins/glances_docker.py +++ b/glances/plugins/glances_docker.py @@ -477,10 +477,10 @@ class Plugin(GlancesPlugin): header=i['name'] + '_mem', action_key=i['name']) if alert == 'DEFAULT': - # Not found ? Get back to default MEM threasold value - alert = self.get_alert(i['memory']['usage'], - maximum=i['memory']['limit'], - header='mem') + # Not found ? Get back to default MEM threasold value + alert = self.get_alert(i['memory']['usage'], + maximum=i['memory']['limit'], + header='mem') self.views[i[self.get_key()]]['mem']['decoration'] = alert return True diff --git a/glances/plugins/glances_ip.py b/glances/plugins/glances_ip.py index c859b8b4..adb1224f 100644 --- a/glances/plugins/glances_ip.py +++ b/glances/plugins/glances_ip.py @@ -22,7 +22,7 @@ import threading from json import loads -from glances.compat import iterkeys, urlopen, URLError, queue +from glances.compat import iterkeys, urlopen, queue from glances.globals import BSD from glances.logger import logger from glances.timer import Timer diff --git a/glances/plugins/glances_ports.py b/glances/plugins/glances_ports.py index fedd044c..a4d002a0 100644 --- a/glances/plugins/glances_ports.py +++ b/glances/plugins/glances_ports.py @@ -23,7 +23,6 @@ import os import subprocess import threading import socket -import types import time from glances.globals import WINDOWS diff --git a/glances/plugins/glances_quicklook.py b/glances/plugins/glances_quicklook.py index 58efc746..f67813a3 100644 --- a/glances/plugins/glances_quicklook.py +++ b/glances/plugins/glances_quicklook.py @@ -22,7 +22,6 @@ from glances.cpu_percent import cpu_percent from glances.outputs.glances_bars import Bar from glances.plugins.glances_plugin import GlancesPlugin -from glances.logger import logger import psutil diff --git a/glances/plugins/glances_sensors.py b/glances/plugins/glances_sensors.py index cc8f993e..4566b02d 100644 --- a/glances/plugins/glances_sensors.py +++ b/glances/plugins/glances_sensors.py @@ -263,8 +263,8 @@ class GlancesGrabSensors(object): try: sensors_current['label'] = feature.label sensors_current['value'] = int(feature.get_value()) - except SensorsError as e: - logger.debug("Cannot grab sensor stat(%s)" % e) + except Exception as e: + logger.debug("Cannot grab sensor stat (%s)" % e) else: self.sensors_list.append(sensors_current) diff --git a/glances/processes.py b/glances/processes.py index 844c5bc5..4f7ad336 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -19,7 +19,6 @@ import operator import os -import re from glances.compat import iteritems, itervalues, listitems from glances.globals import BSD, LINUX, OSX, WINDOWS From 1149608a97721ab226e2797d0277ccd42bbadff6 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 15 Oct 2016 19:16:24 +0200 Subject: [PATCH 14/89] Update docs for ZeroMQ export module --- docs/gw/zeromq.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/gw/zeromq.rst b/docs/gw/zeromq.rst index 9e4cdbef..5da68080 100644 --- a/docs/gw/zeromq.rst +++ b/docs/gw/zeromq.rst @@ -4,6 +4,7 @@ ZeroMQ ====== You can export statistics to a ``ZeroMQ`` server. + The connection should be defined in the Glances configuration file as following: @@ -14,9 +15,10 @@ following: port=5678 prefix=G -Note: Glances envelopes the stats in a publish message with two frames. +Note: Glances `envelopes`_ the stats before publishing it. +The message is composed of three frames. -- first frame containing the following prefix (as STRING) +- first frame containing the prefix configured in the [zeromq] section (as STRING) - second frame with the Glances plugin name (as STRING) - third frame with the Glances plugin stats (as JSON) @@ -24,7 +26,7 @@ Run Glances with: .. code-block:: console - $ glances --export-zeromq + $ glances --export-zeromq -C /glances.conf Following is a simple Python client to subscribe to the Glances stats: @@ -51,3 +53,5 @@ Following is a simple Python client to subscribe to the Glances stats: subscriber.close() context.term() + +.. _envelopes: http://zguide.zeromq.org/page:all#Pub-Sub-Message-Envelopes From d37c4306dfbef35d4ea49a54f702abbdece55b1c Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Mon, 17 Oct 2016 11:10:35 +0200 Subject: [PATCH 15/89] ZeroMQ export module, close the context --- conf/glances.conf | 3 ++- glances/exports/glances_zeromq.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index 8e30d07e..663a9a6e 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -322,7 +322,8 @@ db=glances [zeromq] # Configuration for the --export-zeromq option # http://www.zeromq.org -host=127.0.0.1 +# Use * to bind on all interfaces +host=* port=5678 # Glances envelopes the stats in a publish message with two frames: # - First frame containing the following prefix (STRING) diff --git a/glances/exports/glances_zeromq.py b/glances/exports/glances_zeromq.py index 30f18765..5a98179b 100644 --- a/glances/exports/glances_zeromq.py +++ b/glances/exports/glances_zeromq.py @@ -45,6 +45,7 @@ class Export(GlancesExport): sys.exit(2) # Init the ZeroMQ context + self.context = None self.client = self.init() def load_conf(self, section="zeromq"): @@ -74,8 +75,8 @@ class Export(GlancesExport): server_uri = 'tcp://{}:{}'.format(self.host, self.port) try: - context = zmq.Context() - publisher = context.socket(zmq.PUB) + self.context = zmq.Context() + publisher = self.context.socket(zmq.PUB) publisher.bind(server_uri) except Exception as e: logger.critical("Cannot connect to ZeroMQ server %s (%s)" % (server_uri, e)) @@ -86,8 +87,11 @@ class Export(GlancesExport): return publisher def exit(self): - """Close the socket""" - self.client.close() + """Close the socket and context""" + if self.client is not None: + self.client.close() + if self.context is not None: + self.context.destroy() def export(self, name, columns, points): """Write the points to the ZeroMQ server.""" From e711f7fb0941bb446400c090cdb8f88def272dcd Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 21 Oct 2016 11:15:02 +0200 Subject: [PATCH 16/89] SSID and Quality OK (issue #937) --- conf/glances.conf | 4 + glances/main.py | 2 + glances/outputs/glances_curses.py | 9 +- glances/plugins/glances_wifi.py | 185 ++++++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 glances/plugins/glances_wifi.py diff --git a/conf/glances.conf b/conf/glances.conf index 663a9a6e..ea37169b 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -111,6 +111,10 @@ critical=90 #wlan0_tx_critical=1000000 #wlan0_tx_log=True +#[wifi] +# Define the list of hidden wireless network interfaces (comma-separated regexp) +#hide=docker.*,lo + #[diskio] # Define the list of hidden disks (comma-separated regexp) #hide=sda2,sda5,loop.* diff --git a/glances/main.py b/glances/main.py index 0d222c43..960b7376 100644 --- a/glances/main.py +++ b/glances/main.py @@ -113,6 +113,8 @@ Start the client browser (browser mode):\n\ dest='disable_load', help='disable load module') parser.add_argument('--disable-network', action='store_true', default=False, dest='disable_network', help='disable network module') + parser.add_argument('--disable-wifi', action='store_true', default=False, + dest='disable_wifi', help='disable wifi module') parser.add_argument('--disable-ports', action='store_true', default=False, dest='disable_ports', help='disable ports scanner module') parser.add_argument('--disable-ip', action='store_true', default=False, diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index b22e9d33..5f51ece8 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -444,6 +444,9 @@ class _GlancesCurses(object): elif self.pressedkey == ord('w'): # 'w' > Delete finished warning logs glances_logs.clean() + elif self.pressedkey == ord('W'): + # 'W' > Enable/Disable Wifi plugin + self.args.disable_wifi = not self.args.disable_wifi elif self.pressedkey == ord('x'): # 'x' > Delete finished warning and critical logs glances_logs.clean(critical=True) @@ -540,6 +543,8 @@ class _GlancesCurses(object): stats_memswap = stats.get_plugin('memswap').get_stats_display(args=self.args) stats_network = stats.get_plugin('network').get_stats_display( args=self.args, max_width=plugin_max_width) + stats_wifi = stats.get_plugin('wifi').get_stats_display( + args=self.args, max_width=plugin_max_width) stats_irq = stats.get_plugin('irq').get_stats_display( args=self.args, max_width=plugin_max_width) try: @@ -718,13 +723,15 @@ class _GlancesCurses(object): # ================================================================== self.init_column() if not (self.args.disable_network and + self.args.disable_wifi and self.args.disable_ports and self.args.disable_diskio and self.args.disable_fs and + self.args.disable_irq and self.args.disable_folder and self.args.disable_raid and self.args.disable_sensors) and not self.args.disable_left_sidebar: - for s in (stats_network, stats_ports, stats_diskio, stats_fs, stats_irq, stats_folders, stats_raid, stats_sensors, stats_now): + for s in (stats_network, stats_wifi, stats_ports, stats_diskio, stats_fs, stats_irq, stats_folders, stats_raid, stats_sensors, stats_now): self.new_line() self.display_plugin(s) diff --git a/glances/plugins/glances_wifi.py b/glances/plugins/glances_wifi.py new file mode 100644 index 00000000..03b36db0 --- /dev/null +++ b/glances/plugins/glances_wifi.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2016 Nicolargo +# +# Glances is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Glances 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""Wifi plugin.""" + +import base64 +import operator + +from glances.logger import logger +from glances.timer import getTimeSinceLastUpdate +from glances.plugins.glances_plugin import GlancesPlugin + +import psutil +# Use the Wifi Python lib (https://pypi.python.org/pypi/wifi) +# Linux-only +try: + from wifi.scan import Cell + from wifi.exceptions import InterfaceError +except ImportError as e: + logger.debug("Wifi library not found. Glances cannot grab Wifi info.") + wifi_tag = False +else: + wifi_tag = True + + +class Plugin(GlancesPlugin): + + """Glances Wifi plugin. + Get stats of the current Wifi hotspots. + """ + + def __init__(self, args=None): + """Init the plugin.""" + super(Plugin, self).__init__(args=args) + + # We want to display the stat in the curse interface + self.display_curse = True + + # Init the stats + self.reset() + + def get_key(self): + """Return the key of the list. + + :returns: string -- SSID is the dict key + """ + return 'ssid' + + def reset(self): + """Reset/init the stats to an empty list. + + :returns: None + """ + self.stats = [] + + @GlancesPlugin._check_decorator + @GlancesPlugin._log_result_decorator + def update(self): + """Update Wifi stats using the input method. + + Stats is a list of dict (one dict per hotspot) + + :returns: list -- Stats is a list of dict (hotspot) + """ + # Reset stats + self.reset() + + # Exist if we can not grab the stats + if not wifi_tag: + return self.stats + + if self.input_method == 'local': + # Update stats using the standard system lib + + # Grab network interface stat using the PsUtil net_io_counter method + try: + netiocounters = psutil.net_io_counters(pernic=True) + except UnicodeDecodeError: + return self.stats + + for net in netiocounters: + # Do not take hidden interface into account + if self.is_hide(net): + continue + + # Grab the stats using the Wifi Python lib + try: + wifi_cells = Cell.all(net) + except InterfaceError: + # Not a Wifi interface + pass + else: + for wifi_cell in wifi_cells: + hotspot = { + 'key': self.get_key(), + 'ssid': wifi_cell.ssid, + 'signal': wifi_cell.signal, + 'quality': wifi_cell.quality, + 'encrypted': wifi_cell.encrypted, + 'encryption_type': wifi_cell.encryption_type if wifi_cell.encrypted else None + } + # Add the hotspot to the list + self.stats.append(hotspot) + + elif self.input_method == 'snmp': + # Update stats using SNMP + + # Not implemented yet + pass + + # Update the view + self.update_views() + + return self.stats + + def update_views(self): + """Update stats views.""" + # Call the father's method + super(Plugin, self).update_views() + + # Add specifics informations + # Alert + # for i in self.stats: + # ifrealname = i['interface_name'].split(':')[0] + # self.views[i[self.get_key()]]['rx']['decoration'] = self.get_alert(int(i['rx'] // i['time_since_update'] * 8), + # header=ifrealname + '_rx') + # self.views[i[self.get_key()]]['tx']['decoration'] = self.get_alert(int(i['tx'] // i['time_since_update'] * 8), + # header=ifrealname + '_tx') + + def msg_curse(self, args=None, max_width=None): + """Return the dict to display in the curse interface.""" + # Init the return message + ret = [] + + # Only process if stats exist and display plugin enable... + if not self.stats or args.disable_wifi or not wifi_tag: + return ret + + # Max size for the interface name + if max_width is not None and max_width >= 23: + # Interface size name = max_width - space for interfaces bitrate + ifname_max_width = max_width - 14 + else: + ifname_max_width = 9 + + # Build the string message + # Header + msg = '{:{width}}'.format('WIFI', width=ifname_max_width) + ret.append(self.curse_add_line(msg, "TITLE")) + msg = '{:>14}'.format('Quality') + ret.append(self.curse_add_line(msg)) + ret.append(self.curse_new_line()) + + # Hotspot list (sorted by name) + for i in sorted(self.stats, key=operator.itemgetter(self.get_key())): + # Do not display hotspot with no name (/ssid) + if i['ssid'] == '': + continue + # New hotspot + hotspotname = i['ssid'] + if len(hotspotname) > ifname_max_width: + # Cut hotspotname if it is too long + hotspotname = '_' + hotspotname[-ifname_max_width + 1:] + msg = '{:{width}}'.format(hotspotname, width=ifname_max_width) + ret.append(self.curse_add_line(msg)) + msg = '{:>14}'.format(i['quality'], width=ifname_max_width) + ret.append(self.curse_add_line(msg)) + + return ret From 092930d4a07f9a2e3111f2982f49a443fbb654a9 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 21 Oct 2016 17:06:03 +0200 Subject: [PATCH 17/89] Add encryption (issue #937) --- conf/glances.conf | 4 ++-- glances/plugins/glances_wifi.py | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index ea37169b..6ada518a 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -111,9 +111,9 @@ critical=90 #wlan0_tx_critical=1000000 #wlan0_tx_log=True -#[wifi] +[wifi] # Define the list of hidden wireless network interfaces (comma-separated regexp) -#hide=docker.*,lo +hide=lo,docker.* #[diskio] # Define the list of hidden disks (comma-separated regexp) diff --git a/glances/plugins/glances_wifi.py b/glances/plugins/glances_wifi.py index 03b36db0..6b614aef 100644 --- a/glances/plugins/glances_wifi.py +++ b/glances/plugins/glances_wifi.py @@ -154,16 +154,16 @@ class Plugin(GlancesPlugin): # Max size for the interface name if max_width is not None and max_width >= 23: - # Interface size name = max_width - space for interfaces bitrate - ifname_max_width = max_width - 14 + # Interface size name = max_width - space for encyption + quality + ifname_max_width = max_width - 5 else: - ifname_max_width = 9 + ifname_max_width = 16 # Build the string message # Header msg = '{:{width}}'.format('WIFI', width=ifname_max_width) ret.append(self.curse_add_line(msg, "TITLE")) - msg = '{:>14}'.format('Quality') + msg = '{:>6}'.format('Quality') ret.append(self.curse_add_line(msg)) ret.append(self.curse_new_line()) @@ -174,12 +174,16 @@ class Plugin(GlancesPlugin): continue # New hotspot hotspotname = i['ssid'] + # Add the encryption type (if it is available) + if i['encrypted']: + hotspotname = hotspotname + ' {}'.format(i['encryption_type']) + # Cut hotspotname if it is too long if len(hotspotname) > ifname_max_width: - # Cut hotspotname if it is too long hotspotname = '_' + hotspotname[-ifname_max_width + 1:] + # Add the new hotspot to the message msg = '{:{width}}'.format(hotspotname, width=ifname_max_width) ret.append(self.curse_add_line(msg)) - msg = '{:>14}'.format(i['quality'], width=ifname_max_width) + msg = '{:>7}'.format(i['quality'], width=ifname_max_width) ret.append(self.curse_add_line(msg)) return ret From deb42a075ff3e4d2b1c08c919dd77c9a8b93a180 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 21 Oct 2016 18:03:33 +0200 Subject: [PATCH 18/89] Add documentation (issue #937) --- NEWS | 3 ++- docs/_static/wifi.png | Bin 0 -> 9385 bytes docs/aoa/index.rst | 1 + docs/aoa/network.rst | 2 +- docs/aoa/wifi.rst | 18 ++++++++++++++++++ docs/man/glances.1 | 2 +- glances/plugins/glances_wifi.py | 2 +- 7 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 docs/_static/wifi.png create mode 100644 docs/aoa/wifi.rst diff --git a/NEWS b/NEWS index def52859..a6fa9eef 100644 --- a/NEWS +++ b/NEWS @@ -5,10 +5,11 @@ Glances Version 2 Version 2.8 =========== -Enhancements and new features: +Enhancements and news features: * Add ZeroMQ exporter (issue #939) * Add CouchDB exporter (issue #928) + * Add hotspot Wifi informations (issue #937) * Highlight max stats in the processes list (issue #878) * Docker alerts and actions (issue #875) * Glances API returns the processes PPID (issue #926) diff --git a/docs/_static/wifi.png b/docs/_static/wifi.png new file mode 100644 index 0000000000000000000000000000000000000000..1b865b2a2301a8c80ebc13107b3402a7ce6e0e89 GIT binary patch literal 9385 zcmai4Wl&sAm%eC#APEG5TW}AM;F7`J-9wNGGQnZ+1YQCJcX!v|3=A&8-3ATr?z+6S z^=;MG{@AYWd%FAAsZ-U@dCqfBu&S~Q4i+gE00200vXbi07XG}FFp!_W7e7m7o(+l< zNKO+217rTL%F6Tcm9vztvj)V{+0D$+0?@E_adx(FH2*$|2>{f9oa84>cf>)Oo9-K( znYNL^kGk>3xtL74uO&28e@m89Um90-;oIQnczykh`Mb;5oHO%tI};0aHZ`&WCo5ZJ zrnJ=SsPMLqy2fRymsQ9wzw-8{+rFyizHN}BSQ0L*<%^^Cg1Yv_ zf%nf>PzT3Yq=T1Xctg&&T1XL`Pjc{$J&|&P6!&FLf?exz>g)yv0KyPHt$`5hAvC|T0Y)nyyc7A zZlYiX+KdhTX5`WONel5P-KF~YA_m!UO2QZ;U)<)#9-@$#BRU%#%I%viBg6zpI*r!p zp+aYP+R)~C@<;RIQ>TRV?yDt@3lZKN!3f@cDcp_?9Xqy}!oZw%;Yp+X zF&YXFN4N?(y#_`2)|(*}iM`JyqB&WVyOm6X%O%=KB4_c@W6@}O+Z96L@Wzj0b_D>< z`TB?%bUmS_mv>@Ehv%q#kGa);PTAA^T8EKj%x`l0VIYkuBWY6+psu}zG*Wn4Mw3XK zl-M@m)SE}(PL(BO>YCivU{l{-vhGaqv-jKUAJHV`#VyCt2b4;>sdo8jz zD0LC2@b>(;aZ$I-aGLV$A{GF&NH2PmH3a0#tCNL4(O_3{)jC&MqUhq(@idj79qX(9 z*6(L~iGIXZktJeCt$O|obC5Gej?(AAZL2slo?s%mkor3osJuzA6Gsi@~& zka(*p`P@%!;Rlk~Zowucpmz4o za%uN0(+VGlzhoq>e%pay&Ns;5l_mSGb%_S0;y^W09L5>#s%EgZ5_@_-IHqkfKP{7O z&Rxh~NT0K;5k?+B`SEyJ{%+amM{Hjxz6_XCS>`I61*q8w)B$nKfdHUw+H69+oDZ(j z8k>&p*B{VXy>;;V=O?A545$z~P?Nzu@1wz8kMFJLO>>cN^plh`r#f*wNC*D*@kE@7 zs%D>u)eT`R>|==?N_qWI>UqS(Uo!A3P^3!bk)_bPDPC2*YxMomtS$bQ8{DX2hnSsoV9p>76mQYSt1q|d_=hsU^r&6Ixl#4hTdEZ)#^=|SB+ z_@)!;cr@q2@(30d@+;0gKJSm}<(LKJ-nb1rfwg-^Dh%aI)HUtZdo51hOdp`Qy(GQo zR-4YssP9S~79vlKeeLovD(pGrYlrlR?b!bi9D6iBQM-6Quu;!P^>-N<*u3h+pb1bX zawGee4bTLr?hm>Kge1d-!Vh)8qI1krWTUy z&%%8>_ZwEELC5`2rTiVQI~nVmqvSNV?1z?WO;w0=uapk{Q!;qE8AYN}v32oP zl%rD=+S=J2Uxvi6+c`#@)Zv9E+RzQ3qZclFt+pCVWdk)26_80Sdf_phrbq(BK0Rb( zOm8A9XZQoENx;P(2tA|rY*9EKFJ!~PN#r+EF1+JQ#&R&CQyzyjv+gtT+ zPM-Ou@cjw37&&UNl1rJQz5)Az|M*_)Kma`H7erAHuyG2G&Kx8S?#y-9fPT&?Frc;1QWz{2b{0RWEPzJo{R** z?(t#|#hEj;IdqR+;?N}Qf#82L&!CRK{79JF=S!g???JaDBW?5oL+6S1CATx&z`5qk zfKY<-zBrd7Kt@3c1Npd(Y)I~*CWVCVQ z3)Ng~nh+ytJ^c`v_4ElvU;`>qs2e_y|0;ZAvx$|6Ux*qevCeygFh2p57Kc=i_|&Qi0|#luuYq-Q-M!)(y?Q(xyfOGD+|*oy!|)=J*GDK_d(a z13+Q_+X#hRI)yZ;pT$gR#yLF^E(dLeMN%xJY*jHP!lwr#0 zuXgIng23qHg+QTjg(4Qn;*qs&dSY*Jc2~ITRzT20ncDfbK8=tS(W)`JdU^5=g*d1b|mM#DK zb8&kN#ae1OQr5vEh1#9GWGx9CtmXrjdL+B?lV`yyCax32SsRUC&qeXuS2Ck_9MyW4 zrHC%RwOu8L3HX)5u5re>rm|6Fk!0)qIdWLat4TLQUefxv`8ci_<@@vInTR|;l zj4bbz01%(yozCpEc@IcTM84x-zj1uqB^DKf3V?kyWy^Pn>%-Xq*xxqraIvF7V7~?= z-$9y<(xRJzbNVA1fFurY%B5L@;z_~(y-E1PPe4}#yBerswwK3&z$ zT*U7iO>2Y!dXFMv={r|8JqCNDnBTL^EX5pS!?a&gW^ql3)pT5Xju^wR>+*V|f9vCM z$Pm?JiNwaQGb%9*li2ptO$kOAP2zPg{VBEYbi`+&=(TTw-W?vaBHv#fHT(%H^F5S- z@U0X~w96q=ioNUby~{2C3OOp!6m)k_YwlEfdrV1(Mrpv%nxXD>C{yXl(Hcd$D zO8voec){m!35C)mGWm@Q_({DNCd2~jefSI-UB&&yFhXc~*htE_axwwQ(Em`$F4_u~ z_JKWvP#5*lhu)6oHyhAYkihS|!+usr%lIGSe0GsN?Kz)z_r8zW%YQk*b=K+8_P(N9 zO_Q;l;-mG-^+SY|yS>=l1ABN*o7cAOz0N9AY&LH=@&xiz96wf`eh%C7ezo#J=J`JP zeV)UcL?XLuU+VB|dbk>?j6XQ4{sgnPM*!zG$)GG7G^x!JtTH-%K!a!t)Dr5xBMX;Ecf;%HoRSofVhzt2T)VWq!XsVDtDaq+9ec`JIZ; z-QTkdALv=uD6f+4+*b4EuWuyW2v!i}ap_@VzMf9bU_{8j*wLO+U!dDN^DHx~S|vVw zsSYBYBiyEVhUuI7FvV{=mi-V+_|);kOg{0gn_2J367zV^%|^FJZ^6}X7ss0CndYjU z?s_S)wMPM0+i4W#{v1@jT1Qy+K>pPAl2ZQQP3PyoW$QwBkNgzhHl%0MGS@<(;1~d? z)nkO?nM4eD=&7Ibp6>7PcJw#G?{Cb`Txb!{CCs^$pp)Pvm6!6YEFq^RL|7#G(+8yL zF#P{^teeU7Z#D{!{F&YwQ(;KW-rEY9ye%!hMH~=!ur3`@a2IVpn{Fxny$k z;7=!|aF(~qw0&Sj)tb-|2J`Os)E>!K*lapOJ0g%#i*piF87T@yhcCLfCXTv@=(1Id zYJX@BK$Z)gK}_tm&w)^@q7xN@o!>^-hxiNdE`GA%i*R>wo-py?2D$}bvlI%7vD1|N z`mTL>GLN-dV3iO?B5L2$->Fl+(EOC!(t=tf<^}UGOR5{FJls7>9ZUF`BVH>Y+RB#R zWZDyOvv+SSR3`MhB1&x3y=BFFm8lTl7*wQ3rO*jMx7iAzKzHWdF&prCGu4ltUs~Ky z%#kMP0uC!~*_`Kd0qc8fQ!Os69Y%zDIqu$CwS{lfng5h_$Cl!0Kk$!u)I-1$4)+HhlVf9t zH`#Ab^a3vYXgX_X!>@^iuy@4TxH$DyO&SZpUV#gT;QE>PJ5@!Bcgl8hfeV6THFr>o zDG+tYe)@*q3KT4NZvm?S68`@Zf4Jz81$^zG@MjP^~HxNf)D zp-c0ZuDT0*9OL=75jmN3_*k-493yejH_%r8kmTN|kzWOa^@CUnVVH~z?6>(WG-bI< zrXRv8B7Mi4Pn+BG6{9EYR%sc*wyMfFfKvSvBBtpsuk_*ZTXE#@=l6QpjWFgSg0Y@6 zu%{>zAJX}SR(;Z&59E|W`(r;&aX=s1(NS?PyzIt+`tU1rW zzrxA4udTW!VMqB8QsD#X*HVQpjwV-k5F!LN9Y-J5(#gP-7YUHLyM8d><^fsiy;zqu zW=y}(_oi>f2@Iqm**;He_3X2xAoN3gf6;F6)?TF)bgXP2h6oZZ!EH3YLWr_jED4bK z(OnsD@Glpp^+|U#L+E!W54a z#S*LXDp`KX%#^|DH}a10U+6$>9ph(OYkipx#hxbn519TZar|H5C9xt^`!uNVe8HVD zzXf?y?4LF04Gw5b<6^m`@PD+Gua{TMpN=A%M`O`5KiBhIEsm&-dl8TpGJ_+)IO+v6 z+3)!9QH6b^6PNSVHlL^)`40S zm(>%Hq)v;6o!K>%B6CGp%KPwyJV2HRkA-u8#Q45=>+D;-BrTjH3C(Yu#@`iOa)RCqd%a~dqE7aX>| z|ILNnlroN0eY(MErJ&$wEQ<}R)v+@Z$5g*PUM*(k_=tNc!6#g4Pz`ZNAewN`tU8{0 zXC%!5Go;hH*1WGDNrf26u)Qo}YN>Ot+!Igu%Y(cA6X;U;exISIL`mb@9cy;@ap^wm zsmxIB9sD)C-yQ8s6Xh^UnEdsPsYtdCBm1jOQFh3|lw4R#jHL(k-@!pXk2w>3d>@T-t}5gJy`42H5SIhRqEVNKBezV`6W0q7ra$&m3*!6ODAAD6o|f#u8KUit6@gH$8;NE=d~EqJ68c86?J&N)o>B%aryik z7Va`5e-g@Kzn&irIF9HJG`9Xd8@>DMut8*BH+ZF&&y|7ES5hjR^oj4?jk?6l!9u!T zYz(6$z|D-t%Mcc6Kz{+#q|JS-wDV-_8|I{#voaMc$Vz8CqZ`fqAb%QE)9?#vPIj+w zyy5Kq{_7YDs1j-asOADVY?q5xt;j!>16?2=LoN)DT8*ZRj-osCmAX6id(zeSuq6-b3!i@ zow==gM&OjoTwG;hK;d)A%lR`x>E|yjeVeOknmn=y#4c4#dSs&4Q-8lb&SR~7{nTsW zmiuAcGnY=@jLJLF5#PA#%>-YE3`^cQF&cL%MuznGk6D7!@YFJX6GJ;$m)@x?JdQ!$ zoHge5=7L>)c367XRuK8=es0L3#=o#vV}=ojo1>kEv2prbzJL9B1oMaRl*B;o3%K3y zfBs{@EB(w7(VBT%9ZvU)Qy=eQqFwomro4xr`Q`V9%2rnn3fsZ2S@4#%%Yf^5KlqPI zOIVRTkTiagK+QQ>r?XZ9pT%Hgz|{2&OYOdyV>Cc>`qB3d{e>ieF{Nzo+vLnA)wX7} zfH%7bo>%UKmBT}xe3$pmfj-pVC#iqhca1RP< zBiBIlH7XleTi#X7Fz}8LW&=RwsZTmo2%5C;vPT$eUmkf%Ht@_J!R#>q$D>|n4W%<9M|c~o(^F52I^ zAf)JEsb`yi+!h@j@^#~a`I*;Bwm`f_Nh^J;;lMr@PIN#9&Y8lBjp!yhjs*)mw=7P&&&JIu3-R=if6Fq?JYg!q8$(BZ?N zWYX1BIT+>zo{Ok1P)BaOI$S3SBmX*`$-mlKalLVs!6ObHBmTQKyOkSPofJ}anf?72 zZCxOeOnuXbYDw$qd63bJdco#SPSr?CM?;(-!_YMA&1gfQ?}l+BSR@KjdM3I9rIaY+ zr<-VN_XZgEv>k&xtfLPM6QHsc6d@d0q@2dM)iNcFsZBc9Ef?Cd0JT=_oTO4?CHM739lm0EKc$6pcdx^XT73(~`>|WSI zQv{!~x1#FD*{0Fy@^U2~1z4^|(Z#w8NwgD4UV47X5azTt^!PF9l(Z+mcov+%Snrbo zWu*gDKUjcri@WW2CBU=cN8N+4hfL()^rf}<Ie@9$;k)u3sHE}p28U=k%9pw*X~Ym<2zw#+zVoXN%Pg7bvzY~ zWk-q_%e^?S!-l{J{nF$~0vg^_zEr @mX;LND%os+GCoQx~@`r}OAt42e|C7Dd0+ z%h992iso&5PpMm9xR~FF{u|azma4T=-DwoTkBBx}{*UIVT42}KK&#Vh;bl3P>HDvd z!QZJ2Qe1XEH|{n23>|t@m)$0-MXY7au-I`j4|>QQ2{(wbi-&p%5%@~Zs)V{ICKu_9J1%s*-LeyTEiE)pnVL}luy$(0EC=p!X=QU=jxyT4 zs#$xa6(q3+$;%<{H?Y>$Zv5wc7RGEsF$zRYo0Lsow=26^=)_WgX+()l?#7 z8Av$%bDv-bmf zsXGVMus(|IDq4eEPay1sLth@X4!vFxS~{R;yQlwTD*&uBbKwoDk&Hk3XL~`B>jk+w zfReqvEPQP1=y8bv0AOkB%HKQAcoyrvu+&OYAzJdD-`*sv|9$3AP-@*ZD)bGZPK!r! zvL^M%r)X$SX*z~xmpi9u!{bbu+veA?Oa?Lf?HLOz%1|UfOKxbeP4~u*1EJDtg|kr$ zSx3m7*5T_Js!Y2XgPWNflQNx-x(9mN+n@6V!ko)Dv$*gQ$`badVxh3CfMhWDp*><|ITO+%*;KhJ1J1kyzRp{DZ{ zL6h4%mxJIhB2wj#qvIT`!;PJLY2_5(fY`F{xRY^JNbAC)i6bvdc5tPlXRXgwxZ_&2 znm5m6BS}B<)R{DlY@MJjWJ;Xv*UCOCsefn9>uKMGkMOFKRq`=KlTkuv7yZ!X&ptAh zsf-DqLJ8{eaAqdSeAkU7;Fwiudm+pSH8S5b1?Ix?vOk{R_PR)XJ}`-tSNSB1RS=zO zM#|s%M%=#5z?1Y>M2vs>kuuAk z6$PEhHi>0{kURf5Y)SZA6}a;|#$F`s{XLV@wR*G?3P8>K$6kzkA^g1zl?ASe3g47g zow5Zg#}V7es5q%2|Hw1Um7;RL|RT7*fY`Pz?E$or&Al;GAO|1mLdNVK08=Rq4-DR zoZz zql<167yObzCeLYFE}m%4jVk8{CE$1I{LdX}vLJyU>Qs0xkxUWXLqUQPGx@|+Wh+D> z#XBanOLJ(#K;}oFvbmi;OTwkc*~d|SL~$WZQ5LBXVb`407OJv&NKtsw-mB$k%)VZv zI{xH2*?KLUkG`}J*K-sl35m~Ll7MQEz;6jqfp<2_MTj&DydKl}5Vl`J{=db^T8z zv0^c|pFPNRSLu(~3!oSoGqA9j`0Ht8E~(ZOU{E%k7)3tHMQ4riHC=T>tN)%1O-X%9 z(;tCMgXVm2sn5Gs#P~zHulIY;cq}GA8$L^%hbVUEr!#o`FGt?3QNa2uY3ZO!QvD}w zEp3IG2~GN}s*mO)shDbFCc>tFXk)2#`jE@lpZ_N%sKRn1^0IP8Riu*G@5aU$Yy0mC zu#SurFK*2Y3npGzn=jU?*YOqko+K5ns+|^<`mlSC`p*?mwTDR4V*Yu>1z5O;QXzAB zPKba1f*~G&>f29Ty(966TKp4=ANa*Ps)pn58)|?~!VxbRBpwX_gnW5k(U!Hbe>2hI zz*WvBdU7tT(03#693DK_Wd}}KB(#7Rjc*OXbdUQc{-s5t~V|RfoWD;XSG&i5JU6r1f+*%|xQqjRzW!~SDFNbP`;sH_EvPmXkRFPk)Dyl z8;R)+ZA1ZcC4N*1#pP#$?niIEiHg~w#GW2Hsi$jwv72^;_>=Yp$utaB@bZ35p zpi)^{EX0k@{x!Pj8B;8-J*VFC)ACl$)wusuvnDYDl=zoQ{rxn-{bT=|M*}=VOe3A- z1H Date: Fri, 21 Oct 2016 22:06:02 +0200 Subject: [PATCH 19/89] Add thresholds and update docs (issue #937) --- conf/glances.conf | 5 ++++ docs/_static/wifi.png | Bin 9385 -> 8692 bytes docs/aoa/wifi.rst | 17 +++++++++--- glances/plugins/glances_plugin.py | 16 ++++++------ glances/plugins/glances_wifi.py | 42 ++++++++++++++++++++++-------- 5 files changed, 58 insertions(+), 22 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index 6ada518a..41c6702f 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -114,6 +114,11 @@ critical=90 [wifi] # Define the list of hidden wireless network interfaces (comma-separated regexp) hide=lo,docker.* +# Define SIGNAL thresholds in db (lower is better...) +# Based on: http://serverfault.com/questions/501025/industry-standard-for-minimum-wifi-signal-strength +careful=-65 +warning=-75 +critical=-85 #[diskio] # Define the list of hidden disks (comma-separated regexp) diff --git a/docs/_static/wifi.png b/docs/_static/wifi.png index 1b865b2a2301a8c80ebc13107b3402a7ce6e0e89..328c556ebfb1a06873583ef5d4722f1aceccd87a 100644 GIT binary patch literal 8692 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 @mX;LND%os+GCoQx~@`r}OAt42e|C7Dd0+ z%h992iso&5PpMm9xR~FF{u|azma4T=-DwoTkBBx}{*UIVT42}KK&#Vh;bl3P>HDvd z!QZJ2Qe1XEH|{n23>|t@m)$0-MXY7au-I`j4|>QQ2{(wbi-&p%5%@~Zs)V{ICKu_9J1%s*-LeyTEiE)pnVL}luy$(0EC=p!X=QU=jxyT4 zs#$xa6(q3+$;%<{H?Y>$Zv5wc7RGEsF$zRYo0Lsow=26^=)_WgX+()l?#7 z8Av$%bDv-bmf zsXGVMus(|IDq4eEPay1sLth@X4!vFxS~{R;yQlwTD*&uBbKwoDk&Hk3XL~`B>jk+w zfReqvEPQP1=y8bv0AOkB%HKQAcoyrvu+&OYAzJdD-`*sv|9$3AP-@*ZD)bGZPK!r! zvL^M%r)X$SX*z~xmpi9u!{bbu+veA?Oa?Lf?HLOz%1|UfOKxbeP4~u*1EJDtg|kr$ zSx3m7*5T_Js!Y2XgPWNflQNx-x(9mN+n@6V!ko)Dv$*gQ$`badVxh3CfMhWDp*><|ITO+%*;KhJ1J1kyzRp{DZ{ zL6h4%mxJIhB2wj#qvIT`!;PJLY2_5(fY`F{xRY^JNbAC)i6bvdc5tPlXRXgwxZ_&2 znm5m6BS}B<)R{DlY@MJjWJ;Xv*UCOCsefn9>uKMGkMOFKRq`=KlTkuv7yZ!X&ptAh zsf-DqLJ8{eaAqdSeAkU7;Fwiudm+pSH8S5b1?Ix?vOk{R_PR)XJ}`-tSNSB1RS=zO zM#|s%M%=#5z?1Y>M2vs>kuuAk z6$PEhHi>0{kURf5Y)SZA6}a;|#$F`s{XLV@wR*G?3P8>K$6kzkA^g1zl?ASe3g47g zow5Zg#}V7es5q%2|Hw1Um7;RL|RT7*fY`Pz?E$or&Al;GAO|1mLdNVK08=Rq4-DR zoZz zql<167yObzCeLYFE}m%4jVk8{CE$1I{LdX}vLJyU>Qs0xkxUWXLqUQPGx@|+Wh+D> z#XBanOLJ(#K;}oFvbmi;OTwkc*~d|SL~$WZQ5LBXVb`407OJv&NKtsw-mB$k%)VZv zI{xH2*?KLUkG`}J*K-sl35m~Ll7MQEz;6jqfp<2_MTj&DydKl}5Vl`J{=db^T8z zv0^c|pFPNRSLu(~3!oSoGqA9j`0Ht8E~(ZOU{E%k7)3tHMQ4riHC=T>tN)%1O-X%9 z(;tCMgXVm2sn5Gs#P~zHulIY;cq}GA8$L^%hbVUEr!#o`FGt?3QNa2uY3ZO!QvD}w zEp3IG2~GN}s*mO)shDbFCc>tFXk)2#`jE@lpZ_N%sKRn1^0IP8Riu*G@5aU$Yy0mC zu#SurFK*2Y3npGzn=jU?*YOqko+K5ns+|^<`mlSC`p*?mwTDR4V*Yu>1z5O;QXzAB zPKba1f*~G&>f29Ty(966TKp4=ANa*Ps)pn58)|?~!VxbRBpwX_gnW5k(U!Hbe>2hI zz*WvBdU7tT(03#693DK_Wd}}KB(#7Rjc*OXbdUQc{-s5t~V|RfoWD;XSG&i5JU6r1f+*%|xQqjRzW!~SDFNbP`;sH_EvPmXkRFPk)Dyl z8;R)+ZA1ZcC4N*1#pP#$?niIEiHg~w#GW2Hsi$jwv72^;_>=Yp$utaB@bZ35p zpi)^{EX0k@{x!Pj8B;8-J*VFC)ACl$)wusuvnDYDl=zoQ{rxn-{bT=|M*}=VOe3A- z1H self.__get_limit('critical', stat_name=stat_name): + if value >= self.get_limit('critical', stat_name=stat_name): ret = 'CRITICAL' - elif value > self.__get_limit('warning', stat_name=stat_name): + elif value >= self.get_limit('warning', stat_name=stat_name): ret = 'WARNING' - elif value > self.__get_limit('careful', stat_name=stat_name): + elif value >= self.get_limit('careful', stat_name=stat_name): ret = 'CAREFUL' elif current < minimum: ret = 'CAREFUL' @@ -516,7 +516,7 @@ class GlancesPlugin(object): # Manage log log_str = "" - if self.__get_limit_log(stat_name=stat_name, default_action=log): + if self.get_limit_log(stat_name=stat_name, default_action=log): # Add _LOG to the return string # So stats will be highlited with a specific color log_str = "_LOG" @@ -537,7 +537,7 @@ class GlancesPlugin(object): """Manage the action for the current stat""" # Here is a command line for the current trigger ? try: - command = self.__get_limit_action(trigger, stat_name=stat_name) + command = self.get_limit_action(trigger, stat_name=stat_name) except KeyError: # Reset the trigger self.actions.set(stat_name, trigger) @@ -578,7 +578,7 @@ class GlancesPlugin(object): action_key=action_key, log=True) - def __get_limit(self, criticity, stat_name=""): + def get_limit(self, criticity, stat_name=""): """Return the limit value for the alert.""" # Get the limit for stat + header # Exemple: network_wlan0_rx_careful @@ -594,7 +594,7 @@ class GlancesPlugin(object): # Return the limit return limit - def __get_limit_action(self, criticity, stat_name=""): + def get_limit_action(self, criticity, stat_name=""): """Return the action for the alert.""" # Get the action for stat + header # Exemple: network_wlan0_rx_careful_action @@ -608,7 +608,7 @@ class GlancesPlugin(object): # Return the action list return ret - def __get_limit_log(self, stat_name, default_action=False): + def get_limit_log(self, stat_name, default_action=False): """Return the log tag for the alert.""" # Get the log tag for stat + header # Exemple: network_wlan0_rx_log diff --git a/glances/plugins/glances_wifi.py b/glances/plugins/glances_wifi.py index a7036fab..b42939f1 100644 --- a/glances/plugins/glances_wifi.py +++ b/glances/plugins/glances_wifi.py @@ -129,19 +129,36 @@ class Plugin(GlancesPlugin): return self.stats + def get_alert(self, value): + """Overwrite the default get_alert method. + Alert is on signal quality where lower is better... + + :returns: string -- Signal alert + """ + + ret = 'OK' + try: + if value <= self.get_limit('critical', stat_name=self.plugin_name): + ret = 'CRITICAL' + elif value <= self.get_limit('warning', stat_name=self.plugin_name): + ret = 'WARNING' + elif value <= self.get_limit('careful', stat_name=self.plugin_name): + ret = 'CAREFUL' + except KeyError: + ret = 'DEFAULT' + + return ret + def update_views(self): """Update stats views.""" # Call the father's method super(Plugin, self).update_views() # Add specifics informations - # Alert - # for i in self.stats: - # ifrealname = i['interface_name'].split(':')[0] - # self.views[i[self.get_key()]]['rx']['decoration'] = self.get_alert(int(i['rx'] // i['time_since_update'] * 8), - # header=ifrealname + '_rx') - # self.views[i[self.get_key()]]['tx']['decoration'] = self.get_alert(int(i['tx'] // i['time_since_update'] * 8), - # header=ifrealname + '_tx') + # Alert on signal thresholds + for i in self.stats: + self.views[i[self.get_key()]]['signal']['decoration'] = self.get_alert(i['signal']) + self.views[i[self.get_key()]]['quality']['decoration'] = self.views[i[self.get_key()]]['signal']['decoration'] def msg_curse(self, args=None, max_width=None): """Return the dict to display in the curse interface.""" @@ -163,15 +180,15 @@ class Plugin(GlancesPlugin): # Header msg = '{:{width}}'.format('WIFI', width=ifname_max_width) ret.append(self.curse_add_line(msg, "TITLE")) - msg = '{:>6}'.format('Quality') + msg = '{:>7}'.format('dBm') ret.append(self.curse_add_line(msg)) # Hotspot list (sorted by name) for i in sorted(self.stats, key=operator.itemgetter(self.get_key())): - ret.append(self.curse_new_line()) # Do not display hotspot with no name (/ssid) if i['ssid'] == '': continue + ret.append(self.curse_new_line()) # New hotspot hotspotname = i['ssid'] # Add the encryption type (if it is available) @@ -183,7 +200,10 @@ class Plugin(GlancesPlugin): # Add the new hotspot to the message msg = '{:{width}}'.format(hotspotname, width=ifname_max_width) ret.append(self.curse_add_line(msg)) - msg = '{:>7}'.format(i['quality'], width=ifname_max_width) - ret.append(self.curse_add_line(msg)) + msg = '{:>7}'.format(i['signal'], width=ifname_max_width) + ret.append(self.curse_add_line(msg, + self.get_views(item=i[self.get_key()], + key='signal', + option='decoration'))) return ret From 8c15914f73cd3906a59ba7dd7ae6f943d0eb61bb Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 21 Oct 2016 22:11:13 +0200 Subject: [PATCH 20/89] Update docs with dependenties (issue #937) --- README.rst | 1 + docs/aoa/wifi.rst | 2 ++ docs/install.rst | 5 +++-- setup.py | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index febce7de..e7ec368c 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,7 @@ Optional dependencies: - ``bernhard`` (for the Riemann export module) - ``py-cpuinfo`` (for the Quicklook CPU info module) - ``scandir`` (for the Folders plugin) [Only for Python < 3.5] +- ``wifi`` (for the wifi plugin) [Linux-only] *Note for Python 2.6 users* diff --git a/docs/aoa/wifi.rst b/docs/aoa/wifi.rst index 46ffdef8..69012735 100644 --- a/docs/aoa/wifi.rst +++ b/docs/aoa/wifi.rst @@ -3,6 +3,8 @@ Wifi ===== +*Availability: Linux* + .. image:: ../_static/wifi.png Glances displays the Wifi hotspots' name and signal quality. diff --git a/docs/install.rst b/docs/install.rst index a1f373c4..cc36c12b 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -22,12 +22,13 @@ features (like the Web interface, exports modules, sensors...): .. code-block:: console - pip install bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb pyzmq + pip install bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb pyzmq wifi -To upgrade Glances to the latest version: +To upgrade Glances and all its dependencies to the latests versions: .. code-block:: console + pip install --upgrade bottle requests batinfo py3sensors zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard cassandra scandir couchdb pyzmq wifi pip install --upgrade glances For additionnal installation methods, read the official `README`_ file. diff --git a/setup.py b/setup.py index 07176c07..7fb20a02 100755 --- a/setup.py +++ b/setup.py @@ -92,7 +92,8 @@ setup( 'EXPORT': ['influxdb>=1.0.0', 'elasticsearch', 'potsdb' 'statsd', 'pika', 'bernhard', 'cassandra-driver'], 'ACTION': ['pystache'], 'CPUINFO': ['py-cpuinfo'], - 'FOLDERS': ['scandir'] + 'FOLDERS': ['scandir'], + 'WIFI': ['wifi'] }, packages=['glances'], include_package_data=True, From 92b4e1adf0b881808ce881973c409b4425809682 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 21 Oct 2016 22:32:39 +0200 Subject: [PATCH 21/89] Correct an issue in the UT for the new Wifi plugin --- unitest-restful.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unitest-restful.py b/unitest-restful.py index 3744b630..8c11ed93 100755 --- a/unitest-restful.py +++ b/unitest-restful.py @@ -94,7 +94,8 @@ class TestGlances(unittest.TestCase): if p in ('uptime', 'now'): self.assertIsInstance(req.json(), text_type) elif p in ('fs', 'percpu', 'sensors', 'alert', 'processlist', 'diskio', - 'hddtemp', 'batpercent', 'network', 'folders', 'amps', 'ports', 'irq'): + 'hddtemp', 'batpercent', 'network', 'folders', 'amps', 'ports', + 'irq', 'wifi'): self.assertIsInstance(req.json(), list) elif p in ('psutilversion', 'help'): pass From c08dd576650bee9e55897dcfe386f46e1121e89c Mon Sep 17 00:00:00 2001 From: Floran Brutel Date: Sat, 22 Oct 2016 09:28:48 +0200 Subject: [PATCH 22/89] :bug: Fix loading of the raid plugin in the web UI --- .../js/services/plugins/glances_raid.js | 8 +- glances/outputs/static/public/css/style.css | 6 +- glances/outputs/static/public/js/main.js | 49 +- glances/outputs/static/public/js/vendor.js | 1781 +++++++++++++---- 4 files changed, 1494 insertions(+), 350 deletions(-) diff --git a/glances/outputs/static/js/services/plugins/glances_raid.js b/glances/outputs/static/js/services/plugins/glances_raid.js index 637009e7..900b2a5d 100644 --- a/glances/outputs/static/js/services/plugins/glances_raid.js +++ b/glances/outputs/static/js/services/plugins/glances_raid.js @@ -3,7 +3,7 @@ glancesApp.service('GlancesPluginRaid', function () { this.disks = []; this.setData = function (data, views) { - this.disks = []; + var disks = []; data = data[_pluginName]; _.forIn(data, function(diskData, diskKey) { @@ -26,8 +26,10 @@ glancesApp.service('GlancesPluginRaid', function () { }); }); - this.disks.push(disk); - }, this); + disks.push(disk); + }); + + this.disks = disks; }; this.hasDisks = function() { diff --git a/glances/outputs/static/public/css/style.css b/glances/outputs/static/public/css/style.css index b54b310f..acc7d2cf 100644 --- a/glances/outputs/static/public/css/style.css +++ b/glances/outputs/static/public/css/style.css @@ -62,11 +62,15 @@ body { } .ok, .status, .process { color: #3E7B04; - font-weight: bold; + /*font-weight: bold;*/ } .ok_log { background-color: #3E7B04; color: white; + /*font-weight: bold;*/ +} +.max { + color: #3E7B04; font-weight: bold; } .careful { diff --git a/glances/outputs/static/public/js/main.js b/glances/outputs/static/public/js/main.js index 85121641..b33108cc 100644 --- a/glances/outputs/static/public/js/main.js +++ b/glances/outputs/static/public/js/main.js @@ -177,7 +177,7 @@ glancesApp.filter('timedelta', ["$filter", function($filter) { } }]); -glancesApp.controller('statsController', ["$scope", "$rootScope", "$interval", "GlancesStats", "help", "arguments", function ($scope, $rootScope, $interval, GlancesStats, help, arguments) { +glancesApp.controller('statsController', ["$scope", "$rootScope", "$interval", "GlancesStats", "help", "arguments", "favicoService", function ($scope, $rootScope, $interval, GlancesStats, help, arguments, favicoService) { $scope.help = help; $scope.arguments = arguments; @@ -203,7 +203,7 @@ glancesApp.controller('statsController', ["$scope", "$rootScope", "$interval", " $scope.statsAlert = GlancesStats.getPlugin('alert'); $scope.statsCpu = GlancesStats.getPlugin('cpu'); $scope.statsDiskio = GlancesStats.getPlugin('diskio'); - $scope.statsIrq = GlancesStats.getPlugin('irq'); + $scope.statsIrq = GlancesStats.getPlugin('irq'); $scope.statsDocker = GlancesStats.getPlugin('docker'); $scope.statsFs = GlancesStats.getPlugin('fs'); $scope.statsFolders = GlancesStats.getPlugin('folders'); @@ -225,6 +225,12 @@ glancesApp.controller('statsController', ["$scope", "$rootScope", "$interval", " $rootScope.title = $scope.statsSystem.hostname + ' - Glances'; + if ($scope.statsAlert.hasOngoingAlerts()) { + favicoService.badge($scope.statsAlert.countOngoingAlerts()); + } else { + favicoService.reset(); + } + $scope.is_disconnected = false; $scope.dataLoaded = true; }, function() { @@ -283,8 +289,8 @@ glancesApp.controller('statsController', ["$scope", "$rootScope", "$interval", " // d => Show/hide disk I/O stats $scope.arguments.disable_diskio = !$scope.arguments.disable_diskio; break; - case $event.shiftKey && $event.keyCode == keycodes.Q: - // R => Show/hide IRQ + case $event.shiftKey && $event.keyCode == keycodes.Q: + // Q => Show/hide IRQ $scope.arguments.disable_irq = !$scope.arguments.disable_irq; break; case !$event.shiftKey && $event.keyCode == keycodes.f: @@ -405,6 +411,21 @@ var keycodes = { 'R' : '82', } +glancesApp.service('favicoService', function() { + + var favico = new Favico({ + animation : 'none' + }); + + this.badge = function(nb) { + favico.badge(nb); + }; + + this.reset = function() { + favico.reset(); + }; +}); + glancesApp.service('GlancesStats', ["$http", "$injector", "$q", "GlancesPlugin", function($http, $injector, $q, GlancesPlugin) { var _stats = [], _views = [], _limits = []; @@ -412,7 +433,7 @@ glancesApp.service('GlancesStats', ["$http", "$injector", "$q", "GlancesPlugin", 'alert': 'GlancesPluginAlert', 'cpu': 'GlancesPluginCpu', 'diskio': 'GlancesPluginDiskio', - 'irq' : 'GlancesPluginIrq', + 'irq' : 'GlancesPluginIrq', 'docker': 'GlancesPluginDocker', 'ip': 'GlancesPluginIp', 'fs': 'GlancesPluginFs', @@ -532,7 +553,7 @@ glancesApp.service('GlancesPluginAlert', function () { , minutes = parseInt((duration / (1000 * 60)) % 60) , hours = parseInt((duration / (1000 * 60 * 60)) % 24); - alert.duration = _.padLeft(hours, 2, '0') + ":" + _.padLeft(minutes, 2, '0') + ":" + _.padLeft(seconds, 2, '0'); + alert.duration = _.padStart(hours, 2, '0') + ":" + _.padStart(minutes, 2, '0') + ":" + _.padStart(seconds, 2, '0'); } _alerts.push(alert); @@ -550,6 +571,14 @@ glancesApp.service('GlancesPluginAlert', function () { this.count = function () { return _alerts.length; }; + + this.hasOngoingAlerts = function () { + return _.filter(_alerts, { 'ongoing': true }).length > 0; + }; + + this.countOngoingAlerts = function () { + return _.filter(_alerts, { 'ongoing': true }).length; + } }); glancesApp.service('GlancesPluginAmps', function() { @@ -1176,7 +1205,7 @@ glancesApp.service('GlancesPluginRaid', function () { this.disks = []; this.setData = function (data, views) { - this.disks = []; + var disks = []; data = data[_pluginName]; _.forIn(data, function(diskData, diskKey) { @@ -1199,8 +1228,10 @@ glancesApp.service('GlancesPluginRaid', function () { }); }); - this.disks.push(disk); - }, this); + disks.push(disk); + }); + + this.disks = disks; }; this.hasDisks = function() { diff --git a/glances/outputs/static/public/js/vendor.js b/glances/outputs/static/public/js/vendor.js index a5fd87ff..db4b128d 100644 --- a/glances/outputs/static/public/js/vendor.js +++ b/glances/outputs/static/public/js/vendor.js @@ -32850,17 +32850,21 @@ function ngViewFillContentFactory($compile, $controller, $route) { var undefined; /** Used as the semantic version number. */ - var VERSION = '4.15.0'; + var VERSION = '4.16.4'; /** Used as the size to enable large array optimizations. */ var LARGE_ARRAY_SIZE = 200; - /** Used as the `TypeError` message for "Functions" methods. */ - var FUNC_ERROR_TEXT = 'Expected a function'; + /** Error message constants. */ + var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://github.com/es-shims.', + FUNC_ERROR_TEXT = 'Expected a function'; /** Used to stand-in for `undefined` hash values. */ var HASH_UNDEFINED = '__lodash_hash_undefined__'; + /** Used as the maximum memoize cache size. */ + var MAX_MEMOIZE_SIZE = 500; + /** Used as the internal argument placeholder. */ var PLACEHOLDER = '__lodash_placeholder__'; @@ -32885,7 +32889,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { DEFAULT_TRUNC_OMISSION = '...'; /** Used to detect hot functions by number of calls within a span of milliseconds. */ - var HOT_COUNT = 150, + var HOT_COUNT = 500, HOT_SPAN = 16; /** Used to indicate the type of lazy iteratees. */ @@ -32929,6 +32933,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { numberTag = '[object Number]', objectTag = '[object Object]', promiseTag = '[object Promise]', + proxyTag = '[object Proxy]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', @@ -32954,8 +32959,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; /** Used to match HTML entities and HTML characters. */ - var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g, - reUnescapedHtml = /[&<>"'`]/g, + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, + reUnescapedHtml = /[&<>"']/g, reHasEscapedHtml = RegExp(reEscapedHtml.source), reHasUnescapedHtml = RegExp(reUnescapedHtml.source); @@ -33002,9 +33007,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** Used to match `RegExp` flags from their coerced string values. */ var reFlags = /\w*$/; - /** Used to detect hexadecimal string values. */ - var reHasHexPrefix = /^0x/i; - /** Used to detect bad signed hexadecimal string values. */ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; @@ -33199,7 +33201,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', '\u0132': 'IJ', '\u0133': 'ij', '\u0152': 'Oe', '\u0153': 'oe', - '\u0149': "'n", '\u017f': 'ss' + '\u0149': "'n", '\u017f': 's' }; /** Used to map characters to HTML entities. */ @@ -33208,8 +33210,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { '<': '<', '>': '>', '"': '"', - "'": ''', - '`': '`' + "'": ''' }; /** Used to map HTML entities to characters. */ @@ -33218,8 +33219,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { '<': '<', '>': '>', '"': '"', - ''': "'", - '`': '`' + ''': "'" }; /** Used to escape characters for inclusion in compiled string literals. */ @@ -33660,18 +33660,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {number} Returns the index of the matched value, else `-1`. */ function baseIndexOf(array, value, fromIndex) { - if (value !== value) { - return baseFindIndex(array, baseIsNaN, fromIndex); - } - var index = fromIndex - 1, - length = array.length; - - while (++index < length) { - if (array[index] === value) { - return index; - } - } - return -1; + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex); } /** @@ -33876,7 +33867,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } /** - * Checks if a cache value for `key` exists. + * Checks if a `cache` value for `key` exists. * * @private * @param {Object} cache The cache to query. @@ -33934,7 +33925,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { while (length--) { if (array[length] === placeholder) { - result++; + ++result; } } return result; @@ -34004,25 +33995,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { return reHasUnicodeWord.test(string); } - /** - * Checks if `value` is a host object in IE < 9. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a host object, else `false`. - */ - function isHostObject(value) { - // Many host objects are `Object` objects that can coerce to strings - // despite having improperly defined `toString` methods. - var result = false; - if (value != null && typeof value.toString != 'function') { - try { - result = !!(value + ''); - } catch (e) {} - } - return result; - } - /** * Converts `iterator` to an array. * @@ -34130,6 +34102,48 @@ function ngViewFillContentFactory($compile, $controller, $route) { return result; } + /** + * A specialized version of `_.indexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictIndexOf(array, value, fromIndex) { + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * A specialized version of `_.lastIndexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictLastIndexOf(array, value, fromIndex) { + var index = fromIndex + 1; + while (index--) { + if (array[index] === value) { + return index; + } + } + return index; + } + /** * Gets the number of symbols in `string`. * @@ -34175,7 +34189,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { function unicodeSize(string) { var result = reUnicode.lastIndex = 0; while (reUnicode.test(string)) { - result++; + ++result; } return result; } @@ -34230,17 +34244,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { * lodash.isFunction(lodash.bar); * // => true * - * // Use `context` to stub `Date#getTime` use in `_.now`. - * var stubbed = _.runInContext({ - * 'Date': function() { - * return { 'getTime': stubGetTime }; - * } - * }); - * * // Create a suped-up `defer` in Node.js. * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; */ - function runInContext(context) { + var runInContext = (function runInContext(context) { context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root; /** Built-in constructor references. */ @@ -34300,6 +34307,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { var Buffer = moduleExports ? context.Buffer : undefined, Symbol = context.Symbol, Uint8Array = context.Uint8Array, + allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined, getPrototype = overArg(Object.getPrototypeOf, Object), iteratorSymbol = Symbol ? Symbol.iterator : undefined, objectCreate = Object.create, @@ -34307,6 +34315,14 @@ function ngViewFillContentFactory($compile, $controller, $route) { splice = arrayProto.splice, spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined; + var defineProperty = (function() { + try { + var func = getNative(Object, 'defineProperty'); + func({}, '', {}); + return func; + } catch (e) {} + }()); + /** Mocked built-ins. */ var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, ctxNow = Date && Date.now !== root.Date.now && Date.now, @@ -34322,6 +34338,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { nativeKeys = overArg(Object.keys, Object), nativeMax = Math.max, nativeMin = Math.min, + nativeNow = Date.now, nativeParseInt = context.parseInt, nativeRandom = Math.random, nativeReverse = arrayProto.reverse; @@ -34334,20 +34351,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { WeakMap = getNative(context, 'WeakMap'), nativeCreate = getNative(Object, 'create'); - /* Used to set `toString` methods. */ - var defineProperty = (function() { - var func = getNative(Object, 'defineProperty'), - name = getNative.name; - - return (name && name.length > 2) ? func : undefined; - }()); - /** Used to store function metadata. */ var metaMap = WeakMap && new WeakMap; - /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */ - var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf'); - /** Used to lookup unminified function names. */ var realNames = {}; @@ -34494,6 +34500,30 @@ function ngViewFillContentFactory($compile, $controller, $route) { return new LodashWrapper(value); } + /** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} proto The object to inherit from. + * @returns {Object} Returns the new object. + */ + var baseCreate = (function() { + function object() {} + return function(proto) { + if (!isObject(proto)) { + return {}; + } + if (objectCreate) { + return objectCreate(proto); + } + object.prototype = proto; + var result = new object; + object.prototype = undefined; + return result; + }; + }()); + /** * The function whose prototype chain sequence wrappers inherit from. * @@ -34735,6 +34765,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function hashClear() { this.__data__ = nativeCreate ? nativeCreate(null) : {}; + this.size = 0; } /** @@ -34748,7 +34779,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function hashDelete(key) { - return this.has(key) && delete this.__data__[key]; + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; } /** @@ -34795,6 +34828,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function hashSet(key, value) { var data = this.__data__; + this.size += this.has(key) ? 0 : 1; data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; return this; } @@ -34835,6 +34869,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function listCacheClear() { this.__data__ = []; + this.size = 0; } /** @@ -34859,6 +34894,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } else { splice.call(data, index, 1); } + --this.size; return true; } @@ -34906,6 +34942,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { index = assocIndexOf(data, key); if (index < 0) { + ++this.size; data.push([key, value]); } else { data[index][1] = value; @@ -34948,6 +34985,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @memberOf MapCache */ function mapCacheClear() { + this.size = 0; this.__data__ = { 'hash': new Hash, 'map': new (Map || ListCache), @@ -34965,7 +35003,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function mapCacheDelete(key) { - return getMapData(this, key)['delete'](key); + var result = getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; } /** @@ -35005,7 +35045,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Object} Returns the map cache instance. */ function mapCacheSet(key, value) { - getMapData(this, key).set(key, value); + var data = getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; return this; } @@ -35078,7 +35122,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {Array} [entries] The key-value pairs to cache. */ function Stack(entries) { - this.__data__ = new ListCache(entries); + var data = this.__data__ = new ListCache(entries); + this.size = data.size; } /** @@ -35090,6 +35135,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function stackClear() { this.__data__ = new ListCache; + this.size = 0; } /** @@ -35102,7 +35148,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function stackDelete(key) { - return this.__data__['delete'](key); + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; } /** @@ -35142,16 +35192,18 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Object} Returns the stack cache instance. */ function stackSet(key, value) { - var cache = this.__data__; - if (cache instanceof ListCache) { - var pairs = cache.__data__; + var data = this.__data__; + if (data instanceof ListCache) { + var pairs = data.__data__; if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { pairs.push([key, value]); + this.size = ++data.size; return this; } - cache = this.__data__ = new MapCache(pairs); + data = this.__data__ = new MapCache(pairs); } - cache.set(key, value); + data.set(key, value); + this.size = data.size; return this; } @@ -35173,24 +35225,67 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Array} Returns the array of property names. */ function arrayLikeKeys(value, inherited) { - // Safari 8.1 makes `arguments.callee` enumerable in strict mode. - // Safari 9 makes `arguments.length` enumerable in strict mode. - var result = (isArray(value) || isArguments(value)) - ? baseTimes(value.length, String) - : []; - - var length = result.length, - skipIndexes = !!length; + var isArr = isArray(value), + isArg = !isArr && isArguments(value), + isBuff = !isArr && !isArg && isBuffer(value), + isType = !isArr && !isArg && !isBuff && isTypedArray(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? baseTimes(value.length, String) : [], + length = result.length; for (var key in value) { if ((inherited || hasOwnProperty.call(value, key)) && - !(skipIndexes && (key == 'length' || isIndex(key, length)))) { + !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + (isBuff && (key == 'offset' || key == 'parent')) || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || + // Skip index properties. + isIndex(key, length) + ))) { result.push(key); } } return result; } + /** + * A specialized version of `_.sample` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @returns {*} Returns the random element. + */ + function arraySample(array) { + var length = array.length; + return length ? array[baseRandom(0, length - 1)] : undefined; + } + + /** + * A specialized version of `_.sampleSize` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function arraySampleSize(array, n) { + return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length)); + } + + /** + * A specialized version of `_.shuffle` for arrays. + * + * @private + * @param {Array} array The array to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function arrayShuffle(array) { + return shuffleSelf(copyArray(array)); + } + /** * Used by `_.defaults` to customize its `_.assignIn` use. * @@ -35220,8 +35315,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function assignMergeValue(object, key, value) { if ((value !== undefined && !eq(object[key], value)) || - (typeof key == 'number' && value === undefined && !(key in object))) { - object[key] = value; + (value === undefined && !(key in object))) { + baseAssignValue(object, key, value); } } @@ -35239,7 +35334,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { var objValue = object[key]; if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || (value === undefined && !(key in object))) { - object[key] = value; + baseAssignValue(object, key, value); } } @@ -35292,6 +35387,28 @@ function ngViewFillContentFactory($compile, $controller, $route) { return object && copyObject(source, keys(source), object); } + /** + * The base implementation of `assignValue` and `assignMergeValue` without + * value checks. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function baseAssignValue(object, key, value) { + if (key == '__proto__' && defineProperty) { + defineProperty(object, key, { + 'configurable': true, + 'enumerable': true, + 'value': value, + 'writable': true + }); + } else { + object[key] = value; + } + } + /** * The base implementation of `_.at` without support for individual paths. * @@ -35372,9 +35489,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { return cloneBuffer(value, isDeep); } if (tag == objectTag || tag == argsTag || (isFunc && !object)) { - if (isHostObject(value)) { - return object ? value : {}; - } result = initCloneObject(isFunc ? {} : value); if (!isDeep) { return copySymbols(value, baseAssign(result, value)); @@ -35394,9 +35508,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } stack.set(value, result); - if (!isArr) { - var props = isFull ? getAllKeys(value) : keys(value); - } + var props = isArr ? undefined : (isFull ? getAllKeys : keys)(value); arrayEach(props || value, function(subValue, key) { if (props) { key = subValue; @@ -35448,18 +35560,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { return true; } - /** - * The base implementation of `_.create` without support for assigning - * properties to the created object. - * - * @private - * @param {Object} prototype The object to inherit from. - * @returns {Object} Returns the new object. - */ - function baseCreate(proto) { - return isObject(proto) ? objectCreate(proto) : {}; - } - /** * The base implementation of `_.delay` and `_.defer` which accepts `args` * to provide to `func`. @@ -35942,6 +36042,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { return func == null ? undefined : apply(func, object, args); } + /** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ + function baseIsArguments(value) { + return isObjectLike(value) && objectToString.call(value) == argsTag; + } + /** * The base implementation of `_.isArrayBuffer` without Node.js optimizations. * @@ -36018,10 +36129,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { othTag = getTag(other); othTag = othTag == argsTag ? objectTag : othTag; } - var objIsObj = objTag == objectTag && !isHostObject(object), - othIsObj = othTag == objectTag && !isHostObject(other), + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, isSameTag = objTag == othTag; + if (isSameTag && isBuffer(object)) { + if (!isBuffer(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } if (isSameTag && !objIsObj) { stack || (stack = new Stack); return (objIsArr || isTypedArray(object)) @@ -36124,7 +36242,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (!isObject(value) || isMasked(value)) { return false; } - var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor; + var pattern = isFunction(value) ? reIsNative : reIsHostCtor; return pattern.test(toSource(value)); } @@ -36311,14 +36429,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (object === source) { return; } - if (!(isArray(source) || isTypedArray(source))) { - var props = baseKeysIn(source); - } - arrayEach(props || source, function(srcValue, key) { - if (props) { - key = srcValue; - srcValue = source[key]; - } + baseFor(source, function(srcValue, key) { if (isObject(srcValue)) { stack || (stack = new Stack); baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); @@ -36333,7 +36444,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } assignMergeValue(object, key, newValue); } - }); + }, keysIn); } /** @@ -36367,29 +36478,37 @@ function ngViewFillContentFactory($compile, $controller, $route) { var isCommon = newValue === undefined; if (isCommon) { + var isArr = isArray(srcValue), + isBuff = !isArr && isBuffer(srcValue), + isTyped = !isArr && !isBuff && isTypedArray(srcValue); + newValue = srcValue; - if (isArray(srcValue) || isTypedArray(srcValue)) { + if (isArr || isBuff || isTyped) { if (isArray(objValue)) { newValue = objValue; } else if (isArrayLikeObject(objValue)) { newValue = copyArray(objValue); } - else { + else if (isBuff) { isCommon = false; - newValue = baseClone(srcValue, true); + newValue = cloneBuffer(srcValue, true); + } + else if (isTyped) { + isCommon = false; + newValue = cloneTypedArray(srcValue, true); + } + else { + newValue = []; } } else if (isPlainObject(srcValue) || isArguments(srcValue)) { + newValue = objValue; if (isArguments(objValue)) { newValue = toPlainObject(objValue); } else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) { - isCommon = false; - newValue = baseClone(srcValue, true); - } - else { - newValue = objValue; + newValue = initCloneObject(srcValue); } } else { @@ -36482,7 +36601,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { value = object[key]; if (predicate(value, key)) { - result[key] = value; + baseAssignValue(result, key, value); } } return result; @@ -36648,24 +36767,31 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Function} Returns the new function. */ function baseRest(func, start) { - start = nativeMax(start === undefined ? (func.length - 1) : start, 0); - return function() { - var args = arguments, - index = -1, - length = nativeMax(args.length - start, 0), - array = Array(length); + return setToString(overRest(func, start, identity), func + ''); + } - while (++index < length) { - array[index] = args[start + index]; - } - index = -1; - var otherArgs = Array(start + 1); - while (++index < start) { - otherArgs[index] = args[index]; - } - otherArgs[start] = array; - return apply(func, this, otherArgs); - }; + /** + * The base implementation of `_.sample`. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @returns {*} Returns the random element. + */ + function baseSample(collection) { + return arraySample(values(collection)); + } + + /** + * The base implementation of `_.sampleSize` without param guards. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function baseSampleSize(collection, n) { + var array = values(collection); + return shuffleSelf(array, baseClamp(n, 0, array.length)); } /** @@ -36709,7 +36835,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { } /** - * The base implementation of `setData` without support for hot loop detection. + * The base implementation of `setData` without support for hot loop shorting. * * @private * @param {Function} func The function to associate metadata with. @@ -36721,6 +36847,34 @@ function ngViewFillContentFactory($compile, $controller, $route) { return func; }; + /** + * The base implementation of `setToString` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var baseSetToString = !defineProperty ? identity : function(func, string) { + return defineProperty(func, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant(string), + 'writable': true + }); + }; + + /** + * The base implementation of `_.shuffle`. + * + * @private + * @param {Array|Object} collection The collection to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function baseShuffle(collection) { + return shuffleSelf(values(collection)); + } + /** * The base implementation of `_.slice` without an iteratee call guard. * @@ -36914,6 +37068,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (typeof value == 'string') { return value; } + if (isArray(value)) { + // Recursively convert values (susceptible to call stack limits). + return arrayMap(value, baseToString) + ''; + } if (isSymbol(value)) { return symbolToString ? symbolToString.call(value) : ''; } @@ -37135,6 +37293,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { return isArray(value) ? value : stringToPath(value); } + /** + * A `baseRest` alias which can be replaced with `identity` by module + * replacement plugins. + * + * @private + * @type {Function} + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + var castRest = baseRest; + /** * Casts `array` to a slice if it's needed. * @@ -37172,7 +37341,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (isDeep) { return buffer.slice(); } - var result = new buffer.constructor(buffer.length); + var length = buffer.length, + result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length); + buffer.copy(result); return result; } @@ -37449,6 +37620,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Object} Returns `object`. */ function copyObject(source, props, object, customizer) { + var isNew = !object; object || (object = {}); var index = -1, @@ -37461,7 +37633,14 @@ function ngViewFillContentFactory($compile, $controller, $route) { ? customizer(object[key], source[key], key, object, source) : undefined; - assignValue(object, key, newValue === undefined ? source[key] : newValue); + if (newValue === undefined) { + newValue = source[key]; + } + if (isNew) { + baseAssignValue(object, key, newValue); + } else { + assignValue(object, key, newValue); + } } return object; } @@ -37740,9 +37919,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Function} Returns the new flow function. */ function createFlow(fromRight) { - return baseRest(function(funcs) { - funcs = baseFlatten(funcs, 1); - + return flatRest(function(funcs) { var length = funcs.length, index = length, prereq = LodashWrapper.prototype.thru; @@ -37925,11 +38102,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {Function} Returns the new over function. */ function createOver(arrayFunc) { - return baseRest(function(iteratees) { - iteratees = (iteratees.length == 1 && isArray(iteratees[0])) - ? arrayMap(iteratees[0], baseUnary(getIteratee())) - : arrayMap(baseFlatten(iteratees, 1), baseUnary(getIteratee())); - + return flatRest(function(iteratees) { + iteratees = arrayMap(iteratees, baseUnary(getIteratee())); return baseRest(function(args) { var thisArg = this; return arrayFunc(iteratees, function(iteratee) { @@ -38272,9 +38446,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { // Recursively compare arrays (susceptible to call stack limits). if (seen) { if (!arraySome(other, function(othValue, othIndex) { - if (!seen.has(othIndex) && + if (!cacheHas(seen, othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) { - return seen.add(othIndex); + return seen.push(othIndex); } })) { result = false; @@ -38454,6 +38628,17 @@ function ngViewFillContentFactory($compile, $controller, $route) { return result; } + /** + * A specialized version of `baseRest` which flattens the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + function flatRest(func) { + return setToString(overRest(func, undefined, flatten), func + ''); + } + /** * Creates an array of own enumerable property names and symbols of `object`. * @@ -38622,8 +38807,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ var getTag = baseGetTag; - // Fallback for data views, maps, sets, and weak maps in IE 11, - // for data views in Edge < 14, and promises in Node.js. + // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || (Map && getTag(new Map) != mapTag) || (Promise && getTag(Promise.resolve()) != promiseTag) || @@ -38699,9 +38883,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { function hasPath(object, path, hasFunc) { path = isKey(path, object) ? [path] : castPath(path); - var result, - index = -1, - length = path.length; + var index = -1, + length = path.length, + result = false; while (++index < length) { var key = toKey(path[index]); @@ -38710,10 +38894,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { } object = object[key]; } - if (result) { + if (result || ++index != length) { return result; } - var length = object ? object.length : 0; + length = object ? object.length : 0; return !!length && isLength(length) && isIndex(key, length) && (isArray(object) || isArguments(object)); } @@ -38808,9 +38992,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @returns {string} Returns the modified source. */ function insertWrapDetails(source, details) { - var length = details.length, - lastIndex = length - 1; - + var length = details.length; + if (!length) { + return source; + } + var lastIndex = length - 1; details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; details = details.join(length > 2 ? ', ' : ' '); return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); @@ -38989,6 +39175,26 @@ function ngViewFillContentFactory($compile, $controller, $route) { }; } + /** + * A specialized version of `_.memoize` which clears the memoized function's + * cache when it exceeds `MAX_MEMOIZE_SIZE`. + * + * @private + * @param {Function} func The function to have its output memoized. + * @returns {Function} Returns the new memoized function. + */ + function memoizeCapped(func) { + var result = memoize(func, function(key) { + if (cache.size === MAX_MEMOIZE_SIZE) { + cache.clear(); + } + return key; + }); + + var cache = result.cache; + return result; + } + /** * Merges the function metadata of `source` into `data`. * @@ -39102,6 +39308,36 @@ function ngViewFillContentFactory($compile, $controller, $route) { return result; } + /** + * A specialized version of `baseRest` which transforms the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @param {Function} transform The rest array transform. + * @returns {Function} Returns the new function. + */ + function overRest(func, start, transform) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = transform(array); + return apply(func, this, otherArgs); + }; + } + /** * Gets the parent value at `path` of `object`. * @@ -39150,25 +39386,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {*} data The metadata. * @returns {Function} Returns `func`. */ - var setData = (function() { - var count = 0, - lastCalled = 0; - - return function(key, value) { - var stamp = now(), - remaining = HOT_SPAN - (stamp - lastCalled); - - lastCalled = stamp; - if (remaining > 0) { - if (++count >= HOT_COUNT) { - return key; - } - } else { - count = 0; - } - return baseSetData(key, value); - }; - }()); + var setData = shortOut(baseSetData); /** * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). @@ -39182,6 +39400,16 @@ function ngViewFillContentFactory($compile, $controller, $route) { return root.setTimeout(func, wait); }; + /** + * Sets the `toString` method of `func` to return `string`. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var setToString = shortOut(baseSetToString); + /** * Sets the `toString` method of `wrapper` to mimic the source of `reference` * with wrapper details in a comment at the top of the source body. @@ -39192,14 +39420,64 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {number} bitmask The bitmask flags. See `createWrap` for more details. * @returns {Function} Returns `wrapper`. */ - var setWrapToString = !defineProperty ? identity : function(wrapper, reference, bitmask) { + function setWrapToString(wrapper, reference, bitmask) { var source = (reference + ''); - return defineProperty(wrapper, 'toString', { - 'configurable': true, - 'enumerable': false, - 'value': constant(insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))) - }); - }; + return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))); + } + + /** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new shortable function. + */ + function shortOut(func) { + var count = 0, + lastCalled = 0; + + return function() { + var stamp = nativeNow(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return arguments[0]; + } + } else { + count = 0; + } + return func.apply(undefined, arguments); + }; + } + + /** + * A specialized version of `_.shuffle` which mutates and sets the size of `array`. + * + * @private + * @param {Array} array The array to shuffle. + * @param {number} [size=array.length] The size of `array`. + * @returns {Array} Returns `array`. + */ + function shuffleSelf(array, size) { + var index = -1, + length = array.length, + lastIndex = length - 1; + + size = size === undefined ? length : size; + while (++index < size) { + var rand = baseRandom(index, lastIndex), + value = array[rand]; + + array[rand] = array[index]; + array[index] = value; + } + array.length = size; + return array; + } /** * Converts `string` to a property path array. @@ -39208,7 +39486,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @param {string} string The string to convert. * @returns {Array} Returns the property path array. */ - var stringToPath = memoize(function(string) { + var stringToPath = memoizeCapped(function(string) { string = toString(string); var result = []; @@ -39387,24 +39665,25 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [1] */ function concat() { - var length = arguments.length, - args = Array(length ? length - 1 : 0), + var length = arguments.length; + if (!length) { + return []; + } + var args = Array(length - 1), array = arguments[0], index = length; while (index--) { args[index - 1] = arguments[index]; } - return length - ? arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)) - : []; + return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); } /** * Creates an array of `array` values not included in the other given arrays * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order of result values is determined by the - * order they occur in the first array. + * for equality comparisons. The order and references of result values are + * determined by the first array. * * **Note:** Unlike `_.pullAll`, this method returns a new array. * @@ -39430,8 +39709,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.difference` except that it accepts `iteratee` which * is invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. Result values are chosen from the first array. - * The iteratee is invoked with one argument: (value). + * by which they're compared. The order and references of result values are + * determined by the first array. The iteratee is invoked with one argument: + * (value). * * **Note:** Unlike `_.pullAllBy`, this method returns a new array. * @@ -39464,9 +39744,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.difference` except that it accepts `comparator` - * which is invoked to compare elements of `array` to `values`. Result values - * are chosen from the first array. The comparator is invoked with two arguments: - * (arrVal, othVal). + * which is invoked to compare elements of `array` to `values`. The order and + * references of result values are determined by the first array. The comparator + * is invoked with two arguments: (arrVal, othVal). * * **Note:** Unlike `_.pullAllWith`, this method returns a new array. * @@ -39960,8 +40240,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * Creates an array of unique values that are included in all given arrays * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order of result values is determined by the - * order they occur in the first array. + * for equality comparisons. The order and references of result values are + * determined by the first array. * * @static * @memberOf _ @@ -39984,8 +40264,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.intersection` except that it accepts `iteratee` * which is invoked for each element of each `arrays` to generate the criterion - * by which they're compared. Result values are chosen from the first array. - * The iteratee is invoked with one argument: (value). + * by which they're compared. The order and references of result values are + * determined by the first array. The iteratee is invoked with one argument: + * (value). * * @static * @memberOf _ @@ -40019,9 +40300,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.intersection` except that it accepts `comparator` - * which is invoked to compare elements of `arrays`. Result values are chosen - * from the first array. The comparator is invoked with two arguments: - * (arrVal, othVal). + * which is invoked to compare elements of `arrays`. The order and references + * of result values are determined by the first array. The comparator is + * invoked with two arguments: (arrVal, othVal). * * @static * @memberOf _ @@ -40119,21 +40400,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { var index = length; if (fromIndex !== undefined) { index = toInteger(fromIndex); - index = ( - index < 0 - ? nativeMax(length + index, 0) - : nativeMin(index, length - 1) - ) + 1; + index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1); } - if (value !== value) { - return baseFindIndex(array, baseIsNaN, index - 1, true); - } - while (index--) { - if (array[index] === value) { - return index; - } - } - return -1; + return value === value + ? strictLastIndexOf(array, value, index) + : baseFindIndex(array, baseIsNaN, index, true); } /** @@ -40295,9 +40566,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * console.log(pulled); * // => ['b', 'd'] */ - var pullAt = baseRest(function(array, indexes) { - indexes = baseFlatten(indexes, 1); - + var pullAt = flatRest(function(array, indexes) { var length = array ? array.length : 0, result = baseAt(array, indexes); @@ -40872,8 +41141,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * Creates a duplicate-free version of an array, using * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons, in which only the first occurrence of each - * element is kept. + * for equality comparisons, in which only the first occurrence of each element + * is kept. The order of result values is determined by the order they occur + * in the array. * * @static * @memberOf _ @@ -40895,7 +41165,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.uniq` except that it accepts `iteratee` which is * invoked for each element in `array` to generate the criterion by which - * uniqueness is computed. The iteratee is invoked with one argument: (value). + * uniqueness is computed. The order of result values is determined by the + * order they occur in the array. The iteratee is invoked with one argument: + * (value). * * @static * @memberOf _ @@ -40922,8 +41194,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.uniq` except that it accepts `comparator` which - * is invoked to compare elements of `array`. The comparator is invoked with - * two arguments: (arrVal, othVal). + * is invoked to compare elements of `array`. The order of result values is + * determined by the order they occur in the array.The comparator is invoked + * with two arguments: (arrVal, othVal). * * @static * @memberOf _ @@ -41065,8 +41338,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.xor` except that it accepts `iteratee` which is * invoked for each element of each `arrays` to generate the criterion by - * which by which they're compared. The iteratee is invoked with one argument: - * (value). + * which by which they're compared. The order of result values is determined + * by the order they occur in the arrays. The iteratee is invoked with one + * argument: (value). * * @static * @memberOf _ @@ -41095,8 +41369,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * This method is like `_.xor` except that it accepts `comparator` which is - * invoked to compare elements of `arrays`. The comparator is invoked with - * two arguments: (arrVal, othVal). + * invoked to compare elements of `arrays`. The order of result values is + * determined by the order they occur in the arrays. The comparator is invoked + * with two arguments: (arrVal, othVal). * * @static * @memberOf _ @@ -41313,8 +41588,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _(object).at(['a[0].b.c', 'a[1]']).value(); * // => [3, 4] */ - var wrapperAt = baseRest(function(paths) { - paths = baseFlatten(paths, 1); + var wrapperAt = flatRest(function(paths) { var length = paths.length, start = length ? paths[0] : 0, value = this.__wrapped__, @@ -41579,7 +41853,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { '3': 2, '5': 1 } */ var countBy = createAggregator(function(result, value, key) { - hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1); + if (hasOwnProperty.call(result, key)) { + ++result[key]; + } else { + baseAssignValue(result, key, 1); + } }); /** @@ -41834,7 +42112,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @see _.forEachRight * @example * - * _([1, 2]).forEach(function(value) { + * _.forEach([1, 2], function(value) { * console.log(value); * }); * // => Logs `1` then `2`. @@ -41902,7 +42180,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { if (hasOwnProperty.call(result, key)) { result[key].push(value); } else { - result[key] = [value]; + baseAssignValue(result, key, [value]); } }); @@ -42015,7 +42293,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } */ var keyBy = createAggregator(function(result, value, key) { - result[key] = value; + baseAssignValue(result, key, value); }); /** @@ -42275,10 +42553,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => 2 */ function sample(collection) { - var array = isArrayLike(collection) ? collection : values(collection), - length = array.length; - - return length > 0 ? array[baseRandom(0, length - 1)] : undefined; + var func = isArray(collection) ? arraySample : baseSample; + return func(collection); } /** @@ -42302,25 +42578,13 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [2, 3, 1] */ function sampleSize(collection, n, guard) { - var index = -1, - result = toArray(collection), - length = result.length, - lastIndex = length - 1; - if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) { n = 1; } else { - n = baseClamp(toInteger(n), 0, length); + n = toInteger(n); } - while (++index < n) { - var rand = baseRandom(index, lastIndex), - value = result[rand]; - - result[rand] = result[index]; - result[index] = value; - } - result.length = n; - return result; + var func = isArray(collection) ? arraySampleSize : baseSampleSize; + return func(collection, n); } /** @@ -42339,7 +42603,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [4, 1, 3, 2] */ function shuffle(collection) { - return sampleSize(collection, MAX_ARRAY_LENGTH); + var func = isArray(collection) ? arrayShuffle : baseShuffle; + return func(collection); } /** @@ -42444,16 +42709,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * { 'user': 'barney', 'age': 34 } * ]; * - * _.sortBy(users, function(o) { return o.user; }); + * _.sortBy(users, [function(o) { return o.user; }]); * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] * * _.sortBy(users, ['user', 'age']); * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] - * - * _.sortBy(users, 'user', function(o) { - * return Math.floor(o.age / 10); - * }); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] */ var sortBy = baseRest(function(collection, iteratees) { if (collection == null) { @@ -42968,7 +43228,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.defer(function(text) { * console.log(text); * }, 'deferred'); - * // => Logs 'deferred' after one or more milliseconds. + * // => Logs 'deferred' after one millisecond. */ var defer = baseRest(function(func, args) { return baseDelay(func, 1, args); @@ -43076,14 +43336,14 @@ function ngViewFillContentFactory($compile, $controller, $route) { return cache.get(key); } var result = func.apply(this, args); - memoized.cache = cache.set(key, result); + memoized.cache = cache.set(key, result) || cache; return result; }; memoized.cache = new (memoize.Cache || MapCache); return memoized; } - // Assign cache to `_.memoize`. + // Expose `MapCache`. memoize.Cache = MapCache; /** @@ -43175,7 +43435,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * func(10, 5); * // => [100, 10] */ - var overArgs = baseRest(function(func, transforms) { + var overArgs = castRest(function(func, transforms) { transforms = (transforms.length == 1 && isArray(transforms[0])) ? arrayMap(transforms[0], baseUnary(getIteratee())) : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee())); @@ -43289,8 +43549,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * rearged('b', 'c', 'a') * // => ['a', 'b', 'c'] */ - var rearg = baseRest(function(func, indexes) { - return createWrap(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes, 1)); + var rearg = flatRest(function(func, indexes) { + return createWrap(func, REARG_FLAG, undefined, undefined, undefined, indexes); }); /** @@ -43780,11 +44040,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.isArguments([1, 2, 3]); * // => false */ - function isArguments(value) { - // Safari 8.1 makes `arguments.callee` enumerable in strict mode. - return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && - (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); - } + var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { + return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && + !propertyIsEnumerable.call(value, 'callee'); + }; /** * Checks if `value` is classified as an `Array` object. @@ -43966,7 +44225,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => false */ function isElement(value) { - return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value); + return value != null && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value); } /** @@ -44004,16 +44263,16 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isEmpty(value) { if (isArrayLike(value) && - (isArray(value) || typeof value == 'string' || - typeof value.splice == 'function' || isBuffer(value) || isArguments(value))) { + (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || + isBuffer(value) || isTypedArray(value) || isArguments(value))) { return !value.length; } var tag = getTag(value); if (tag == mapTag || tag == setTag) { return !value.size; } - if (nonEnumShadows || isPrototype(value)) { - return !nativeKeys(value).length; + if (isPrototype(value)) { + return !baseKeys(value).length; } for (var key in value) { if (hasOwnProperty.call(value, key)) { @@ -44168,9 +44427,9 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isFunction(value) { // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 8-9 which returns 'object' for typed array and other constructors. + // in Safari 9 which returns 'object' for typed array and other constructors. var tag = isObject(value) ? objectToString.call(value) : ''; - return tag == funcTag || tag == genTag; + return tag == funcTag || tag == genTag || tag == proxyTag; } /** @@ -44261,7 +44520,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isObject(value) { var type = typeof value; - return !!value && (type == 'object' || type == 'function'); + return value != null && (type == 'object' || type == 'function'); } /** @@ -44289,7 +44548,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => false */ function isObjectLike(value) { - return !!value && typeof value == 'object'; + return value != null && typeof value == 'object'; } /** @@ -44443,7 +44702,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { */ function isNative(value) { if (isMaskable(value)) { - throw new Error('This method is not supported with core-js. Try https://github.com/es-shims.'); + throw new Error(CORE_ERROR_TEXT); } return baseIsNative(value); } @@ -44553,8 +44812,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => true */ function isPlainObject(value) { - if (!isObjectLike(value) || - objectToString.call(value) != objectTag || isHostObject(value)) { + if (!isObjectLike(value) || objectToString.call(value) != objectTag) { return false; } var proto = getPrototype(value); @@ -45059,8 +45317,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * @memberOf _ * @since 4.0.0 * @category Lang - * @param {*} value The value to process. - * @returns {string} Returns the string. + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. * @example * * _.toString(null); @@ -45111,7 +45369,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { 'a': 1, 'c': 3 } */ var assign = createAssigner(function(object, source) { - if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) { + if (isPrototype(source) || isArrayLike(source)) { copyObject(source, keys(source), object); return; } @@ -45239,9 +45497,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.at(object, ['a[0].b.c', 'a[1]']); * // => [3, 4] */ - var at = baseRest(function(object, paths) { - return baseAt(object, baseFlatten(paths, 1)); - }); + var at = flatRest(baseAt); /** * Creates an object that inherits from the `prototype` object. If a @@ -45844,7 +46100,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { iteratee = getIteratee(iteratee, 3); baseForOwn(object, function(value, key, object) { - result[iteratee(value, key, object)] = value; + baseAssignValue(result, iteratee(value, key, object), value); }); return result; } @@ -45882,7 +46138,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { iteratee = getIteratee(iteratee, 3); baseForOwn(object, function(value, key, object) { - result[key] = iteratee(value, key, object); + baseAssignValue(result, key, iteratee(value, key, object)); }); return result; } @@ -45926,7 +46182,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { * This method is like `_.merge` except that it accepts `customizer` which * is invoked to produce the merged values of the destination and source * properties. If `customizer` returns `undefined`, merging is handled by the - * method instead. The `customizer` is invoked with seven arguments: + * method instead. The `customizer` is invoked with six arguments: * (objValue, srcValue, key, object, source, stack). * * **Note:** This method mutates `object`. @@ -45976,11 +46232,11 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.omit(object, ['a', 'c']); * // => { 'b': '2' } */ - var omit = baseRest(function(object, props) { + var omit = flatRest(function(object, props) { if (object == null) { return {}; } - props = arrayMap(baseFlatten(props, 1), toKey); + props = arrayMap(props, toKey); return basePick(object, baseDifference(getAllKeysIn(object), props)); }); @@ -46025,8 +46281,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * _.pick(object, ['a', 'c']); * // => { 'a': 1, 'c': 3 } */ - var pick = baseRest(function(object, props) { - return object == null ? {} : basePick(object, arrayMap(baseFlatten(props, 1), toKey)); + var pick = flatRest(function(object, props) { + return object == null ? {} : basePick(object, arrayMap(props, toKey)); }); /** @@ -46246,22 +46502,23 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => { '1': ['a', 'c'], '2': ['b'] } */ function transform(object, iteratee, accumulator) { - var isArr = isArray(object) || isTypedArray(object); - iteratee = getIteratee(iteratee, 4); + var isArr = isArray(object), + isArrLike = isArr || isBuffer(object) || isTypedArray(object); + iteratee = getIteratee(iteratee, 4); if (accumulator == null) { - if (isArr || isObject(object)) { - var Ctor = object.constructor; - if (isArr) { - accumulator = isArray(object) ? new Ctor : []; - } else { - accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; - } - } else { + var Ctor = object && object.constructor; + if (isArrLike) { + accumulator = isArr ? new Ctor : []; + } + else if (isObject(object)) { + accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; + } + else { accumulator = {}; } } - (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) { + (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) { return iteratee(accumulator, value, index, object); }); return accumulator; @@ -46680,8 +46937,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { } /** - * Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to - * their corresponding HTML entities. + * Converts the characters "&", "<", ">", '"', and "'" in `string` to their + * corresponding HTML entities. * * **Note:** No other characters are escaped. To escape additional * characters use a third-party library like [_he_](https://mths.be/he). @@ -46692,12 +46949,6 @@ function ngViewFillContentFactory($compile, $controller, $route) { * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) * (under "semi-related fun fact") for more details. * - * Backticks are escaped because in IE < 9, they can break out of - * attribute values or HTML comments. See [#59](https://html5sec.org/#59), - * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and - * [#133](https://html5sec.org/#133) of the - * [HTML5 Security Cheatsheet](https://html5sec.org/) for more details. - * * When working with HTML you should always * [quote attribute values](http://wonko.com/post/html-escaping) to reduce * XSS vectors. @@ -46940,15 +47191,12 @@ function ngViewFillContentFactory($compile, $controller, $route) { * // => [6, 8, 10] */ function parseInt(string, radix, guard) { - // Chrome fails to trim leading whitespace characters. - // See https://bugs.chromium.org/p/v8/issues/detail?id=3109 for more details. if (guard || radix == null) { radix = 0; } else if (radix) { radix = +radix; } - string = toString(string).replace(reTrim, ''); - return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10)); + return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); } /** @@ -47187,7 +47435,8 @@ function ngViewFillContentFactory($compile, $controller, $route) { * compiled({ 'user': 'barney' }); * // => 'hello barney!' * - * // Use the ES delimiter as an alternative to the default "interpolate" delimiter. + * // Use the ES template literal delimiter as an "interpolate" delimiter. + * // Disable support by replacing the "interpolate" delimiter. * var compiled = _.template('hello ${ user }!'); * compiled({ 'user': 'pebbles' }); * // => 'hello pebbles!' @@ -47588,7 +47837,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { /** * The inverse of `_.escape`; this method converts the HTML entities - * `&`, `<`, `>`, `"`, `'`, and ``` in `string` to + * `&`, `<`, `>`, `"`, and `'` in `string` to * their corresponding characters. * * **Note:** No other HTML entities are unescaped. To unescape additional @@ -47742,10 +47991,10 @@ function ngViewFillContentFactory($compile, $controller, $route) { * jQuery(element).on('click', view.click); * // => Logs 'clicked docs' when clicked. */ - var bindAll = baseRest(function(object, methodNames) { - arrayEach(baseFlatten(methodNames, 1), function(key) { + var bindAll = flatRest(function(object, methodNames) { + arrayEach(methodNames, function(key) { key = toKey(key); - object[key] = bind(object[key], object); + baseAssignValue(object, key, bind(object[key], object)); }); return object; }); @@ -49536,7 +49785,7 @@ function ngViewFillContentFactory($compile, $controller, $route) { lodash.prototype[iteratorSymbol] = wrapperToIterator; } return lodash; - } + }); /*--------------------------------------------------------------------------*/ @@ -49569,3 +49818,861 @@ function ngViewFillContentFactory($compile, $controller, $route) { root._ = _; } }.call(this)); + +/** + * @license MIT + * @fileOverview Favico animations + * @author Miroslav Magda, http://blog.ejci.net + * @version 0.3.10 + */ + +/** + * Create new favico instance + * @param {Object} Options + * @return {Object} Favico object + * @example + * var favico = new Favico({ + * bgColor : '#d00', + * textColor : '#fff', + * fontFamily : 'sans-serif', + * fontStyle : 'bold', + * position : 'down', + * type : 'circle', + * animation : 'slide', + * dataUrl: function(url){}, + * win: top + * }); + */ +(function () { + + var Favico = (function (opt) { + 'use strict'; + opt = (opt) ? opt : {}; + var _def = { + bgColor: '#d00', + textColor: '#fff', + fontFamily: 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,... + fontStyle: 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900 + type: 'circle', + position: 'down', // down, up, left, leftup (upleft) + animation: 'slide', + elementId: false, + dataUrl: false, + win: window + }; + var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout, _doc; + + _browser = {}; + _browser.ff = typeof InstallTrigger != 'undefined'; + _browser.chrome = !!window.chrome; + _browser.opera = !!window.opera || navigator.userAgent.indexOf('Opera') >= 0; + _browser.ie = /*@cc_on!@*/false; + _browser.safari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; + _browser.supported = (_browser.chrome || _browser.ff || _browser.opera); + + var _queue = []; + _readyCb = function () { + }; + _ready = _stop = false; + /** + * Initialize favico + */ + var init = function () { + //merge initial options + _opt = merge(_def, opt); + _opt.bgColor = hexToRgb(_opt.bgColor); + _opt.textColor = hexToRgb(_opt.textColor); + _opt.position = _opt.position.toLowerCase(); + _opt.animation = (animation.types['' + _opt.animation]) ? _opt.animation : _def.animation; + + _doc = _opt.win.document; + + var isUp = _opt.position.indexOf('up') > -1; + var isLeft = _opt.position.indexOf('left') > -1; + + //transform animation + if (isUp || isLeft) { + for (var i = 0; i < animation.types['' + _opt.animation].length; i++) { + var step = animation.types['' + _opt.animation][i]; + + if (isUp) { + if (step.y < 0.6) { + step.y = step.y - 0.4; + } else { + step.y = step.y - 2 * step.y + (1 - step.w); + } + } + + if (isLeft) { + if (step.x < 0.6) { + step.x = step.x - 0.4; + } else { + step.x = step.x - 2 * step.x + (1 - step.h); + } + } + + animation.types['' + _opt.animation][i] = step; + } + } + _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type; + + _orig = link.getIcon(); + //create temp canvas + _canvas = document.createElement('canvas'); + //create temp image + _img = document.createElement('img'); + if (_orig.hasAttribute('href')) { + _img.setAttribute('crossOrigin', 'anonymous'); + //get width/height + _img.onload = function () { + _h = (_img.height > 0) ? _img.height : 32; + _w = (_img.width > 0) ? _img.width : 32; + _canvas.height = _h; + _canvas.width = _w; + _context = _canvas.getContext('2d'); + icon.ready(); + }; + _img.setAttribute('src', _orig.getAttribute('href')); + } else { + _img.onload = function () { + _h = 32; + _w = 32; + _img.height = _h; + _img.width = _w; + _canvas.height = _h; + _canvas.width = _w; + _context = _canvas.getContext('2d'); + icon.ready(); + }; + _img.setAttribute('src', ''); + } + + }; + /** + * Icon namespace + */ + var icon = {}; + /** + * Icon is ready (reset icon) and start animation (if ther is any) + */ + icon.ready = function () { + _ready = true; + icon.reset(); + _readyCb(); + }; + /** + * Reset icon to default state + */ + icon.reset = function () { + //reset + if (!_ready) { + return; + } + _queue = []; + _lastBadge = false; + _running = false; + _context.clearRect(0, 0, _w, _h); + _context.drawImage(_img, 0, 0, _w, _h); + //_stop=true; + link.setIcon(_canvas); + //webcam('stop'); + //video('stop'); + window.clearTimeout(_animTimeout); + window.clearTimeout(_drawTimeout); + }; + /** + * Start animation + */ + icon.start = function () { + if (!_ready || _running) { + return; + } + var finished = function () { + _lastBadge = _queue[0]; + _running = false; + if (_queue.length > 0) { + _queue.shift(); + icon.start(); + } else { + + } + }; + if (_queue.length > 0) { + _running = true; + var run = function () { + // apply options for this animation + ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function (a) { + if (a in _queue[0].options) { + _opt[a] = _queue[0].options[a]; + } + }); + animation.run(_queue[0].options, function () { + finished(); + }, false); + }; + if (_lastBadge) { + animation.run(_lastBadge.options, function () { + run(); + }, true); + } else { + run(); + } + } + }; + + /** + * Badge types + */ + var type = {}; + var options = function (opt) { + opt.n = ((typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n; + opt.x = _w * opt.x; + opt.y = _h * opt.y; + opt.w = _w * opt.w; + opt.h = _h * opt.h; + opt.len = ("" + opt.n).length; + return opt; + }; + /** + * Generate circle + * @param {Object} opt Badge options + */ + type.circle = function (opt) { + opt = options(opt); + var more = false; + if (opt.len === 2) { + opt.x = opt.x - opt.w * 0.4; + opt.w = opt.w * 1.4; + more = true; + } else if (opt.len >= 3) { + opt.x = opt.x - opt.w * 0.65; + opt.w = opt.w * 1.65; + more = true; + } + _context.clearRect(0, 0, _w, _h); + _context.drawImage(_img, 0, 0, _w, _h); + _context.beginPath(); + _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.85 : 1)) + "px " + _opt.fontFamily; + _context.textAlign = 'center'; + if (more) { + _context.moveTo(opt.x + opt.w / 2, opt.y); + _context.lineTo(opt.x + opt.w - opt.h / 2, opt.y); + _context.quadraticCurveTo(opt.x + opt.w, opt.y, opt.x + opt.w, opt.y + opt.h / 2); + _context.lineTo(opt.x + opt.w, opt.y + opt.h - opt.h / 2); + _context.quadraticCurveTo(opt.x + opt.w, opt.y + opt.h, opt.x + opt.w - opt.h / 2, opt.y + opt.h); + _context.lineTo(opt.x + opt.h / 2, opt.y + opt.h); + _context.quadraticCurveTo(opt.x, opt.y + opt.h, opt.x, opt.y + opt.h - opt.h / 2); + _context.lineTo(opt.x, opt.y + opt.h / 2); + _context.quadraticCurveTo(opt.x, opt.y, opt.x + opt.h / 2, opt.y); + } else { + _context.arc(opt.x + opt.w / 2, opt.y + opt.h / 2, opt.h / 2, 0, 2 * Math.PI); + } + _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; + _context.fill(); + _context.closePath(); + _context.beginPath(); + _context.stroke(); + _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; + //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + if ((typeof opt.n) === 'number' && opt.n > 999) { + _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); + } else { + _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + } + _context.closePath(); + }; + /** + * Generate rectangle + * @param {Object} opt Badge options + */ + type.rectangle = function (opt) { + opt = options(opt); + var more = false; + if (opt.len === 2) { + opt.x = opt.x - opt.w * 0.4; + opt.w = opt.w * 1.4; + more = true; + } else if (opt.len >= 3) { + opt.x = opt.x - opt.w * 0.65; + opt.w = opt.w * 1.65; + more = true; + } + _context.clearRect(0, 0, _w, _h); + _context.drawImage(_img, 0, 0, _w, _h); + _context.beginPath(); + _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.9 : 1)) + "px " + _opt.fontFamily; + _context.textAlign = 'center'; + _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; + _context.fillRect(opt.x, opt.y, opt.w, opt.h); + _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; + //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + if ((typeof opt.n) === 'number' && opt.n > 999) { + _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); + } else { + _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); + } + _context.closePath(); + }; + + /** + * Set badge + */ + var badge = function (number, opts) { + opts = ((typeof opts) === 'string' ? { + animation: opts + } : opts) || {}; + _readyCb = function () { + try { + if (typeof (number) === 'number' ? (number > 0) : (number !== '')) { + var q = { + type: 'badge', + options: { + n: number + } + }; + if ('animation' in opts && animation.types['' + opts.animation]) { + q.options.animation = '' + opts.animation; + } + if ('type' in opts && type['' + opts.type]) { + q.options.type = '' + opts.type; + } + ['bgColor', 'textColor'].forEach(function (o) { + if (o in opts) { + q.options[o] = hexToRgb(opts[o]); + } + }); + ['fontStyle', 'fontFamily'].forEach(function (o) { + if (o in opts) { + q.options[o] = opts[o]; + } + }); + _queue.push(q); + if (_queue.length > 100) { + throw new Error('Too many badges requests in queue.'); + } + icon.start(); + } else { + icon.reset(); + } + } catch (e) { + throw new Error('Error setting badge. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + }; + + /** + * Set image as icon + */ + var image = function (imageElement) { + _readyCb = function () { + try { + var w = imageElement.width; + var h = imageElement.height; + var newImg = document.createElement('img'); + var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); + newImg.setAttribute('crossOrigin', 'anonymous'); + newImg.onload=function(){ + _context.clearRect(0, 0, _w, _h); + _context.drawImage(newImg, 0, 0, _w, _h); + link.setIcon(_canvas); + }; + newImg.setAttribute('src', imageElement.getAttribute('src')); + newImg.height = (h / ratio); + newImg.width = (w / ratio); + } catch (e) { + throw new Error('Error setting image. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + }; + /** + * Set video as icon + */ + var video = function (videoElement) { + _readyCb = function () { + try { + if (videoElement === 'stop') { + _stop = true; + icon.reset(); + _stop = false; + return; + } + //var w = videoElement.width; + //var h = videoElement.height; + //var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); + videoElement.addEventListener('play', function () { + drawVideo(this); + }, false); + + } catch (e) { + throw new Error('Error setting video. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + }; + /** + * Set video as icon + */ + var webcam = function (action) { + //UR + if (!window.URL || !window.URL.createObjectURL) { + window.URL = window.URL || {}; + window.URL.createObjectURL = function (obj) { + return obj; + }; + } + if (_browser.supported) { + var newVideo = false; + navigator.getUserMedia = navigator.getUserMedia || navigator.oGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia; + _readyCb = function () { + try { + if (action === 'stop') { + _stop = true; + icon.reset(); + _stop = false; + return; + } + newVideo = document.createElement('video'); + newVideo.width = _w; + newVideo.height = _h; + navigator.getUserMedia({ + video: true, + audio: false + }, function (stream) { + newVideo.src = URL.createObjectURL(stream); + newVideo.play(); + drawVideo(newVideo); + }, function () { + }); + } catch (e) { + throw new Error('Error setting webcam. Message: ' + e.message); + } + }; + if (_ready) { + _readyCb(); + } + } + + }; + + /** + * Draw video to context and repeat :) + */ + function drawVideo(video) { + if (video.paused || video.ended || _stop) { + return false; + } + //nasty hack for FF webcam (Thanks to Julian Ćwirko, kontakt@redsunmedia.pl) + try { + _context.clearRect(0, 0, _w, _h); + _context.drawImage(video, 0, 0, _w, _h); + } catch (e) { + + } + _drawTimeout = setTimeout(function () { + drawVideo(video); + }, animation.duration); + link.setIcon(_canvas); + } + + var link = {}; + /** + * Get icon from HEAD tag or create a new element + */ + link.getIcon = function () { + var elm = false; + //get link element + var getLink = function () { + var link = _doc.getElementsByTagName('head')[0].getElementsByTagName('link'); + for (var l = link.length, i = (l - 1); i >= 0; i--) { + if ((/(^|\s)icon(\s|$)/i).test(link[i].getAttribute('rel'))) { + return link[i]; + } + } + return false; + }; + if (_opt.element) { + elm = _opt.element; + } else if (_opt.elementId) { + //if img element identified by elementId + elm = _doc.getElementById(_opt.elementId); + elm.setAttribute('href', elm.getAttribute('src')); + } else { + //if link element + elm = getLink(); + if (elm === false) { + elm = _doc.createElement('link'); + elm.setAttribute('rel', 'icon'); + _doc.getElementsByTagName('head')[0].appendChild(elm); + } + } + elm.setAttribute('type', 'image/png'); + return elm; + }; + link.setIcon = function (canvas) { + var url = canvas.toDataURL('image/png'); + if (_opt.dataUrl) { + //if using custom exporter + _opt.dataUrl(url); + } + if (_opt.element) { + _opt.element.setAttribute('href', url); + _opt.element.setAttribute('src', url); + } else if (_opt.elementId) { + //if is attached to element (image) + var elm = _doc.getElementById(_opt.elementId); + elm.setAttribute('href', url); + elm.setAttribute('src', url); + } else { + //if is attached to fav icon + if (_browser.ff || _browser.opera) { + //for FF we need to "recreate" element, atach to dom and remove old + //var originalType = _orig.getAttribute('rel'); + var old = _orig; + _orig = _doc.createElement('link'); + //_orig.setAttribute('rel', originalType); + if (_browser.opera) { + _orig.setAttribute('rel', 'icon'); + } + _orig.setAttribute('rel', 'icon'); + _orig.setAttribute('type', 'image/png'); + _doc.getElementsByTagName('head')[0].appendChild(_orig); + _orig.setAttribute('href', url); + if (old.parentNode) { + old.parentNode.removeChild(old); + } + } else { + _orig.setAttribute('href', url); + } + } + }; + + //http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb#answer-5624139 + //HEX to RGB convertor + function hexToRgb(hex) { + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : false; + } + + /** + * Merge options + */ + function merge(def, opt) { + var mergedOpt = {}; + var attrname; + for (attrname in def) { + mergedOpt[attrname] = def[attrname]; + } + for (attrname in opt) { + mergedOpt[attrname] = opt[attrname]; + } + return mergedOpt; + } + + /** + * Cross-browser page visibility shim + * http://stackoverflow.com/questions/12536562/detect-whether-a-window-is-visible + */ + function isPageHidden() { + return _doc.hidden || _doc.msHidden || _doc.webkitHidden || _doc.mozHidden; + } + + /** + * @namespace animation + */ + var animation = {}; + /** + * Animation "frame" duration + */ + animation.duration = 40; + /** + * Animation types (none,fade,pop,slide) + */ + animation.types = {}; + animation.types.fade = [{ + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.0 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.1 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.2 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.3 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.4 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.5 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.6 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.7 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.8 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 0.9 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1.0 + }]; + animation.types.none = [{ + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + animation.types.pop = [{ + x: 1, + y: 1, + w: 0, + h: 0, + o: 1 + }, { + x: 0.9, + y: 0.9, + w: 0.1, + h: 0.1, + o: 1 + }, { + x: 0.8, + y: 0.8, + w: 0.2, + h: 0.2, + o: 1 + }, { + x: 0.7, + y: 0.7, + w: 0.3, + h: 0.3, + o: 1 + }, { + x: 0.6, + y: 0.6, + w: 0.4, + h: 0.4, + o: 1 + }, { + x: 0.5, + y: 0.5, + w: 0.5, + h: 0.5, + o: 1 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + animation.types.popFade = [{ + x: 0.75, + y: 0.75, + w: 0, + h: 0, + o: 0 + }, { + x: 0.65, + y: 0.65, + w: 0.1, + h: 0.1, + o: 0.2 + }, { + x: 0.6, + y: 0.6, + w: 0.2, + h: 0.2, + o: 0.4 + }, { + x: 0.55, + y: 0.55, + w: 0.3, + h: 0.3, + o: 0.6 + }, { + x: 0.50, + y: 0.50, + w: 0.4, + h: 0.4, + o: 0.8 + }, { + x: 0.45, + y: 0.45, + w: 0.5, + h: 0.5, + o: 0.9 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + animation.types.slide = [{ + x: 0.4, + y: 1, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.9, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.9, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.8, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.7, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.6, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.5, + w: 0.6, + h: 0.6, + o: 1 + }, { + x: 0.4, + y: 0.4, + w: 0.6, + h: 0.6, + o: 1 + }]; + /** + * Run animation + * @param {Object} opt Animation options + * @param {Object} cb Callabak after all steps are done + * @param {Object} revert Reverse order? true|false + * @param {Object} step Optional step number (frame bumber) + */ + animation.run = function (opt, cb, revert, step) { + var animationType = animation.types[isPageHidden() ? 'none' : _opt.animation]; + if (revert === true) { + step = (typeof step !== 'undefined') ? step : animationType.length - 1; + } else { + step = (typeof step !== 'undefined') ? step : 0; + } + cb = (cb) ? cb : function () { + }; + if ((step < animationType.length) && (step >= 0)) { + type[_opt.type](merge(opt, animationType[step])); + _animTimeout = setTimeout(function () { + if (revert) { + step = step - 1; + } else { + step = step + 1; + } + animation.run(opt, cb, revert, step); + }, animation.duration); + + link.setIcon(_canvas); + } else { + cb(); + return; + } + }; + //auto init + init(); + return { + badge: badge, + video: video, + image: image, + webcam: webcam, + reset: icon.reset, + browser: { + supported: _browser.supported + } + }; + }); + + // AMD / RequireJS + if (typeof define !== 'undefined' && define.amd) { + define([], function () { + return Favico; + }); + } + // CommonJS + else if (typeof module !== 'undefined' && module.exports) { + module.exports = Favico; + } + // included directly via - - + + + diff --git a/glances/outputs/static/package.json b/glances/outputs/static/package.json index 2c4ce437..b5247938 100644 --- a/glances/outputs/static/package.json +++ b/glances/outputs/static/package.json @@ -8,6 +8,7 @@ "gulp-angular-templatecache": "^2.0.0", "gulp-concat": "^2.6.0", "gulp-ng-annotate": "^2.0.0", + "gulp-rename": "^1.2.2", "main-bower-files": "^2.13.1" }, "scripts": { diff --git a/glances/outputs/static/public/css/normalize.css b/glances/outputs/static/public/css/normalize.min.css similarity index 100% rename from glances/outputs/static/public/css/normalize.css rename to glances/outputs/static/public/css/normalize.min.css diff --git a/glances/outputs/static/public/css/style.css b/glances/outputs/static/public/css/style.min.css similarity index 100% rename from glances/outputs/static/public/css/style.css rename to glances/outputs/static/public/css/style.min.css diff --git a/glances/outputs/static/public/index.html b/glances/outputs/static/public/index.html index d6c8e55e643f7e098787d2ec732e6ce20defab55..eec4fb704b577775328f65131f70df95f2533407 100644 GIT binary patch delta 57 zcmZ3@wx4Z72jk@PjE<9!G6pi|X68*6WwHU&0ZjH#+KRU%HMbxqu_U!v52WHEQxpI~ C`xFWQ delta 30 mcmdnbwwi522jk=(#=gmNOxBa Date: Sun, 23 Oct 2016 10:54:03 +0200 Subject: [PATCH 27/89] Update CPU docs --- docs/aoa/cpu.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/aoa/cpu.rst b/docs/aoa/cpu.rst index 9af80ff8..c8acde26 100644 --- a/docs/aoa/cpu.rst +++ b/docs/aoa/cpu.rst @@ -16,13 +16,21 @@ displayed. CPU stats description: * user: percent time spent in user space +> User CPU time is time spent on the processor running your program's code (or code in libraries) * system: percent time spent in kernel space +> System CPU time is the time spent running code in the operating system kernel * idle: percent of CPU used by any program +> Every program or task that runs on a computer system occupies a certain amount of processing time on the CPU. If the CPU has completed all tasks it is idle. * nice: percent time occupied by user level processes with a positive nice value +> The time the CPU has spent running users' processes that have been "niced" * irq: percent time spent servicing/handling hardware/software interrupts +> Time servicing interrupts (hardware + software) * iowait: percent time spent in wait (on disk) -* steal: percent time in involuntary wait by virtual cpu while hypervisor is servicing another processor/virtual machine +> Time spent by the CPU waiting for a IO operations to complete +* steal: percent time in involuntary wait by virtual CPU +> Steal time is the percentage of time a virtual CPU waits for a real CPU while the hypervisor is servicing another virtual processor * ctx_sw: number of context switches (voluntary + involuntary) per second +> A context switch is a procedure that a computer's CPU (central processing unit) follows to change from one task (or process) to another while ensuring that the tasks do not conflict * inter: number of interrupts per second * sw_inter: number of software interrupts per second. Always set to 0 on Windows and SunOS. * syscal: number of system calls per second. Do not displayed on Linux (always 0). From 4458ac97ea958490ffa2e03e93b814fb1c30910e Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 23 Oct 2016 12:29:57 +0200 Subject: [PATCH 28/89] Export uptime to CSV (issue #890) --- glances/exports/glances_csv.py | 9 ++++++++- glances/exports/glances_export.py | 3 ++- glances/plugins/glances_uptime.py | 12 ++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/glances/exports/glances_csv.py b/glances/exports/glances_csv.py index 833c6da6..16f6b5c0 100644 --- a/glances/exports/glances_csv.py +++ b/glances/exports/glances_csv.py @@ -68,7 +68,8 @@ class Export(GlancesExport): plugins = stats.getAllPlugins() # Init data with timestamp (issue#708) - csv_header = ['timestamp'] + if self.first_line: + csv_header = ['timestamp'] csv_data = [time.strftime('%Y-%m-%d %H:%M:%S')] # Loop over available plugin @@ -90,6 +91,12 @@ class Export(GlancesExport): for fieldname in fieldnames) # Others lines: stats csv_data += itervalues(all_stats[i]) + elif isinstance(all_stats[i], (int, float, str)): + # First line: header + if self.first_line: + csv_header.append(str(plugin)) + # Others lines: stats + csv_data.append(str(all_stats[i])) # Export to CSV if self.first_line: diff --git a/glances/exports/glances_export.py b/glances/exports/glances_export.py index a0472810..96994395 100644 --- a/glances/exports/glances_export.py +++ b/glances/exports/glances_export.py @@ -64,7 +64,8 @@ class GlancesExport(object): 'system', 'uptime', 'sensors', - 'docker'] + 'docker', + 'uptime'] def get_item_key(self, item): """Return the value of the item 'key'.""" diff --git a/glances/plugins/glances_uptime.py b/glances/plugins/glances_uptime.py index 25217e87..f557a0e8 100644 --- a/glances/plugins/glances_uptime.py +++ b/glances/plugins/glances_uptime.py @@ -47,12 +47,20 @@ class Plugin(GlancesPlugin): self.align = 'right' # Init the stats + self.uptime = datetime.now() self.reset() def reset(self): """Reset/init the stats.""" self.stats = {} + def get_export(self): + """Overwrite the default export method. + + Export uptime in seconds. + """ + return self.uptime.seconds + @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator def update(self): @@ -62,10 +70,10 @@ class Plugin(GlancesPlugin): if self.input_method == 'local': # Update stats using the standard system lib - uptime = datetime.now() - datetime.fromtimestamp(psutil.boot_time()) + self.uptime = datetime.now() - datetime.fromtimestamp(psutil.boot_time()) # Convert uptime to string (because datetime is not JSONifi) - self.stats = str(uptime).split('.')[0] + self.stats = str(self.uptime).split('.')[0] elif self.input_method == 'snmp': # Update stats using SNMP uptime = self.get_stats_snmp(snmp_oid=snmp_oid)['_uptime'] From f77553c4588f964788b44a69ffd7fd0e08113d8f Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 23 Oct 2016 12:49:47 +0200 Subject: [PATCH 29/89] System uptime in export #890 --- NEWS | 1 + glances/exports/glances_csv.py | 6 ------ glances/plugins/glances_uptime.py | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index be71b9f2..d79fbeeb 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,7 @@ Enhancements and news features: * Glances API returns the processes PPID (issue #926) * Configure server cached time from the command line --cached-time (issue #901) * Make the log logger configurable (issue #900) + * System uptime in export (issue #890) Bugs corrected: diff --git a/glances/exports/glances_csv.py b/glances/exports/glances_csv.py index 16f6b5c0..b854e51b 100644 --- a/glances/exports/glances_csv.py +++ b/glances/exports/glances_csv.py @@ -91,12 +91,6 @@ class Export(GlancesExport): for fieldname in fieldnames) # Others lines: stats csv_data += itervalues(all_stats[i]) - elif isinstance(all_stats[i], (int, float, str)): - # First line: header - if self.first_line: - csv_header.append(str(plugin)) - # Others lines: stats - csv_data.append(str(all_stats[i])) # Export to CSV if self.first_line: diff --git a/glances/plugins/glances_uptime.py b/glances/plugins/glances_uptime.py index f557a0e8..89f84bea 100644 --- a/glances/plugins/glances_uptime.py +++ b/glances/plugins/glances_uptime.py @@ -59,7 +59,7 @@ class Plugin(GlancesPlugin): Export uptime in seconds. """ - return self.uptime.seconds + return {'seconds': self.uptime.seconds} @GlancesPlugin._check_decorator @GlancesPlugin._log_result_decorator From 708bdc48eaebc73496ca800d23ebc27127f10085 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 23 Oct 2016 21:16:17 +0200 Subject: [PATCH 30/89] Add default interface speed and automatic rate thresolds #718 --- NEWS | 1 + conf/glances.conf | 11 +++++++++- docs/aoa/network.rst | 35 +++++++++++++++++++++++++----- glances/config.py | 10 +++++++++ glances/plugins/glances_network.py | 24 ++++++++++++++++---- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index d79fbeeb..2dc96899 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ Enhancements and news features: * Add ZeroMQ exporter (issue #939) * Add CouchDB exporter (issue #928) * Add hotspot Wifi informations (issue #937) + * Add default interface speed and automatic rate thresolds (issue #718) * Highlight max stats in the processes list (issue #878) * Docker alerts and actions (issue #875) * Glances API returns the processes PPID (issue #926) diff --git a/conf/glances.conf b/conf/glances.conf index 57b048f5..990da6ec 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -96,11 +96,20 @@ careful=50 warning=70 critical=90 -#[network] +[network] +# Default bitrate thresholds in % of the network interface speed +# Default values if not defined: 70/80/90 +rx_careful=70 +rx_warning=80 +rx_critical=90 +tx_careful=70 +tx_warning=80 +tx_critical=90 # Define the list of hidden network interfaces (comma-separated regexp) #hide=docker.*,lo # WLAN 0 alias #wlan0_alias=Wireless IF +# It is possible to overwrite the bitrate thresholds per interface # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate #wlan0_rx_careful=4000000 #wlan0_rx_warning=5000000 diff --git a/docs/aoa/network.rst b/docs/aoa/network.rst index 09c25792..c27aba50 100644 --- a/docs/aoa/network.rst +++ b/docs/aoa/network.rst @@ -8,16 +8,20 @@ Network Glances displays the network interface bit rate. The unit is adapted dynamically (bit/s, kbit/s, Mbit/s, etc). -Alerts are only set if the maximum speed per network interface is -available (see sample in the configuration file). +If the interface speed is detected (not on all systems), the defaults +thresholds are applied (70% for careful, 80% warning and 90% critical). +It is possible to define this percents thresholds form the configuration +file. It is also possible to define per interface bit rate thresholds. +In this case thresholds values are define in bps. -It's also possible to define: +Additionally, you can define: - a list of network interfaces to hide - per-interface limit values - aliases for interface name -in the ``[network]`` section of the configuration file. +The configuration should be done in the ``[network]`` section of the +Glances configuration file. For example, if you want to hide the loopback interface (lo) and all the virtual docker interface (docker0, docker1, ...): @@ -25,4 +29,25 @@ virtual docker interface (docker0, docker1, ...): .. code-block:: ini [network] - hide=lo,docker.* + # Default bitrate thresholds in % of the network interface speed + # Default values if not defined: 70/80/90 + rx_careful=70 + rx_warning=80 + rx_critical=90 + tx_careful=70 + tx_warning=80 + tx_critical=90 + # Define the list of hidden network interfaces (comma-separated regexp) + hide=docker.*,lo + # WLAN 0 alias + wlan0_alias=Wireless IF + # It is possible to overwrite the bitrate thresholds per interface + # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate + wlan0_rx_careful=4000000 + wlan0_rx_warning=5000000 + wlan0_rx_critical=6000000 + wlan0_rx_log=True + wlan0_tx_careful=700000 + wlan0_tx_warning=900000 + wlan0_tx_critical=1000000 + wlan0_tx_log=True diff --git a/glances/config.py b/glances/config.py index 309e257d..77375ad0 100644 --- a/glances/config.py +++ b/glances/config.py @@ -172,6 +172,16 @@ class Config(object): self.set_default('memswap', 'warning', '70') self.set_default('memswap', 'critical', '90') + # NETWORK + if not self.parser.has_section('network'): + self.parser.add_section('network') + self.set_default('network', 'rx_careful', '70') + self.set_default('network', 'rx_warning', '80') + self.set_default('network', 'rx_critical', '90') + self.set_default('network', 'tx_careful', '70') + self.set_default('network', 'tx_warning', '80') + self.set_default('network', 'tx_critical', '90') + # FS if not self.parser.has_section('fs'): self.parser.add_section('fs') diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index e2393f21..7b8c5e1d 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -22,6 +22,7 @@ import base64 import operator +from glances.logger import logger from glances.timer import getTimeSinceLastUpdate from glances.plugins.glances_plugin import GlancesPlugin @@ -240,10 +241,25 @@ class Plugin(GlancesPlugin): # Alert for i in self.stats: ifrealname = i['interface_name'].split(':')[0] - self.views[i[self.get_key()]]['rx']['decoration'] = self.get_alert(int(i['rx'] // i['time_since_update'] * 8), - header=ifrealname + '_rx') - self.views[i[self.get_key()]]['tx']['decoration'] = self.get_alert(int(i['tx'] // i['time_since_update'] * 8), - header=ifrealname + '_tx') + # Convert rate in bps ( to be able to compare to interface speed) + bps_rx = int(i['rx'] // i['time_since_update'] * 8) + bps_tx = int(i['tx'] // i['time_since_update'] * 8) + # Decorate the bitrate with the configuration file thresolds + alert_rx = self.get_alert(bps_rx, header=ifrealname + '_rx') + alert_tx = self.get_alert(bps_tx, header=ifrealname + '_tx') + # If nothing is define in the configuration file... + # ... then use the interface speed (not available on all systems) + if alert_rx == 'DEFAULT' and 'speed' in i and i['speed'] != 0: + alert_rx = self.get_alert(current=bps_rx, + maximum=i['speed'], + header='rx') + if alert_tx == 'DEFAULT' and 'speed' in i and i['speed'] != 0: + alert_tx = self.get_alert(current=bps_tx, + maximum=i['speed'], + header='tx') + # then decorates + self.views[i[self.get_key()]]['rx']['decoration'] = alert_rx + self.views[i[self.get_key()]]['tx']['decoration'] = alert_tx def msg_curse(self, args=None, max_width=None): """Return the dict to display in the curse interface.""" From b95252846d34b4b197d666e948a9dca8710bbef8 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 23 Oct 2016 21:39:53 +0200 Subject: [PATCH 31/89] Update unitest with views --- unitest.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/unitest.py b/unitest.py index dc7b02da..b190674f 100755 --- a/unitest.py +++ b/unitest.py @@ -82,7 +82,7 @@ class TestGlances(unittest.TestCase): for plugin in plugins_to_check: self.assertTrue(plugin in plugins_list) - def test_002_cpu(self): + def test_002_system(self): """Check SYSTEM plugin.""" stats_to_check = ['hostname', 'os_name'] print('INFO: [TEST_002] Check SYSTEM stats: %s' % ', '.join(stats_to_check)) @@ -201,6 +201,16 @@ class TestGlances(unittest.TestCase): self.assertTrue(type(stats_grab) is list, msg='IRQ stats is not a list') print('INFO: IRQ stats: %s' % stats_grab) + def test_096_views(self): + """Test get_views method""" + print('INFO: [TEST_096] Test views') + plugins_list = stats.getAllPlugins() + for plugin in plugins_list: + stats_grab = stats.get_plugin(plugin).get_raw() + views_grab = stats.get_plugin(plugin).get_views() + self.assertTrue(type(views_grab) is dict, + msg='{} view is not a dict'.format(plugin)) + def test_097_attribute(self): """Test GlancesAttribute classe""" print('INFO: [TEST_097] Test attribute') From 54f0e1d33fbe0627c10fc5e924322653961a10d6 Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Tue, 25 Oct 2016 14:02:03 +0200 Subject: [PATCH 32/89] Change initialisation step for alert (do not change anythink) --- glances/logs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glances/logs.py b/glances/logs.py index 4c81aeab..0e3d952e 100644 --- a/glances/logs.py +++ b/glances/logs.py @@ -147,7 +147,7 @@ class GlancesLogs(object): 1, # COUNT [], # TOP 3 PROCESS LIST proc_desc, # MONITORED PROCESSES DESC - 'cpu_percent'] # TOP PROCESS SORTKEY + glances_processes.sort_key] # TOP PROCESS SORTKEY # Add the item to the list self.logs_list.insert(0, item) From ae539ddea49be0db1c898038b5f4d99cdbabdf2d Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 26 Oct 2016 20:34:14 +0200 Subject: [PATCH 33/89] Latest DockerHub Version and latest pip Version doesn't match #944 --- Dockerfile => docker/devel/Dockerfile | 0 docker/master/Dockerfile | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+) rename Dockerfile => docker/devel/Dockerfile (100%) create mode 100644 docker/master/Dockerfile diff --git a/Dockerfile b/docker/devel/Dockerfile similarity index 100% rename from Dockerfile rename to docker/devel/Dockerfile diff --git a/docker/master/Dockerfile b/docker/master/Dockerfile new file mode 100644 index 00000000..c907b327 --- /dev/null +++ b/docker/master/Dockerfile @@ -0,0 +1,25 @@ +# +# Glances Dockerfile +# +# https://github.com/nicolargo/glances +# + +# Pull base image. +FROM ubuntu + +# Install Glances (develop branch) +RUN apt-get update && apt-get -y install curl && rm -rf /var/lib/apt/lists/* +RUN curl -L https://raw.githubusercontent.com/nicolargo/glancesautoinstall/master/install.sh | /bin/bash && rm -rf /var/lib/apt/lists/* + + +# Define working directory. +WORKDIR /glances + +# EXPOSE PORT (For XMLRPC) +EXPOSE 61209 + +# EXPOSE PORT (For Web UI) +EXPOSE 61208 + +# Define default command. +CMD python -m glances -C /glances/conf/glances.conf $GLANCES_OPT From 4877be45ea60e9bfa5b7820bf6f61e3d53ed0fd9 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 26 Oct 2016 22:22:03 +0200 Subject: [PATCH 34/89] Deprecated platform method in Python 3.7 #945 --- NEWS | 3 ++- glances/plugins/glances_system.py | 28 ++++++++++++++++++++++------ requirements.txt | 3 ++- setup.py | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 2dc96899..93d563bc 100644 --- a/NEWS +++ b/NEWS @@ -10,13 +10,14 @@ Enhancements and news features: * Add ZeroMQ exporter (issue #939) * Add CouchDB exporter (issue #928) * Add hotspot Wifi informations (issue #937) - * Add default interface speed and automatic rate thresolds (issue #718) + * Add default interface speed and automatic rate thresolds (issue #718) * Highlight max stats in the processes list (issue #878) * Docker alerts and actions (issue #875) * Glances API returns the processes PPID (issue #926) * Configure server cached time from the command line --cached-time (issue #901) * Make the log logger configurable (issue #900) * System uptime in export (issue #890) + * Deprecated platform method in Python 3.7 (issue #945) Bugs corrected: diff --git a/glances/plugins/glances_system.py b/glances/plugins/glances_system.py index 684851cb..a2eec6ab 100644 --- a/glances/plugins/glances_system.py +++ b/glances/plugins/glances_system.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# Copyright (C) 2015 Nicolargo +# Copyright (C) 2016 Nicolargo # # Glances is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by @@ -23,7 +23,14 @@ import os import platform import re from io import open +try: + import distro +except ImportError: + distro_tag = False +else: + distro_tag = True +from glances.logger import logger from glances.compat import iteritems from glances.plugins.glances_plugin import GlancesPlugin @@ -38,7 +45,8 @@ snmp_oid = {'default': {'hostname': '1.3.6.1.2.1.1.5.0', # Dict (key: OS short name) of dict (reg exp OID to human) # Windows: # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx -snmp_to_human = {'windows': {'Windows Version 6.3': 'Windows 8.1 or Server 2012R2', +snmp_to_human = {'windows': {'Windows Version 10.0': 'Windows 10 or Server 2016', + 'Windows Version 6.3': 'Windows 8.1 or Server 2012R2', 'Windows Version 6.2': 'Windows 8 or Server 2012', 'Windows Version 6.1': 'Windows 7 or Server 2008R2', 'Windows Version 6.0': 'Windows Vista or Server 2008', @@ -112,11 +120,19 @@ class Plugin(GlancesPlugin): self.stats['hostname'] = platform.node() self.stats['platform'] = platform.architecture()[0] if self.stats['os_name'] == "Linux": - linux_distro = platform.linux_distribution() - if linux_distro[0] == '': - self.stats['linux_distro'] = _linux_os_release() - else: + if distro_tag: + # Use the distro external lib + # Why ? + # Because platform.linux_distribution is predicated in Python 3.7 + linux_distro = distro.linux_distribution() self.stats['linux_distro'] = ' '.join(linux_distro[:2]) + else: + try: + # For Python < 3.7 + linux_distro = platform.linux_distribution() + self.stats['linux_distro'] = ' '.join(linux_distro[:2]) + except AttributeError: + self.stats['linux_distro'] = _linux_os_release() self.stats['os_version'] = platform.release() elif self.stats['os_name'].endswith('BSD'): self.stats['os_version'] = platform.release() diff --git a/requirements.txt b/requirements.txt index ec31d5b8..55b276a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -psutil==4.3.0 +psutil==4.4.0 +distro==1.0.0 diff --git a/setup.py b/setup.py index 7fb20a02..ce5c5869 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def get_data_files(): def get_requires(): - requires = ['psutil>=2.0.0'] + requires = ['psutil>=2.0.0', 'distro>=1.0.0'] return requires From 8866321a49a145049afe30d490378749f3b8ef41 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Thu, 27 Oct 2016 21:37:57 +0200 Subject: [PATCH 35/89] Deprecate Windows Curse UI: automaticaly open Web Browser for the standalone mode #946 --- glances/__init__.py | 16 ++++++++++++---- glances/main.py | 2 ++ glances/outputs/glances_bottle.py | 14 +++++++++++++- glances/webserver.py | 2 +- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/glances/__init__.py b/glances/__init__.py index efaca56d..2c16d18b 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -43,6 +43,7 @@ except ImportError: # Note: others Glances libs will be imported optionally from glances.logger import logger from glances.main import GlancesMain +from glances.globals import WINDOWS # Check locale try: @@ -118,7 +119,7 @@ def main(): signal.signal(signal.SIGINT, __signal_handler) # Glances can be ran in standalone, client or server mode - if core.is_standalone(): + if core.is_standalone() and not WINDOWS: logger.info("Start standalone mode") # Import the Glances standalone module @@ -131,7 +132,7 @@ def main(): # Start the standalone (CLI) loop standalone.serve_forever() - elif core.is_client(): + elif core.is_client() and not WINDOWS: if core.is_client_browser(): logger.info("Start client mode (browser)") @@ -185,15 +186,22 @@ def main(): # Shutdown the server? server.server_close() - elif core.is_webserver(): + elif core.is_webserver() or (core.is_standalone() and WINDOWS): logger.info("Start web server mode") # Import the Glances web server module from glances.webserver import GlancesWebServer + args = core.get_args() + + # Web server mode replace the standalone mode on Windows OS + # In this case, try to start the web browser mode automaticaly + if core.is_standalone() and WINDOWS: + args.open_web_browser = True + # Init the web server mode webserver = GlancesWebServer(config=core.get_config(), - args=core.get_args()) + args=args) # Start the web server loop webserver.serve_forever() diff --git a/glances/main.py b/glances/main.py index 960b7376..f6253ec0 100644 --- a/glances/main.py +++ b/glances/main.py @@ -215,6 +215,8 @@ Start the client browser (browser mode):\n\ dest='webserver', help='run Glances in web server mode (bottle needed)') parser.add_argument('--cached-time', default=self.cached_time, type=int, dest='cached_time', help='set the server cache time [default: {} sec]'.format(self.cached_time)) + parser.add_argument('--open-web-browser', action='store_true', default=False, + dest='open_web_browser', help='try to open the Web UI in the default Web browser') # Display options parser.add_argument('-q', '--quiet', default=False, action='store_true', dest='quiet', help='do not display the curses interface') diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index 82a68b47..7002b9fc 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -24,8 +24,10 @@ import os import sys import tempfile from io import open +import webbrowser from glances.timer import Timer +from glances.globals import WINDOWS from glances.logger import logger try: @@ -117,9 +119,19 @@ class GlancesBottle(object): self.plugins_list = self.stats.getAllPlugins() # Bind the Bottle TCP address/port - bindmsg = 'Glances web server started on http://{}:{}/'.format(self.args.bind_address, self.args.port) + bindurl = 'http://{}:{}/'.format(self.args.bind_address, + self.args.port) + bindmsg = 'Glances web server started on {}'.format(bindurl) logger.info(bindmsg) print(bindmsg) + if self.args.open_web_browser: + # Implementation of the issue #946 + # Try to open the Glances Web UI in the default Web browser if: + # 1) --open-web-browser option is used + # 2) Glances standalone mode is running on Windows OS + webbrowser.open(bindurl, + new=2, + autoraise=1) self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug) def end(self): diff --git a/glances/webserver.py b/glances/webserver.py index 41bb9840..95e8151f 100644 --- a/glances/webserver.py +++ b/glances/webserver.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# Copyright (C) 2015 Nicolargo +# Copyright (C) 2016 Nicolargo # # Glances is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by From 9104f439f470139467df96db85faabb4aa5ef666 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Thu, 27 Oct 2016 21:40:59 +0200 Subject: [PATCH 36/89] Update NEWS file --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 93d563bc..7f28b57a 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,10 @@ Glances Version 2 Version 2.8 =========== +Deprecated: + + * Deprecate Windows Curse UI: automaticaly open Web Browser for the standalone mode (issue #946) + Enhancements and news features: * Add ZeroMQ exporter (issue #939) From b19b1f54b1fc690011a28d735cee1d491da9d66e Mon Sep 17 00:00:00 2001 From: nicolargo Date: Fri, 28 Oct 2016 09:26:57 +0200 Subject: [PATCH 37/89] Add --disable-