summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornicolargo <nicolas@nicolargo.com>2023-08-14 10:30:20 +0200
committernicolargo <nicolas@nicolargo.com>2023-08-14 10:30:20 +0200
commitef2f949f04808613a459fa8739fc685304df4097 (patch)
treeff716162a1b845900b3edca870a3b63daa732975
parentc5e11cef5960ee13289c10c615e2d2034a1aede4 (diff)
Implement a new API method to limit the number of returned list object #2491
-rw-r--r--glances/outputs/glances_bottle.py35
-rw-r--r--glances/outputs/glances_stdout_apidoc.py20
-rw-r--r--glances/plugins/wifi/model.py32
-rwxr-xr-xunitest-restful.py12
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 <nicolas@nicolargo.com>
+# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com>
#
# 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/<plugin>/history/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_history
)
+ self._app.route('/api/%s/<plugin>/top/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_top)
self._app.route('/api/%s/<plugin>/limits' % self.API_VERSION, method="GET", callback=self._api_limits)
self._app.route('/api/%s/<plugin>/views' % self.API_VERSION, method="GET", callback=self._api_views)
self._app.route('/api/%s/<plugin>/<item>' % self.API_VERSION, method="GET", callback=self._api_item)
@@ -394,6 +395,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')