Show only active Disk I/O (and network interface) #2929

pull/2932/head
nicolargo 2024-09-08 19:17:07 +02:00
parent a3895e49b6
commit 36755ba4be
8 changed files with 155 additions and 119 deletions

View File

@ -220,6 +220,8 @@ hide=docker.*,lo
hide_no_up=True hide_no_up=True
# Automatically hide interface with no IP address (default is False) # Automatically hide interface with no IP address (default is False)
hide_no_ip=True 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 # It is possible to overwrite the bitrate thresholds per interface
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
#wlan0_rx_careful=4000000 #wlan0_rx_careful=4000000
@ -280,6 +282,8 @@ disable=False
# Define the list of hidden disks (comma-separated regexp) # Define the list of hidden disks (comma-separated regexp)
#hide=sda2,sda5,loop.* #hide=sda2,sda5,loop.*
hide=loop.*,/dev/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) # Define the list of disks to be show (comma-separated)
#show=sda.* #show=sda.*
# Alias for sda1 and sdb1 # Alias for sda1 and sdb1

View File

@ -36,7 +36,7 @@ max_processes_display=25
# Set URL prefix for the WebUI and the API # Set URL prefix for the WebUI and the API
# Example: url_prefix=/glances/ => http://localhost/glances/ # Example: url_prefix=/glances/ => http://localhost/glances/
# Note: The final / is mandatory # Note: The final / is mandatory
# Default is no prefix # Default is no prefix (/)
#url_prefix=/glances/ #url_prefix=/glances/
# Set root path for WebUI statics files # Set root path for WebUI statics files
# Why ? On Debian system, WebUI statics files are not provided. # Why ? On Debian system, WebUI statics files are not provided.
@ -220,6 +220,8 @@ tx_critical=90
hide_no_up=True hide_no_up=True
# Automatically hide interface with no IP address (default is False) # Automatically hide interface with no IP address (default is False)
hide_no_ip=True 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 # It is possible to overwrite the bitrate thresholds per interface
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
#wlan0_rx_careful=4000000 #wlan0_rx_careful=4000000
@ -280,6 +282,8 @@ disable=False
# Define the list of hidden disks (comma-separated regexp) # Define the list of hidden disks (comma-separated regexp)
#hide=sda2,sda5,loop.* #hide=sda2,sda5,loop.*
hide=loop.*,/dev/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) # Define the list of disks to be show (comma-separated)
#show=sda.* #show=sda.*
# Alias for sda1 and sdb1 # Alias for sda1 and sdb1

View File

@ -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 expression works as expected. You can use an online tool like `regex101`_ in
order to test your regular expression. 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/ .. _regex101: https://regex101.com/

View File

@ -47,6 +47,8 @@ virtual docker interface (docker0, docker1, ...):
hide_no_up=True hide_no_up=True
# Automatically hide interface with no IP address (default is False) # Automatically hide interface with no IP address (default is False)
hide_no_ip=True hide_no_ip=True
# Set hide_zero to True to automatically hide interface with no traffic
hide_zero=False
# WLAN 0 alias # WLAN 0 alias
alias=wlan0:Wireless IF alias=wlan0:Wireless IF
# It is possible to overwrite the bitrate thresholds per interface # 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 expression works as expected. You can use an online tool like `regex101`_ in
order to test your regular expression. 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/ .. _regex101: https://regex101.com/

View File

@ -75,7 +75,7 @@ class PluginModel(GlancesPluginModel):
self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False) self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False)
else: else:
self.hide_zero = False 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 # Force a first update because we need two updates to have the first stat
self.update() self.update()
@ -141,9 +141,6 @@ class PluginModel(GlancesPluginModel):
# Call the father's method # Call the father's method
super().update_views() super().update_views()
# Check if the stats should be hidden
self.update_views_hidden()
# Add specifics information # Add specifics information
# Alert # Alert
for i in self.get_raw(): for i in self.get_raw():

View File

@ -86,7 +86,7 @@ class PluginModel(GlancesPluginModel):
self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False) self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False)
else: else:
self.hide_zero = False 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 # Add support for automatically hiding network interfaces that are down
# or that don't have any IP addresses #2799 # or that don't have any IP addresses #2799
@ -196,9 +196,6 @@ class PluginModel(GlancesPluginModel):
# Call the father's method # Call the father's method
super().update_views() super().update_views()
# Check if the stats should be hidden
self.update_views_hidden()
# Add specifics information # Add specifics information
# Alert # Alert
for i in self.get_raw(): for i in self.get_raw():

View File

@ -430,46 +430,6 @@ class GlancesPluginModel:
return default return default
return self.fields_description[item].get(key, 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): def update_views(self):
"""Update the stats views. """Update the stats views.
@ -480,43 +440,56 @@ class GlancesPluginModel:
'optional': False, >>> Is the stat optional 'optional': False, >>> Is the stat optional
'additional': False, >>> Is the stat provide additional information 'additional': False, >>> Is the stat provide additional information
'splittable': False, >>> Is the stat can be cut (like process lon name) 'splittable': False, >>> Is the stat can be cut (like process lon name)
'hidden': False, >>> Is the stats should be hidden in the UI 'hidden': False} >>> Is the stats should be hidden in the UI
'_zero': True} >>> For internal purpose only
""" """
ret = {} ret = {}
if isinstance(self.get_raw(), list) and self.get_raw() is not None and self.get_key() is not None: 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...) # Stats are stored in a list of dict (ex: DISKIO, NETWORK, FS...)
for i in self.get_raw(): for i in self.get_raw():
ret[i[self.get_key()]] = {} key = i[self.get_key()]
for key in listkeys(i): ret[key] = {}
for field in listkeys(i):
value = { value = {
'decoration': 'DEFAULT', 'decoration': 'DEFAULT',
'optional': False, 'optional': False,
'additional': False, 'additional': False,
'splittable': 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: elif isinstance(self.get_raw(), dict) and self.get_raw() is not None:
# Stats are stored in a dict (ex: CPU, LOAD...) # Stats are stored in a dict (ex: CPU, LOAD...)
for key in listkeys(self.get_raw()): for field in listkeys(self.get_raw()):
value = { value = {
'decoration': 'DEFAULT', 'decoration': 'DEFAULT',
'optional': False, 'optional': False,
'additional': False, 'additional': False,
'splittable': False, 'splittable': False,
'hidden': 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 self.views = ret
@ -544,7 +517,7 @@ class GlancesPluginModel:
else: else:
item_views = self.views[item] item_views = self.views[item]
if key is None: if key is None or key not in item_views:
return item_views return item_views
if option is None: if option is None:
return item_views[key] return item_views[key]

View File

@ -23,7 +23,6 @@ from glances.main import GlancesMain
from glances.outputs.glances_bars import Bar from glances.outputs.glances_bars import Bar
from glances.plugins.plugin.model import GlancesPluginModel from glances.plugins.plugin.model import GlancesPluginModel
from glances.programs import processes_to_programs from glances.programs import processes_to_programs
from glances.secure import secure_popen
from glances.stats import GlancesStats from glances.stats import GlancesStats
from glances.thresholds import ( from glances.thresholds import (
GlancesThresholdCareful, GlancesThresholdCareful,
@ -64,7 +63,7 @@ class TestGlances(unittest.TestCase):
return plugin_instance 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)] [method(elem, value) for elem, value in zip(elems, values)]
def do_checks_before_update(self, plugin_instance): def do_checks_before_update(self, plugin_instance):
@ -77,7 +76,7 @@ class TestGlances(unittest.TestCase):
values = [plugin_instance.stats_init_value, True, False, {}] 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)) 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()] 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: if len(plugin_instance.fields_description) > 0:
# Get first item of the fields_description # Get first item of the fields_description
@ -155,7 +154,7 @@ class TestGlances(unittest.TestCase):
values = [dict, dict, str] values = [dict, dict, str]
self.zipWith(self.assertIsInstance, elems, values) self.zip_with(self.assertIsInstance, elems, values)
def filter_stats(self, plugin_instance): def filter_stats(self, plugin_instance):
current_stats = plugin_instance.get_raw() current_stats = plugin_instance.get_raw()
@ -715,60 +714,104 @@ class TestGlances(unittest.TestCase):
print('INFO: [TEST_107] Test fs plugin methods') print('INFO: [TEST_107] Test fs plugin methods')
self._common_plugin_tests('fs') self._common_plugin_tests('fs')
def test_700_secure(self): def test_200_views_hidden(self):
"""Test secure functions""" """Test hide feature"""
print('INFO: [TEST_700] Secure functions') 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: # def test_700_secure(self):
self.assertIn(secure_popen('echo TEST'), ['TEST\n', 'TEST\r\n']) # """Test secure functions"""
self.assertIn(secure_popen('echo TEST1 && echo TEST2'), ['TEST1\nTEST2\n', 'TEST1\r\nTEST2\r\n']) # print('INFO: [TEST_700] Secure functions')
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_800_memory_leak(self): # if WINDOWS:
"""Memory leak check""" # self.assertIn(secure_popen('echo TEST'), ['TEST\n', 'TEST\r\n'])
import tracemalloc # 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') # def test_800_memory_leak(self):
tracemalloc.start() # """Memory leak check"""
# 3 iterations just to init the stats and fill the memory # import tracemalloc
for _ in range(3):
stats.update()
# Start the memory leak check # print('INFO: [TEST_800] Memory leak check')
snapshot_begin = tracemalloc.take_snapshot() # tracemalloc.start()
for _ in range(3): # # 3 iterations just to init the stats and fill the memory
stats.update() # for _ in range(3):
snapshot_end = tracemalloc.take_snapshot() # stats.update()
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() # # Start the memory leak check
for _ in range(30): # snapshot_begin = tracemalloc.take_snapshot()
stats.update() # for _ in range(3):
snapshot_end = tracemalloc.take_snapshot() # stats.update()
snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') # snapshot_end = tracemalloc.take_snapshot()
memory_leak = sum([s.size_diff for s in snapshot_diff]) # snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename')
print(f'INFO: Memory leak: {memory_leak} bytes') # memory_leak = sum([s.size_diff for s in snapshot_diff])
# print(f'INFO: Memory leak: {memory_leak} bytes')
# snapshot_begin = tracemalloc.take_snapshot() # # snapshot_begin = tracemalloc.take_snapshot()
for _ in range(300): # for _ in range(30):
stats.update() # stats.update()
snapshot_end = tracemalloc.take_snapshot() # snapshot_end = tracemalloc.take_snapshot()
snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename') # snapshot_diff = snapshot_end.compare_to(snapshot_begin, 'filename')
memory_leak = sum([s.size_diff for s in snapshot_diff]) # memory_leak = sum([s.size_diff for s in snapshot_diff])
print(f'INFO: Memory leak: {memory_leak} bytes') # print(f'INFO: Memory leak: {memory_leak} bytes')
snapshot_top = snapshot_end.compare_to(snapshot_begin, 'traceback')
print("Memory consumption (top 5):") # # snapshot_begin = tracemalloc.take_snapshot()
for stat in snapshot_top[:5]: # for _ in range(300):
print(stat) # stats.update()
for line in stat.traceback.format(): # snapshot_end = tracemalloc.take_snapshot()
print(line) # 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): def test_999_the_end(self):
"""Free all the stats""" """Free all the stats"""