Test Failed
Push — master ( ee826a...d9056e )
by Nicolas
03:09
created

glances.outputs.glances_stdout_apidoc   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 370
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 228
dl 0
loc 370
rs 8.96
c 0
b 0
f 0
wmc 43

11 Functions

Rating   Name   Duplication   Size   Complexity  
A indent_stat() 0 7 4
A print_api_status() 0 13 1
A print_plugin_stats() 0 11 1
A print_plugins_list() 0 10 1
A print_fields_info() 0 21 1
A print_history() 0 29 1
D print_plugin_description() 0 55 12
A print_all() 0 10 1
A print_limits() 0 15 1
A print_top() 0 14 1
D print_plugin_item_value() 0 28 12

3 Methods

Rating   Name   Duplication   Size   Complexity  
A GlancesStdoutApiDoc.__init__() 0 4 1
A GlancesStdoutApiDoc.end() 0 2 1
A GlancesStdoutApiDoc.update() 0 41 5

How to fix   Complexity   

Complexity

Complex classes like glances.outputs.glances_stdout_apidoc often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# SPDX-FileCopyrightText: 2024 Nicolas Hennion <[email protected]>
6
#
7
# SPDX-License-Identifier: LGPL-3.0-only
8
#
9
10
"""Fields description interface class."""
11
12
from pprint import pformat
13
import json
14
import time
15
16
from glances import __apiversion__
17
from glances.logger import logger
18
from glances.globals import iteritems
19
20
21
API_URL = "http://localhost:61208/api/{api_version}".format(api_version=__apiversion__)
22
23
APIDOC_HEADER = """\
24
.. _api:
25
26
API (Restfull/JSON) documentation
27
=================================
28
29
This documentation describes the Glances API version {api_version} (Restfull/JSON) interface.
30
31
For Glances version 3, please have a look on:
32
``https://github.com/nicolargo/glances/blob/support/glancesv3/docs/api.rst``
33
34
Run the Glances API server
35
--------------------------
36
37
The Glances Restfull/API server could be ran using the following command line:
38
39
.. code-block:: bash
40
41
    # glances -w --disable-webui
42
43
It is also ran automatically when Glances is started in Web server mode (-w).
44
45
API URL
46
-------
47
48
The default root API URL is ``http://localhost:61208/api/{api_version}``.
49
50
The bind address and port could be changed using the ``--bind`` and ``--port`` command line options.
51
52
It is also possible to define an URL prefix using the ``url_prefix`` option from the [outputs] section
53
of the Glances configuration file.
54
55
Note: The url_prefix should always end with a slash (``/``).
56
57
For example:
58
59
.. code-block:: ini
60
    [outputs]
61
    url_prefix = /glances/
62
63
will change the root API URL to ``http://localhost:61208/glances/api/{api_version}`` and the Web UI URL to
64
``http://localhost:61208/glances/``
65
66
API documentation URL
67
---------------------
68
69
The API documentation is embeded in the server and available at the following URL:
70
``http://localhost:61208/docs#/``.
71
72
WebUI refresh
73
-------------
74
75
It is possible to change the Web UI refresh rate (default is 2 seconds) using the following option in the URL:
76
``http://localhost:61208/glances/?refresh=5``
77
78
""".format(
79
    api_version=__apiversion__
80
)
81
82
83
def indent_stat(stat, indent='    '):
84
    # Indent stats to pretty print it
85
    if isinstance(stat, list) and len(stat) > 1 and isinstance(stat[0], dict):
86
        # Only display two first items
87
        return indent + pformat(stat[0:2]).replace('\n', '\n' + indent).replace("'", '"')
88
    else:
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('    # curl -I {}/status'.format(API_URL))
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('    # curl {}/pluginslist'.format(API_URL))
115
    print(indent_stat(stat))
116
    print('')
117
118
119
def print_plugin_stats(plugin, stat):
120
    sub_title = 'GET {}'.format(plugin)
121
    print(sub_title)
122
    print('-' * len(sub_title))
123
    print('')
124
125
    print('Get plugin stats::')
126
    print('')
127
    print('    # curl {}/{}'.format(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 iteritems(stat.fields_description):
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('No fields_description variable defined for plugin {}'.format(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('    # curl {}/{}/{}'.format(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('    # curl {}/{}/{}/{}'.format(API_URL, plugin, item, 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('    # curl {}/all'.format(API_URL))
227
    print('    Return a very big dictionary (avoid using this request, performances will be poor)...')
228
    print('')
229
230
231
def print_top(stats):
232
    time.sleep(1)
233
    stats.update()
234
    sub_title = 'GET top n items of a specific plugin'
235
    print(sub_title)
236
    print('-' * len(sub_title))
237
    print('')
238
    print('Get top 2 processes of the processlist plugin::')
239
    print('')
240
    print('    # curl {}/processlist/top/2'.format(API_URL))
241
    print(indent_stat(stats.get_plugin('processlist').get_export()[:2]))
242
    print('')
243
    print('Note: Only work for plugin with a list of items')
244
    print('')
245
246
247
def print_fields_info(stats):
248
    sub_title = 'GET item description'
249
    print(sub_title)
250
    print('-' * len(sub_title))
251
    print('Get item description (human readable) for a specific plugin/item::')
252
    print('')
253
    print('    # curl {}/diskio/read_bytes/description'.format(API_URL))
254
    print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'description')))
255
    print('')
256
    print('Note: the description is defined in the fields_description variable of the plugin.')
257
    print('')
258
    sub_title = 'GET item unit'
259
    print(sub_title)
260
    print('-' * len(sub_title))
261
    print('Get item unit for a specific plugin/item::')
262
    print('')
263
    print('    # curl {}/diskio/read_bytes/unit'.format(API_URL))
264
    print(indent_stat(stats.get_plugin('diskio').get_item_info('read_bytes', 'unit')))
265
    print('')
266
    print('Note: the description is defined in the fields_description variable of the plugin.')
267
    print('')
268
269
270
def print_history(stats):
271
    time.sleep(1)
272
    stats.update()
273
    time.sleep(1)
274
    stats.update()
275
    sub_title = 'GET stats history'
276
    print(sub_title)
277
    print('-' * len(sub_title))
278
    print('')
279
    print('History of a plugin::')
280
    print('')
281
    print('    # curl {}/cpu/history'.format(API_URL))
282
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=3))))
283
    print('')
284
    print('Limit history to last 2 values::')
285
    print('')
286
    print('    # curl {}/cpu/history/2'.format(API_URL))
287
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history(nb=2))))
288
    print('')
289
    print('History for a specific field::')
290
    print('')
291
    print('    # curl {}/cpu/system/history'.format(API_URL))
292
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system'))))
293
    print('')
294
    print('Limit history for a specific field to last 2 values::')
295
    print('')
296
    print('    # curl {}/cpu/system/history'.format(API_URL))
297
    print(indent_stat(json.loads(stats.get_plugin('cpu').get_stats_history('system', nb=2))))
298
    print('')
299
300
301
def print_limits(stats):
302
    sub_title = 'GET limits (used for thresholds)'
303
    print(sub_title)
304
    print('-' * len(sub_title))
305
    print('')
306
    print('All limits/thresholds::')
307
    print('')
308
    print('    # curl {}/all/limits'.format(API_URL))
309
    print(indent_stat(stats.getAllLimitsAsDict()))
310
    print('')
311
    print('Limits/thresholds for the cpu plugin::')
312
    print('')
313
    print('    # curl {}/cpu/limits'.format(API_URL))
314
    print(indent_stat(stats.get_plugin('cpu').limits))
315
    print('')
316
317
318
class GlancesStdoutApiDoc(object):
319
    """This class manages the fields description display."""
320
321
    def __init__(self, config=None, args=None):
322
        # Init
323
        self.config = config
324
        self.args = args
325
326
    def end(self):
327
        pass
328
329
    def update(self, stats, duration=1):
330
        """Display issue"""
331
332
        # Display header
333
        print(APIDOC_HEADER)
334
335
        # Display API status
336
        print_api_status()
337
338
        # Display plugins list
339
        print_plugins_list(sorted(stats._plugins))
340
341
        # Loop over plugins
342
        for plugin in sorted(stats._plugins):
343
            stat = stats.get_plugin(plugin)
344
            print_plugin_stats(plugin, stat)
345
            print_plugin_description(plugin, stat)
346
347
            stat_export = stat.get_export()
348
            if stat_export is None or stat_export == [] or stat_export == {}:
349
                continue
350
            print_plugin_item_value(plugin, stat, stat_export)
351
352
        # Get all stats
353
        print_all()
354
355
        # Get top stats (only for plugins with a list of items)
356
        # Example for processlist plugin: get top 2 processes
357
        print_top(stats)
358
359
        # Fields description
360
        print_fields_info(stats)
361
362
        # History
363
        print_history(stats)
364
365
        # Limits
366
        print_limits(stats)
367
368
        # Return True to exit directly (no refresh)
369
        return True
370