glances.stats   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 145
dl 0
loc 320
rs 5.04
c 0
b 0
f 0
wmc 57

22 Methods

Rating   Name   Duplication   Size   Complexity  
A GlancesStats.__init__() 0 10 1
A GlancesStats.__getattr__() 0 33 5
A GlancesStats.load_modules() 0 19 1
A GlancesStats.export() 0 18 3
B GlancesStats._load_plugin() 0 30 6
C GlancesStats.load_exports() 0 34 9
A GlancesStats.update() 0 15 3
A GlancesStats.getAllAsDict() 0 3 1
A GlancesStats.load_limits() 0 5 2
A GlancesStats.getAllExports() 0 10 2
A GlancesStats.getExportsList() 0 12 2
A GlancesStats.load_plugins() 0 12 5
A GlancesStats.getPluginsList() 0 12 2
A GlancesStats.getAllExportsAsDict() 0 10 2
A GlancesStats.getAll() 0 3 1
A GlancesStats.getAllViews() 0 3 1
A GlancesStats.getAllViewsAsDict() 0 3 1
A GlancesStats.get_plugin() 0 6 2
A GlancesStats.end() 0 8 3
A GlancesStats.getAllLimits() 0 10 2
A GlancesStats.getAllLimitsAsDict() 0 10 2
A GlancesStats.get_plugin_list() 0 3 1

How to fix   Complexity   

Complexity

Complex classes like glances.stats 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: 2022 Nicolas Hennion <[email protected]>
6
#
7
# SPDX-License-Identifier: LGPL-3.0-only
8
#
9
10
"""The stats manager."""
11
12
import collections
13
import os
14
import sys
15
import threading
16
import traceback
17
18
from glances.logger import logger
19
from glances.globals import exports_path, plugins_path, sys_path
20
from glances.timer import Counter
21
22
23
class GlancesStats(object):
24
25
    """This class stores, updates and gives stats."""
26
27
    # Script header constant
28
    header = "glances_"
29
30
    def __init__(self, config=None, args=None):
31
        # Set the config instance
32
        self.config = config
33
34
        # Set the argument instance
35
        self.args = args
36
37
        # Load plugins and exports modules
38
        self.first_export = True
39
        self.load_modules(self.args)
40
41
    def __getattr__(self, item):
42
        """Overwrite the getattr method in case of attribute is not found.
43
44
        The goal is to dynamically generate the following methods:
45
        - getPlugname(): return Plugname stat in JSON format
46
        - getViewsPlugname(): return views of the Plugname stat in JSON format
47
        """
48
        # Check if the attribute starts with 'get'
49
        if item.startswith('getViews'):
50
            # Get the plugin name
51
            plugname = item[len('getViews') :].lower()
52
            # Get the plugin instance
53
            plugin = self._plugins[plugname]
54
            if hasattr(plugin, 'get_json_views'):
55
                # The method get_views exist, return it
56
                return getattr(plugin, 'get_json_views')
57
            else:
58
                # The method get_views is not found for the plugin
59
                raise AttributeError(item)
60
        elif item.startswith('get'):
61
            # Get the plugin name
62
            plugname = item[len('get') :].lower()
63
            # Get the plugin instance
64
            plugin = self._plugins[plugname]
65
            if hasattr(plugin, 'get_stats'):
66
                # The method get_stats exist, return it
67
                return getattr(plugin, 'get_stats')
68
            else:
69
                # The method get_stats is not found for the plugin
70
                raise AttributeError(item)
71
        else:
72
            # Default behavior
73
            raise AttributeError(item)
74
75
    def load_modules(self, args):
76
        """Wrapper to load: plugins and export modules."""
77
78
        # Init the plugins dict
79
        # Active plugins dictionary
80
        self._plugins = collections.defaultdict(dict)
81
        # Load the plugins
82
        self.load_plugins(args=args)
83
84
        # Init the export modules dict
85
        # Active exporters dictionary
86
        self._exports = collections.defaultdict(dict)
87
        # All available exporters dictionary
88
        self._exports_all = collections.defaultdict(dict)
89
        # Load the export modules
90
        self.load_exports(args=args)
91
92
        # Restoring system path
93
        sys.path = sys_path
94
95
    def _load_plugin(self, plugin_script, args=None, config=None):
96
        """Load the plugin (script), init it and add to the _plugin dict."""
97
        # The key is the plugin name
98
        # for example, the file glances_xxx.py
99
        # generate self._plugins_list["xxx"] = ...
100
        name = plugin_script[len(self.header) : -3].lower()
101
102
        # Load the plugin class
103
        try:
104
            # Import the plugin
105
            plugin = __import__(plugin_script[:-3])
106
            # Init and add the plugin to the dictionary
107
            self._plugins[name] = plugin.Plugin(args=args, config=config)
108
        except Exception as e:
109
            # If a plugin can not be loaded, display a critical message
110
            # on the console but do not crash
111
            logger.critical("Error while initializing the {} plugin ({})".format(name, e))
112
            logger.error(traceback.format_exc())
113
            # An error occurred, disable the plugin
114
            if args is not None:
115
                setattr(args, 'disable_' + name, False)
116
        else:
117
            # Manage the default status of the plugin (enable or disable)
118
            if args is not None:
119
                # If the all key is set in the disable_plugin option then look in the enable_plugin option
120
                if getattr(args, 'disable_all', False):
121
                    logger.debug('%s => %s', name, getattr(args, 'enable_' + name, False))
122
                    setattr(args, 'disable_' + name, not getattr(args, 'enable_' + name, False))
123
                else:
124
                    setattr(args, 'disable_' + name, getattr(args, 'disable_' + name, False))
125
126
    def load_plugins(self, args=None):
127
        """Load all plugins in the 'plugins' folder."""
128
        start_duration = Counter()
129
        for item in os.listdir(plugins_path):
130
            if item.startswith(self.header) and item.endswith(".py") and item != (self.header + "plugin.py"):
131
                # Load the plugin
132
                start_duration.reset()
133
                self._load_plugin(os.path.basename(item), args=args, config=self.config)
134
                logger.debug("Plugin {} started in {} seconds".format(item, start_duration.get()))
135
136
        # Log plugins list
137
        logger.debug("Active plugins list: {}".format(self.getPluginsList()))
138
139
    def load_exports(self, args=None):
140
        """Load all export modules in the 'exports' folder."""
141
        if args is None:
142
            return False
143
        header = "glances_"
144
        # Build the export module available list
145
        args_var = vars(locals()['args'])
146
        for item in os.listdir(exports_path):
147
            export_name = os.path.basename(item)[len(header) : -3].lower()
148
            if (
149
                item.startswith(header)
150
                and item.endswith(".py")
151
                and item != (header + "export.py")
152
                and item != (header + "history.py")
153
            ):
154
                self._exports_all[export_name] = os.path.basename(item)[:-3]
155
                # Set the disable_<name> to False by default
156
                setattr(self.args, 'export_' + export_name, getattr(self.args, 'export_' + export_name, False))
157
158
        # Aim is to check if the export module should be loaded
159
        for export_name in self._exports_all:
160
            if getattr(self.args, 'export_' + export_name, False):
161
                # Import the export module
162
                export_module = __import__(self._exports_all[export_name])
163
                # Add the export to the dictionary
164
                # The key is the module name
165
                # for example, the file glances_xxx.py
166
                # generate self._exports_list["xxx"] = ...
167
                self._exports[export_name] = export_module.Export(args=args, config=self.config)
168
                self._exports_all[export_name] = self._exports[export_name]
169
170
        # Log plugins list
171
        logger.debug("Active exports modules list: {}".format(self.getExportsList()))
172
        return True
173
174
    def getPluginsList(self, enable=True):
175
        """Return the plugins list.
176
177
        if enable is True, only return the active plugins (default)
178
        if enable is False, return all the plugins
179
180
        Return: list of plugin name
181
        """
182
        if enable:
183
            return [p for p in self._plugins if self._plugins[p].is_enabled()]
184
        else:
185
            return [p for p in self._plugins]
186
187
    def getExportsList(self, enable=True):
188
        """Return the exports list.
189
190
        if enable is True, only return the active exporters (default)
191
        if enable is False, return all the exporters
192
193
        :return: list of export module names
194
        """
195
        if enable:
196
            return [e for e in self._exports]
197
        else:
198
            return [e for e in self._exports_all]
199
200
    def load_limits(self, config=None):
201
        """Load the stats limits (except the one in the exclude list)."""
202
        # For each plugins, call the load_limits method
203
        for p in self._plugins:
204
            self._plugins[p].load_limits(config)
205
206
    def update(self):
207
        """Wrapper method to update the stats."""
208
        # For standalone and server modes
209
        # For each plugins, call the update method
210
        for p in self._plugins:
211
            if self._plugins[p].is_disabled():
212
                # If current plugin is disable
213
                # then continue to next plugin
214
                continue
215
            # Update the stats...
216
            self._plugins[p].update()
217
            # ... the history
218
            self._plugins[p].update_stats_history()
219
            # ... and the views
220
            self._plugins[p].update_views()
221
222
    def export(self, input_stats=None):
223
        """Export all the stats.
224
225
        Each export module is ran in a dedicated thread.
226
        """
227
        if self.first_export:
228
            logger.debug("Do not export stats during the first iteration because some information are missing")
229
            self.first_export = False
230
            return False
231
232
        input_stats = input_stats or {}
233
234
        for e in self._exports:
235
            logger.debug("Export stats using the %s module" % e)
236
            thread = threading.Thread(target=self._exports[e].update, args=(input_stats,))
237
            thread.start()
238
239
        return True
240
241
    def getAll(self):
242
        """Return all the stats (list)."""
243
        return [self._plugins[p].get_raw() for p in self._plugins]
244
245
    def getAllAsDict(self):
246
        """Return all the stats (dict)."""
247
        return {p: self._plugins[p].get_raw() for p in self._plugins}
248
249
    def getAllExports(self, plugin_list=None):
250
        """Return all the stats to be exported (list).
251
252
        Default behavior is to export all the stat
253
        if plugin_list is provided, only export stats of given plugin (list)
254
        """
255
        if plugin_list is None:
256
            # All enabled plugins should be exported
257
            plugin_list = self.getPluginsList()
258
        return [self._plugins[p].get_export() for p in self._plugins]
259
260
    def getAllExportsAsDict(self, plugin_list=None):
261
        """Return all the stats to be exported (list).
262
263
        Default behavior is to export all the stat
264
        if plugin_list is provided, only export stats of given plugin (list)
265
        """
266
        if plugin_list is None:
267
            # All enabled plugins should be exported
268
            plugin_list = self.getPluginsList()
269
        return {p: self._plugins[p].get_export() for p in plugin_list}
270
271
    def getAllLimits(self, plugin_list=None):
272
        """Return the plugins limits list.
273
274
        Default behavior is to export all the limits
275
        if plugin_list is provided, only export limits of given plugin (list)
276
        """
277
        if plugin_list is None:
278
            # All enabled plugins should be exported
279
            plugin_list = self.getPluginsList()
280
        return [self._plugins[p].limits for p in plugin_list]
281
282
    def getAllLimitsAsDict(self, plugin_list=None):
283
        """Return all the stats limits (dict).
284
285
        Default behavior is to export all the limits
286
        if plugin_list is provided, only export limits of given plugin (list)
287
        """
288
        if plugin_list is None:
289
            # All enabled plugins should be exported
290
            plugin_list = self.getPluginsList()
291
        return {p: self._plugins[p].limits for p in plugin_list}
292
293
    def getAllViews(self):
294
        """Return the plugins views."""
295
        return [self._plugins[p].get_views() for p in self._plugins]
296
297
    def getAllViewsAsDict(self):
298
        """Return all the stats views (dict)."""
299
        return {p: self._plugins[p].get_views() for p in self._plugins}
300
301
    def get_plugin_list(self):
302
        """Return the plugin list."""
303
        return self._plugins
304
305
    def get_plugin(self, plugin_name):
306
        """Return the plugin name."""
307
        if plugin_name in self._plugins:
308
            return self._plugins[plugin_name]
309
        else:
310
            return None
311
312
    def end(self):
313
        """End of the Glances stats."""
314
        # Close export modules
315
        for e in self._exports:
316
            self._exports[e].exit()
317
        # Close plugins
318
        for p in self._plugins:
319
            self._plugins[p].exit()
320