Test Failed
Push — master ( ce0fc3...e09530 )
by Nicolas
03:36
created

GlancesStdoutApiDoc.update()   B

Complexity

Conditions 6

Size

Total Lines 43
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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