From ef2f949f04808613a459fa8739fc685304df4097 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Mon, 14 Aug 2023 10:30:20 +0200 Subject: Implement a new API method to limit the number of returned list object #2491 --- glances/outputs/glances_bottle.py | 35 ++++++++++++++++++++++++++++++-- glances/outputs/glances_stdout_apidoc.py | 20 ++++++++++++++++++ glances/plugins/wifi/model.py | 32 +++++++++++++---------------- unitest-restful.py | 12 +++++++++++ 4 files changed, 79 insertions(+), 20 deletions(-) diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index deb47b19..df6cfc71 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -2,12 +2,12 @@ # # This file is part of Glances. # -# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# SPDX-FileCopyrightText: 2023 Nicolas Hennion # # SPDX-License-Identifier: LGPL-3.0-only # -"""Web interface class.""" +"""RestFull API interface class.""" import os import sys @@ -152,6 +152,7 @@ class GlancesBottle(object): self._app.route( '/api/%s//history/' % self.API_VERSION, method="GET", callback=self._api_history ) + self._app.route('/api/%s//top/' % self.API_VERSION, method="GET", callback=self._api_top) self._app.route('/api/%s//limits' % self.API_VERSION, method="GET", callback=self._api_limits) self._app.route('/api/%s//views' % self.API_VERSION, method="GET", callback=self._api_views) self._app.route('/api/%s//' % self.API_VERSION, method="GET", callback=self._api_item) @@ -393,6 +394,36 @@ class GlancesBottle(object): return statval + @compress + def _api_top(self, plugin, nb=0): + """Glances API RESTful implementation. + + Return the JSON representation of a given plugin limited to the top nb items. + It is used to reduce the payload of the HTTP response (example: processlist). + + HTTP/200 if OK + HTTP/400 if plugin is not found + HTTP/404 if others error + """ + response.content_type = 'application/json; charset=utf-8' + + if plugin not in self.plugins_list: + abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) + + # Update the stat + self.__update__() + + try: + # Get the value of the stat ID + statval = self.stats.get_plugin(plugin).get_export() + except Exception as e: + abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e))) + + if isinstance(statval, list): + return json_dumps(statval[:nb]) + else: + return json_dumps(statval) + @compress def _api_history(self, plugin, nb=0): """Glances API RESTful implementation. diff --git a/glances/outputs/glances_stdout_apidoc.py b/glances/outputs/glances_stdout_apidoc.py index c57eefa5..ed9c2da1 100644 --- a/glances/outputs/glances_stdout_apidoc.py +++ b/glances/outputs/glances_stdout_apidoc.py @@ -163,6 +163,22 @@ def print_all(): print('') +def print_top(stats): + time.sleep(1) + stats.update() + sub_title = 'GET top n items of a specific plugin' + print(sub_title) + print('-' * len(sub_title)) + print('') + print('Get top 2 processes of the processlist plugin::') + print('') + print(' # curl {}/processlist/top/2'.format(API_URL)) + print(indent_stat(stats.get_plugin('processlist').get_export()[:2])) + print('') + print('Note: Only work for plugin with a list of items') + print('') + + def print_history(stats): time.sleep(1) stats.update() @@ -248,6 +264,10 @@ class GlancesStdoutApiDoc(object): # Get all stats print_all() + # Get top stats (only for plugins with a list of items) + # Example for processlist plugin: get top 2 processes + print_top(stats) + # History print_history(stats) diff --git a/glances/plugins/wifi/model.py b/glances/plugins/wifi/model.py index 7d13fcaf..c221d688 100644 --- a/glances/plugins/wifi/model.py +++ b/glances/plugins/wifi/model.py @@ -47,8 +47,7 @@ WIRELESS_FILE = '/proc/net/wireless' wireless_file_exists = file_exists(WIRELESS_FILE) if not nmcli_command_exists and not wireless_file_exists: - logger.debug("Wifi plugin is disabled (no %s command or %s file found)" % ('nmcli', - WIRELESS_FILE)) + logger.debug("Wifi plugin is disabled (no %s command or %s file found)" % ('nmcli', WIRELESS_FILE)) class PluginModel(GlancesPluginModel): @@ -121,12 +120,14 @@ class PluginModel(GlancesPluginModel): # Extract the stats wifi_stats = wifi_stats.split() # Add the Wifi link to the list - stats.append({ - 'key': self.get_key(), - 'ssid': wifi_stats[0][:-1], - 'signal': float(wifi_stats[3]), - 'security': '' - }) + stats.append( + { + 'key': self.get_key(), + 'ssid': wifi_stats[0][:-1], + 'signal': float(wifi_stats[3]), + 'security': '', + } + ) # Next line wifi_stats = f.readline() @@ -202,11 +203,11 @@ class PluginModel(GlancesPluginModel): hotspot_name = i['ssid'] # Cut hotspot_name if it is too long if len(hotspot_name) > if_name_max_width: - hotspot_name = '_' + hotspot_name[-if_name_max_width - len(i['security']) + 1:] + hotspot_name = '_' + hotspot_name[-if_name_max_width - len(i['security']) + 1 :] # Add the new hotspot to the message - msg = '{:{width}} {security}'.format(nativestr(hotspot_name), - width=if_name_max_width - len(i['security']) - 1, - security=i['security']) + msg = '{:{width}} {security}'.format( + nativestr(hotspot_name), width=if_name_max_width - len(i['security']) - 1, security=i['security'] + ) ret.append(self.curse_add_line(msg)) msg = '{:>7}'.format( i['signal'], @@ -244,12 +245,7 @@ class ThreadHotspot(threading.Thread): if len(h) != 4 or h[0] != 'yes': # Do not process the line if it is not the active hotspot continue - nmcli_result.append({ - 'key': 'ssid', - 'ssid': h[1], - 'signal': -float(h[2]), - 'security': h[3] - }) + nmcli_result.append({'key': 'ssid', 'ssid': h[1], 'signal': -float(h[2]), 'security': h[3]}) self.thread_stats = nmcli_result # Wait refresh time until next scan # Note: nmcli cache the result for x seconds diff --git a/unitest-restful.py b/unitest-restful.py index a697ff78..17e734e1 100755 --- a/unitest-restful.py +++ b/unitest-restful.py @@ -231,6 +231,18 @@ class TestGlances(unittest.TestCase): self.assertTrue(req.ok) self.assertEqual(req.text, "Active") + def test_013_top(self): + """Values.""" + method = "processlist" + request = "%s/%s/top/2" % (URL, method) + print('INFO: [TEST_013] Top nb item of PROCESSLIST') + print(request) + req = self.http_get(request) + + self.assertTrue(req.ok) + self.assertIsInstance(req.json(), list) + self.assertEqual(len(req.json()), 2) + def test_999_stop_server(self): """Stop the Glances Web Server.""" print('INFO: [TEST_999] Stop the Glances Web Server') -- cgit v1.2.3