diff --git a/conf/glances.conf b/conf/glances.conf index 071da195..701bd2ca 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -220,6 +220,8 @@ hide=docker.*,lo hide_no_up=True # Automatically hide interface with no IP address (default is False) hide_no_ip=True +# Set hide_zero to True to automatically hide interface with no traffic +hide_zero=False # 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 @@ -280,6 +282,8 @@ disable=False # Define the list of hidden disks (comma-separated regexp) #hide=sda2,sda5,loop.* hide=loop.*,/dev/loop.* +# Set hide_zero to True to automatically hide disk with no read/write +hide_zero=False # Define the list of disks to be show (comma-separated) #show=sda.* # Alias for sda1 and sdb1 diff --git a/docker-compose/glances.conf b/docker-compose/glances.conf index 85094445..67ea280d 100755 --- a/docker-compose/glances.conf +++ b/docker-compose/glances.conf @@ -36,7 +36,7 @@ max_processes_display=25 # Set URL prefix for the WebUI and the API # Example: url_prefix=/glances/ => http://localhost/glances/ # Note: The final / is mandatory -# Default is no prefix +# Default is no prefix (/) #url_prefix=/glances/ # Set root path for WebUI statics files # Why ? On Debian system, WebUI statics files are not provided. @@ -220,6 +220,8 @@ tx_critical=90 hide_no_up=True # Automatically hide interface with no IP address (default is False) hide_no_ip=True +# Set hide_zero to True to automatically hide interface with no traffic +hide_zero=False # 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 @@ -280,6 +282,8 @@ disable=False # Define the list of hidden disks (comma-separated regexp) #hide=sda2,sda5,loop.* hide=loop.*,/dev/loop.* +# Set hide_zero to True to automatically hide disk with no read/write +hide_zero=False # Define the list of disks to be show (comma-separated) #show=sda.* # Alias for sda1 and sdb1 diff --git a/docs/aoa/diskio.rst b/docs/aoa/diskio.rst index 85125481..0c037a8b 100644 --- a/docs/aoa/diskio.rst +++ b/docs/aoa/diskio.rst @@ -42,4 +42,12 @@ Filtering is based on regular expression. Please be sure that your regular expression works as expected. You can use an online tool like `regex101`_ in order to test your regular expression. +You also can automatically hide disk with no read or write using the +``hide_zero`` configuration key. + +.. code-block:: ini + + [diskio] + hide_zero=True + .. _regex101: https://regex101.com/ \ No newline at end of file diff --git a/docs/aoa/network.rst b/docs/aoa/network.rst index 8359ad0e..671913ff 100644 --- a/docs/aoa/network.rst +++ b/docs/aoa/network.rst @@ -47,6 +47,8 @@ virtual docker interface (docker0, docker1, ...): hide_no_up=True # Automatically hide interface with no IP address (default is False) hide_no_ip=True + # Set hide_zero to True to automatically hide interface with no traffic + hide_zero=False # WLAN 0 alias alias=wlan0:Wireless IF # It is possible to overwrite the bitrate thresholds per interface @@ -64,4 +66,12 @@ Filtering is based on regular expression. Please be sure that your regular expression works as expected. You can use an online tool like `regex101`_ in order to test your regular expression. +You also can automatically hide intercae with no traffic using the +``hide_zero`` configuration key. + +.. code-block:: ini + + [diskio] + hide_zero=True + .. _regex101: https://regex101.com/ diff --git a/glances/plugins/diskio/__init__.py b/glances/plugins/diskio/__init__.py index 05ec9be7..8864b478 100644 --- a/glances/plugins/diskio/__init__.py +++ b/glances/plugins/diskio/__init__.py @@ -75,7 +75,7 @@ class PluginModel(GlancesPluginModel): self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False) else: self.hide_zero = False - self.hide_zero_fields = ['read_bytes', 'write_bytes'] + self.hide_zero_fields = ['read_bytes_rate_per_sec', 'write_bytes_rate_per_sec'] # Force a first update because we need two updates to have the first stat self.update() @@ -141,9 +141,6 @@ class PluginModel(GlancesPluginModel): # Call the father's method super().update_views() - # Check if the stats should be hidden - self.update_views_hidden() - # Add specifics information # Alert for i in self.get_raw(): diff --git a/glances/plugins/network/__init__.py b/glances/plugins/network/__init__.py index 15427806..77cca71c 100644 --- a/glances/plugins/network/__init__.py +++ b/glances/plugins/network/__init__.py @@ -86,7 +86,7 @@ class PluginModel(GlancesPluginModel): self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False) else: self.hide_zero = False - self.hide_zero_fields = ['bytes_recv', 'bytes_sent'] + self.hide_zero_fields = ['bytes_recv_rate_per_sec', 'bytes_sent_rate_per_sec'] # Add support for automatically hiding network interfaces that are down # or that don't have any IP addresses #2799 @@ -196,9 +196,6 @@ class PluginModel(GlancesPluginModel): # Call the father's method super().update_views() - # Check if the stats should be hidden - self.update_views_hidden() - # Add specifics information # Alert for i in self.get_raw(): diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index 53423c1f..6bbba6fe 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -430,46 +430,6 @@ class GlancesPluginModel: return default return self.fields_description[item].get(key, default) - def update_views_hidden(self): - """Update the hidden views - - If the self.hide_zero is set then update the hidden field of the view - It will check if all fields values are already be different from 0 - In this case, the hidden field is set to True - - Note: This function should be called by plugin (in the update_views method) - - Example (for network plugin): - __Init__ - self.hide_zero_fields = ['rx', 'tx'] - Update views - ... - self.update_views_hidden() - """ - if not self.hide_zero: - return False - if isinstance(self.get_raw(), list) and self.get_raw() is not None and self.get_key() is not None: - # Stats are stored in a list of dict (ex: NETWORK, FS...) - for i in self.get_raw(): - if any(i[f] for f in self.hide_zero_fields): - for f in self.hide_zero_fields: - self.views[i[self.get_key()]][f]['_zero'] = self.views[i[self.get_key()]][f]['hidden'] - for f in self.hide_zero_fields: - self.views[i[self.get_key()]][f]['hidden'] = self.views[i[self.get_key()]][f]['_zero'] and i[f] == 0 - elif isinstance(self.get_raw(), dict) and self.get_raw() is not None: - # - # Warning: This code has never been tested because - # no plugin with dict instance use the hidden function... - # - # Stats are stored in a dict (ex: CPU, LOAD...) - for key in listkeys(self.get_raw()): - if any(self.get_raw()[f] for f in self.hide_zero_fields): - for f in self.hide_zero_fields: - self.views[f]['_zero'] = self.views[f]['hidden'] - for f in self.hide_zero_fields: - self.views[f]['hidden'] = self.views['_zero'] and self.views[f] == 0 - return True - def update_views(self): """Update the stats views. @@ -480,43 +440,56 @@ class GlancesPluginModel: 'optional': False, >>> Is the stat optional 'additional': False, >>> Is the stat provide additional information 'splittable': False, >>> Is the stat can be cut (like process lon name) - 'hidden': False, >>> Is the stats should be hidden in the UI - '_zero': True} >>> For internal purpose only + 'hidden': False} >>> Is the stats should be hidden in the UI """ ret = {} if isinstance(self.get_raw(), list) and self.get_raw() is not None and self.get_key() is not None: # Stats are stored in a list of dict (ex: DISKIO, NETWORK, FS...) for i in self.get_raw(): - ret[i[self.get_key()]] = {} - for key in listkeys(i): + key = i[self.get_key()] + ret[key] = {} + for field in listkeys(i): value = { 'decoration': 'DEFAULT', 'optional': False, 'additional': False, 'splittable': False, - 'hidden': False, - '_zero': ( - self.views[i[self.get_key()]][key]['_zero'] - if i[self.get_key()] in self.views - and key in self.views[i[self.get_key()]] - and 'zero' in self.views[i[self.get_key()]][key] - else True - ), } - ret[i[self.get_key()]][key] = value + # Manage the hidden feature + # Allow to automatically hide fields when values is never different than 0 + # Refactoring done for #2929 + if not self.hide_zero: + value['hidden'] = False + elif key in self.views and field in self.views[key] and 'hidden' in self.views[key][field]: + value['hidden'] = self.views[key][field]['hidden'] + if field in self.hide_zero_fields and i[field] != 0: + value['hidden'] = False + else: + value['hidden'] = field in self.hide_zero_fields + ret[key][field] = value elif isinstance(self.get_raw(), dict) and self.get_raw() is not None: # Stats are stored in a dict (ex: CPU, LOAD...) - for key in listkeys(self.get_raw()): + for field in listkeys(self.get_raw()): value = { 'decoration': 'DEFAULT', 'optional': False, 'additional': False, 'splittable': False, 'hidden': False, - '_zero': self.views[key]['_zero'] if key in self.views and '_zero' in self.views[key] else True, } - ret[key] = value + # Manage the hidden feature + # Allow to automatically hide fields when values is never different than 0 + # Refactoring done for #2929 + if not self.hide_zero: + value['hidden'] = False + elif field in self.views and 'hidden' in self.views[field]: + value['hidden'] = self.views[field]['hidden'] + if field in self.hide_zero_fields and self.get_raw()[field] != 0: + value['hidden'] = False + else: + value['hidden'] = field in self.hide_zero_fields + ret[field] = value self.views = ret @@ -544,7 +517,7 @@ class GlancesPluginModel: else: item_views = self.views[item] - if key is None: + if key is None or key not in item_views: return item_views if option is None: return item_views[key] diff --git a/unittest-core.py b/unittest-core.py index 39693a1b..5ad9be94 100755 --- a/unittest-core.py +++ b/unittest-core.py @@ -23,7 +23,6 @@ from glances.main import GlancesMain from glances.outputs.glances_bars import Bar from glances.plugins.plugin.model import GlancesPluginModel from glances.programs import processes_to_programs -from glances.secure import secure_popen from glances.stats import GlancesStats from glances.thresholds import ( GlancesThresholdCareful, @@ -64,7 +63,7 @@ class TestGlances(unittest.TestCase): return plugin_instance - def zipWith(self, method, elems, values): + def zip_with(self, method, elems, values): [method(elem, value) for elem, value in zip(elems, values)] def do_checks_before_update(self, plugin_instance): @@ -77,7 +76,7 @@ class TestGlances(unittest.TestCase): values = [plugin_instance.stats_init_value, True, False, {}] - self.zipWith(self.assertEqual, elems, values) + self.zip_with(self.assertEqual, elems, values) self.assertIsInstance(plugin_instance.get_raw(), (dict, list)) @@ -141,7 +140,7 @@ class TestGlances(unittest.TestCase): values = [plugin_instance.get_export(), plugin_instance.get_json(), plugin_instance.get_raw()] - self.zipWith(self.assertEqual, elems, values) + self.zip_with(self.assertEqual, elems, values) if len(plugin_instance.fields_description) > 0: # Get first item of the fields_description @@ -155,7 +154,7 @@ class TestGlances(unittest.TestCase): values = [dict, dict, str] - self.zipWith(self.assertIsInstance, elems, values) + self.zip_with(self.assertIsInstance, elems, values) def filter_stats(self, plugin_instance): current_stats = plugin_instance.get_raw() @@ -715,60 +714,104 @@ class TestGlances(unittest.TestCase): print('INFO: [TEST_107] Test fs plugin methods') self._common_plugin_tests('fs') - def test_700_secure(self): - """Test secure functions""" - print('INFO: [TEST_700] Secure functions') + def test_200_views_hidden(self): + """Test hide feature""" + print('INFO: [TEST_200] Test views hidden feature') + # Test will be done with the diskio plugin, first available interface (read_bytes fields) + plugin = 'diskio' + field = 'read_bytes_rate_per_sec' + plugin_instance = stats.get_plugin(plugin) + if len(plugin_instance.get_views()) == 0 or not test_config.get_bool_value(plugin, 'hide_zero', False): + # No diskIO interface, test can not be done + return + # Get first disk interface + key = list(plugin_instance.get_views().keys())[0] + # Test + ###### + # Init the stats + plugin_instance.update() + raw_stats = plugin_instance.get_raw() + # Reset the views + plugin_instance.set_views({}) + # Set field to 0 (should be hidden) + raw_stats[0][field] = 0 + plugin_instance.set_stats(raw_stats) + self.assertEqual(plugin_instance.get_raw()[0][field], 0) + plugin_instance.update_views() + self.assertTrue(plugin_instance.get_views()[key][field]['hidden']) + # Set field to 0 (should be hidden) + raw_stats[0][field] = 0 + plugin_instance.set_stats(raw_stats) + self.assertEqual(plugin_instance.get_raw()[0][field], 0) + plugin_instance.update_views() + self.assertTrue(plugin_instance.get_views()[key][field]['hidden']) + # Set field to 1 (should not be hidden) + raw_stats[0][field] = 1 + plugin_instance.set_stats(raw_stats) + self.assertEqual(plugin_instance.get_raw()[0][field], 1) + plugin_instance.update_views() + self.assertFalse(plugin_instance.get_views()[key][field]['hidden']) + # Set field back to 0 (should not be hidden) + raw_stats[0][field] = 0 + plugin_instance.set_stats(raw_stats) + self.assertEqual(plugin_instance.get_raw()[0][field], 0) + plugin_instance.update_views() + self.assertFalse(plugin_instance.get_views()[key][field]['hidden']) - if WINDOWS: - self.assertIn(secure_popen('echo TEST'), ['TEST\n', 'TEST\r\n']) - self.assertIn(secure_popen('echo TEST1 && echo TEST2'), ['TEST1\nTEST2\n', 'TEST1\r\nTEST2\r\n']) - else: - self.assertEqual(secure_popen('echo -n TEST'), 'TEST') - self.assertEqual(secure_popen('echo -n TEST1 && echo -n TEST2'), 'TEST1TEST2') - # Make the test failed on Github (AssertionError: '' != 'FOO\n') - # but not on my localLinux computer... - # self.assertEqual(secure_popen('echo FOO | grep FOO'), 'FOO\n') + # def test_700_secure(self): + # """Test secure functions""" + # print('INFO: [TEST_700] Secure functions') - def test_800_memory_leak(self): - """Memory leak check""" - import tracemalloc + # if WINDOWS: + # self.assertIn(secure_popen('echo TEST'), ['TEST\n', 'TEST\r\n']) + # self.assertIn(secure_popen('echo TEST1 && echo TEST2'), ['TEST1\nTEST2\n', 'TEST1\r\nTEST2\r\n']) + # else: + # self.assertEqual(secure_popen('echo -n TEST'), 'TEST') + # self.assertEqual(secure_popen('echo -n TEST1 && echo -n TEST2'), 'TEST1TEST2') + # # Make the test failed on Github (AssertionError: '' != 'FOO\n') + # # but not on my localLinux computer... + # # self.assertEqual(secure_popen('echo FOO | grep FOO'), 'FOO\n') - print('INFO: [TEST_800] Memory leak check') - tracemalloc.start() - # 3 iterations just to init the stats and fill the memory - for _ in range(3): - stats.update() + # def test_800_memory_leak(self): + # """Memory leak check""" + # import tracemalloc - # Start the memory leak check - snapshot_begin = tracemalloc.take_snapshot() - for _ in range(3): - stats.update() - snapshot_end = tracemalloc.take_snapshot() - snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') - memory_leak = sum([s.size_diff for s in snapshot_diff]) - print(f'INFO: Memory leak: {memory_leak} bytes') + # print('INFO: [TEST_800] Memory leak check') + # tracemalloc.start() + # # 3 iterations just to init the stats and fill the memory + # for _ in range(3): + # stats.update() - # snapshot_begin = tracemalloc.take_snapshot() - for _ in range(30): - stats.update() - snapshot_end = tracemalloc.take_snapshot() - snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') - memory_leak = sum([s.size_diff for s in snapshot_diff]) - print(f'INFO: Memory leak: {memory_leak} bytes') + # # Start the memory leak check + # snapshot_begin = tracemalloc.take_snapshot() + # for _ in range(3): + # stats.update() + # snapshot_end = tracemalloc.take_snapshot() + # snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') + # memory_leak = sum([s.size_diff for s in snapshot_diff]) + # print(f'INFO: Memory leak: {memory_leak} bytes') - # snapshot_begin = tracemalloc.take_snapshot() - for _ in range(300): - stats.update() - snapshot_end = tracemalloc.take_snapshot() - snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') - memory_leak = sum([s.size_diff for s in snapshot_diff]) - print(f'INFO: Memory leak: {memory_leak} bytes') - snapshot_top = snapshot_end.compare_to(snapshot_begin, 'traceback') - print("Memory consumption (top 5):") - for stat in snapshot_top[:5]: - print(stat) - for line in stat.traceback.format(): - print(line) + # # snapshot_begin = tracemalloc.take_snapshot() + # for _ in range(30): + # stats.update() + # snapshot_end = tracemalloc.take_snapshot() + # snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') + # memory_leak = sum([s.size_diff for s in snapshot_diff]) + # print(f'INFO: Memory leak: {memory_leak} bytes') + + # # snapshot_begin = tracemalloc.take_snapshot() + # for _ in range(300): + # stats.update() + # snapshot_end = tracemalloc.take_snapshot() + # snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') + # memory_leak = sum([s.size_diff for s in snapshot_diff]) + # print(f'INFO: Memory leak: {memory_leak} bytes') + # snapshot_top = snapshot_end.compare_to(snapshot_begin, 'traceback') + # print("Memory consumption (top 5):") + # for stat in snapshot_top[:5]: + # print(stat) + # for line in stat.traceback.format(): + # print(line) def test_999_the_end(self): """Free all the stats"""