Test Failed
Push — master ( 73a01d...ef36eb )
by Nicolas
04:43
created

print_api_status()   A

Complexity

Conditions 1

Size

Total Lines 13
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 13
nop 0
dl 0
loc 13
rs 9.75
c 0
b 0
f 0
1
#
2
# This file is part of Glances.
3
#
4
# SPDX-FileCopyrightText: 2024 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
"""Generate Glances Restful API documentation."""
10
11
import json
12
import time
13
from pprint import pformat
14
15
from glances import __apiversion__
16
from glances.logger import logger
17
18
API_URL = f"http://localhost:61208/api/{__apiversion__}"
19
20
APIDOC_HEADER = f"""\
21
.. _api_restful:
22
23
Restful/JSON API documentation
24
==============================
25
26
This documentation describes the Glances API version {__apiversion__} (Restful/JSON) interface.
27
28
An OpenAPI specification file is available at:
29
``https://raw.githubusercontent.com/nicolargo/glances/refs/heads/develop/docs/api/openapi.json``
30
31
Run the Glances API server
32
--------------------------
33
34
The Glances Restful/API server could be ran using the following command line:
35
36
.. code-block:: bash
37
38
    # glances -w --disable-webui
39
40
It is also ran automatically when Glances is started in Web server mode (-w).
41
42
If you want to enable the Glances Central Browser, use:
43
44
.. code-block:: bash
45
46
    # glances -w --browser --disable-webui
47
48
API URL
49
-------
50
51
The default root API URL is ``http://localhost:61208/api/{__apiversion__}``.
52
53
The bind address and port could be changed using the ``--bind`` and ``--port`` command line options.
54
55
It is also possible to define an URL prefix using the ``url_prefix`` option from the [outputs] section
56
of the Glances configuration file.
57
58
Note: The url_prefix should always end with a slash (``/``).
59
60
For example:
61
62
.. code-block:: ini
63
    [outputs]
64
    url_prefix = /glances/
65
66
will change the root API URL to ``http://localhost:61208/glances/api/{__apiversion__}`` and the Web UI URL to
67
``http://localhost:61208/glances/``
68
69
API documentation URL
70
---------------------
71
72
The API documentation is embeded in the server and available at the following URL:
73
``http://localhost:61208/docs#/``.
74
75
WebUI refresh
76
-------------
77
78
It is possible to change the Web UI refresh rate (default is 2 seconds) using the following option in the URL:
79
``http://localhost:61208/?refresh=5``
80
81
"""
82
83
84
def indent_stat(stat, indent='    '):
85
    # Indent stats to pretty print it
86
    if isinstance(stat, list) and len(stat) > 1 and isinstance(stat[0], dict):
87
        # Only display two first items
88
        return indent + pformat(stat[0:2]).replace('\n', '\n' + indent).replace("'", '"')
89
    return indent + pformat(stat).replace('\n', '\n' + indent).replace("'", '"')
90
91
92
def print_api_status():
93
    sub_title = 'GET API status'
94
    print(sub_title)
95
    print('-' * len(sub_title))
96
    print('')
97
    print('This entry point should be used to check the API status.')
98
    print('It will the Glances version and a 200 return code if everything is OK.')
99
    print('')
100
    print('Get the Rest API status::')
101
    print('')
102
    print(f'    # curl -I {API_URL}/status')
103
    print(indent_stat('HTTP/1.0 200 OK'))
104
    print('')
105
106
107
def print_plugins_list(stat):
108
    sub_title = 'GET plugins list'
109
    print(sub_title)
110
    print('-' * len(sub_title))
111
    print('')
112
    print('Get the plugins list::')
113
    print('')
114
    print(f'    # curl {API_URL}/pluginslist')
115
    print(indent_stat(stat))
116
    print('')
117
118
119
def print_plugin_stats(plugin, stat):
120
    sub_title = f'GET {plugin}'
121
    print(sub_title)
122
    print('-' * len(sub_title))
123
    print('')
124
125
    print('Get plugin stats::')
126
    print('')
127
    print(f'    # curl {API_URL}/{plugin}')
128
    print(indent_stat(json.loads(stat.get_stats())))
129
    print('')
130
131
132
def print_plugin_description(plugin, stat):
133
    if stat.fields_description:
134
        # For each plugins with a description
135
        print('Fields descriptions:')
136
        print('')
137
        time_since_update = False
138
        for field, description in stat.fields_description.items():
139
            print(
140
                '* **{}**: {} (unit is *{}*)'.format(
141
                    field,
142
                    (
143
                        description['description'][:-1]
144
                        if description['description'].endswith('.')
145
                        else description['description']
146
                    ),
147
                    description['unit'] if 'unit' in description else 'None',
148
                )
149
            )
150
            if 'rate' in description and description['rate']:
151
                time_since_update = True
152
                print(
153
                    '* **{}**: {} (unit is *{}* per second)'.format(
154
                        field + '_rate_per_sec',
155
                        (
156
                            description['description'][:-1]
157
                            if description['description'].endswith('.')
158
                            else description['description']
159
                        )
160
                        + ' per second',
161
                        description['unit'] if 'unit' in description else 'None',
162
                    )
163
                )
164
                print(
165
                    '* **{}**: {} (unit is *{}*)'.format(
166
                        field + '_gauge',
167
                        (
168
                            description['description'][:-1]
169
                            if description['description'].endswith('.')
170
                            else description['description']
171
                        )
172
                        + ' (cumulative)',
173
                        description['unit'] if 'unit' in description else 'None',
174
                    )
175
                )
176
177
        if time_since_update:
178
            print(
179
                '* **{}**: {} (unit is *{}*)'.format(
180
                    'time_since_update', 'Number of seconds since last update', 'seconds'
181
                )
182
            )
183
184
        print('')
185
    else:
186
        logger.error(f'No fields_description variable defined for plugin {plugin}')
187
188
189
def print_plugin_item_value(plugin, stat, stat_export):
190
    item = None
191
    value = None
192
    if isinstance(stat_export, dict):
193
        item = list(stat_export.keys())[0]
194
        value = None
195
    elif isinstance(stat_export, list) and len(stat_export) > 0 and isinstance(stat_export[0], dict):
196
        if 'key' in stat_export[0]:
197
            item = stat_export[0]['key']
198
        else:
199
            item = list(stat_export[0].keys())[0]
200
    if item and stat.get_stats_item(item):
201
        stat_item = json.loads(stat.get_stats_item(item))
202
        if isinstance(stat_item[item], list):
203
            value = stat_item[item][0]
204
        else:
205
            value = stat_item[item]
206
        print('Get a specific field::')
207
        print('')
208
        print(f'    # curl {API_URL}/{plugin}/{item}')
209
        print(indent_stat(stat_item))
210
        print('')
211
    if item and value and stat.get_stats_value(item, value):
212
        print('Get a specific item when field matches the given value::')
213
        print('')
214
        print(f'    # curl {API_URL}/{plugin}/{item}/value/{value}')
215
        print(indent_stat(json.loads(stat.get_stats_value(item, value))))
216
        print('')
217
218
219
def print_all():
220
    sub_title = 'GET all stats'
221
    print(sub_title)
222
    print('-' * len(sub_title))
223
    print('')
224
    print('Get all Glances stats::')
225
    print('')
226
    print(f'    # curl {API_URL}/all')
227
    print('    Return a very big dictionary with all stats')
228
    print('')
229
    print('Note: Update is done automatically every time /all or /<plugin> is called.')
230
    print('')
231
232
233
def print_processes():
234
    sub_title = 'GET stats of a specific process'
235
    print(sub_title)
236
    print('-' * len(sub_title))
237
    print('')
238
    print('Get stats for process with PID == 777::')
239
    print('')
240
    print(f'    # curl {API_URL}/processes/777')
241
    print('    Return stats for process (dict)')
242
    print('')
243
    print('Enable extended stats for process with PID == 777 (only one process at a time can be enabled)::')
244
    print('')
245
    print(f'    # curl -X POST {API_URL}/processes/extended/777')
246
    print(f'    # curl {API_URL}/all')
247
    print(f'    # curl {API_URL}/processes/777')
248
    print('    Return stats for process (dict)')
249
    print('')
250
    print('Note: Update *is not* done automatically when you call /processes/<pid>.')
251
    print('')
252
253
254
def print_top(stats):
255
    time.sleep(1)
256
    stats.update()
257
    sub_title = 'GET top n items of a specific plugin'
258
    print(sub_title)
259
    print('-' * len(sub_title))
260
    print('')
261
    print('Get top 2 processes of the processlist plugin::')
262
    print('')
263
    print(f'    # curl {API_URL}/processlist/top/2')
264
    print(indent_stat(stats.get_plugin('processlist').get_export()[:2]))
265
    print('')
266
    print('Note: Only work for plugin with a list of items')
267
    print('')
268
269
270
def print_fields_info(stats):
271
    sub_title = 'GET item description'
272
    print(sub_title)
273
    print('-' * len(sub_title))
274
    print('Get item description (human readable) for a specific plugin/item::')
275
    print('')
276
    print(f'    # curl {API_URL}/diskio/read_bytes/description')
277
    print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'description')))
278
    print('')
279
    print('Note: the description is defined in the fields_description variable of the plugin.')
280
    print('')
281
    sub_title = 'GET item unit'
282
    print(sub_title)
283
    print('-' * len(sub_title))
284
    print('Get item unit for a specific plugin/item::')
285
    print('')
286
    print(f'    # curl {API_URL}/diskio/read_bytes/unit')
287
    print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'unit')))
288
    print('')
289
    print('Note: the description is defined in the fields_description variable of the plugin.')
290
    print('')
291
292
293
def print_history(stats):
294
    time.sleep(1)
295
    stats.update()
296
    time.sleep(1)
297
    stats.update()
298
    sub_title = 'GET stats history'
299
    print(sub_title)
300
    print('-' * len(sub_title))
301
    print('')
302
    print('History of a plugin::')
303
    print('')
304
    print(f'    # curl {API_URL}/cpu/history')
305
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=3))))
306
    print('')
307
    print('Limit history to last 2 values::')
308
    print('')
309
    print(f'    # curl {API_URL}/cpu/history/2')
310
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=2))))
311
    print('')
312
    print('History for a specific field::')
313
    print('')
314
    print(f'    # curl {API_URL}/cpu/system/history')
315
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system'))))
316
    print('')
317
    print('Limit history for a specific field to last 2 values::')
318
    print('')
319
    print(f'    # curl {API_URL}/cpu/system/history')
320
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system', nb=2))))
321
    print('')
322
323
324
def print_limits(stats):
325
    sub_title = 'GET limits (used for thresholds)'
326
    print(sub_title)
327
    print('-' * len(sub_title))
328
    print('')
329
    print('All limits/thresholds::')
330
    print('')
331
    print(f'    # curl {API_URL}/all/limits')
332
    print(indent_stat(stats.getAllLimitsAsDict()))
333
    print('')
334
    print('Limits/thresholds for the cpu plugin::')
335
    print('')
336
    print(f'    # curl {API_URL}/cpu/limits')
337
    print(indent_stat(stats.get_plugin('cpu').limits))
338
    print('')
339
340
341
def print_plugin_post_events():
342
    sub_title = 'POST clear events'
343
    print(sub_title)
344
    print('-' * len(sub_title))
345
    print('')
346
    print('Clear all alarms from the list::')
347
    print('')
348
    print(f'    # curl -H "Content-Type: application/json" -X POST {API_URL}/events/clear/all')
349
    print('')
350
    print('Clear warning alarms from the list::')
351
    print('')
352
    print(f'    # curl -H "Content-Type: application/json" -X POST {API_URL}/events/clear/warning')
353
    print('')
354
355
356
class GlancesStdoutApiRestfulDoc:
357
    """This class manages the fields description display."""
358
359
    def __init__(self, config=None, args=None):
360
        # Init
361
        self.config = config
362
        self.args = args
363
364
    def end(self):
365
        pass
366
367
    def update(self, stats, duration=1):
368
        """Display issue"""
369
370
        # Display header
371
        print(APIDOC_HEADER)
372
373
        # Display API status
374
        print_api_status()
375
376
        # Display plugins list
377
        print_plugins_list(sorted(stats._plugins))
378
379
        # Loop over plugins
380
        for plugin in sorted(stats._plugins):
381
            stat = stats.get_plugin(plugin)
382
            print_plugin_stats(plugin, stat)
383
            print_plugin_description(plugin, stat)
384
            if plugin == 'alert':
385
                print_plugin_post_events()
386
387
            stat_export = stat.get_export()
388
            if stat_export is None or stat_export == [] or stat_export == {}:
389
                continue
390
            print_plugin_item_value(plugin, stat, stat_export)
391
392
        # Get all stats
393
        print_all()
394
395
        # Get process stats
396
        print_processes()
397
398
        # Get top stats (only for plugins with a list of items)
399
        # Example for processlist plugin: get top 2 processes
400
        print_top(stats)
401
402
        # Fields description
403
        print_fields_info(stats)
404
405
        # History
406
        print_history(stats)
407
408
        # Limits
409
        print_limits(stats)
410
411
        # Return True to exit directly (no refresh)
412
        return True
413