Test Failed
Push — develop ( 504450...f0e8ef )
by Nicolas
03:16
created

glances.stats.GlancesStats._load_plugin()   A

Complexity

Conditions 5

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 16
nop 4
dl 0
loc 29
rs 9.1333
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 Nicolargo <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""The stats manager."""
21
22
import collections
0 ignored issues
show
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
23
import os
0 ignored issues
show
introduced by Nicolas Hennion
import missing from __future__ import absolute_import
Loading history...
24
import sys
0 ignored issues
show
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
25
import threading
0 ignored issues
show
introduced by Nicolas Hennion
import missing from __future__ import absolute_import
Loading history...
26
import traceback
0 ignored issues
show
introduced by Nicolas Hennion
import missing from __future__ import absolute_import
Loading history...
27
28
from glances.globals import exports_path, plugins_path, sys_path
0 ignored issues
show
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
29
from glances.logger import logger
0 ignored issues
show
introduced by Alessio Sergi
import missing from __future__ import absolute_import
Loading history...
30
31
32
class GlancesStats(object):
33
34
    """This class stores, updates and gives stats."""
35
36
    # Script header constant
37
    header = "glances_"
38
39
    def __init__(self, config=None, args=None):
40
        # Set the config instance
41
        self.config = config
42
43
        # Set the argument instance
44
        self.args = args
45
46
        # Load plugins and exports modules
47
        self.load_modules(self.args)
48
49
        # Load the limits (for plugins)
50
        # Not necessary anymore, configuration file is loaded on init
51
        # self.load_limits(self.config)
52
53
    def __getattr__(self, item):
54
        """Overwrite the getattr method in case of attribute is not found.
55
56
        The goal is to dynamically generate the following methods:
57
        - getPlugname(): return Plugname stat in JSON format
58
        - getViewsPlugname(): return views of the Plugname stat in JSON format
59
        """
60
        # Check if the attribute starts with 'get'
61
        if item.startswith('getViews'):
62
            # Get the plugin name
63
            plugname = item[len('getViews'):].lower()
64
            # Get the plugin instance
65
            plugin = self._plugins[plugname]
66
            if hasattr(plugin, 'get_json_views'):
67
                # The method get_views exist, return it
68
                return getattr(plugin, 'get_json_views')
69
            else:
70
                # The method get_views is not found for the plugin
71
                raise AttributeError(item)
72
        elif item.startswith('get'):
73
            # Get the plugin name
74
            plugname = item[len('get'):].lower()
75
            # Get the plugin instance
76
            plugin = self._plugins[plugname]
77
            if hasattr(plugin, 'get_stats'):
78
                # The method get_stats exist, return it
79
                return getattr(plugin, 'get_stats')
80
            else:
81
                # The method get_stats is not found for the plugin
82
                raise AttributeError(item)
83
        else:
84
            # Default behavior
85
            raise AttributeError(item)
86
87
    def load_modules(self, args):
88
        """Wrapper to load: plugins and export modules."""
89
90
        # Init the plugins dict
91
        # Active plugins dictionnary
92
        self._plugins = collections.defaultdict(dict)
93
        # Load the plugins
94
        self.load_plugins(args=args)
95
96
        # Init the export modules dict
97
        # Active exporters dictionnary
98
        self._exports = collections.defaultdict(dict)
99
        # All available exporters dictionnary
100
        self._exports_all = collections.defaultdict(dict)
101
        # Load the export modules
102
        self.load_exports(args=args)
103
104
        # Restoring system path
105
        sys.path = sys_path
106
107
    def _load_plugin(self, plugin_script, args=None, config=None):
108
        """Load the plugin (script), init it and add to the _plugin dict."""
109
        # The key is the plugin name
110
        # for example, the file glances_xxx.py
111
        # generate self._plugins_list["xxx"] = ...
112
        name = plugin_script[len(self.header):-3].lower()
113
114
        # Loaf the plugin class
115
        try:
116
            # Import the plugin
117
            plugin = __import__(plugin_script[:-3])
118
            # Init and add the plugin to the dictionary
119
            self._plugins[name] = plugin.Plugin(args=args, config=config)
120
        except Exception as e:
0 ignored issues
show
Best Practice introduced by Nicolas Hennion
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by Nicolas Hennion
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
121
            # If a plugin can not be loaded, display a critical message
122
            # on the console but do not crash
123
            logger.critical("Error while initializing the {} plugin ({})".format(name, e))
0 ignored issues
show
Coding Style introduced by Drew Bonasera
This line is too long as per the coding-style (90/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by Drew Bonasera
Use formatting in logging functions and pass the parameters as arguments
Loading history...
124
            logger.error(traceback.format_exc())
125
            # Disable the plugin
126
            if args is not None:
127
                setattr(args,
128
                        'disable_' + name,
129
                        False)
130
        else:
131
            # Set the disable_<name> to False by default
132
            if args is not None:
133
                setattr(args,
134
                        'disable_' + name,
135
                        getattr(args, 'disable_' + name, False))
136
137
    def load_plugins(self, args=None):
138
        """Load all plugins in the 'plugins' folder."""
139
        for item in os.listdir(plugins_path):
140
            if (item.startswith(self.header) and
141
                    item.endswith(".py") and
0 ignored issues
show
Coding Style introduced by Alessio Sergi
Wrong continued indentation (remove 4 spaces).
Loading history...
142
                    item != (self.header + "plugin.py")):
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
Wrong continued indentation (remove 4 spaces).
Loading history...
143
                # Load the plugin
144
                self._load_plugin(os.path.basename(item),
145
                                  args=args, config=self.config)
146
147
        # Log plugins list
148
        logger.debug("Active plugins list: {}".format(self.getPluginsList()))
0 ignored issues
show
introduced by Nicolas Hennion
Use formatting in logging functions and pass the parameters as arguments
Loading history...
149
150
    def load_exports(self, args=None):
151
        """Load all export modules in the 'exports' folder."""
152
        if args is None:
153
            return False
154
        header = "glances_"
155
        # Build the export module available list
156
        args_var = vars(locals()['args'])
0 ignored issues
show
Unused Code introduced by Nicolas Hennion
The variable args_var seems to be unused.
Loading history...
157
        for item in os.listdir(exports_path):
158
            export_name = os.path.basename(item)[len(header):-3].lower()
159
            if (item.startswith(header) and
160
                    item.endswith(".py") and
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
Wrong continued indentation (remove 4 spaces).
Loading history...
161
                    item != (header + "export.py") and
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
Wrong continued indentation (remove 4 spaces).
Loading history...
162
                    item != (header + "history.py")):
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
Wrong continued indentation (remove 4 spaces).
Loading history...
163
                self._exports_all[export_name] = os.path.basename(item)[:-3]
164
                # Set the disable_<name> to False by default
165
                setattr(self.args,
166
                        'export_' + export_name,
167
                        getattr(self.args, 'export_' + export_name, False))
168
169
        # Aim is to check if the export module should be loaded
170
        for export_name in self._exports_all:
171
            if getattr(self.args, 'export_' + export_name, False):
172
                # Import the export module
173
                export_module = __import__(self._exports_all[export_name])
174
                # Add the export to the dictionary
175
                # The key is the module name
176
                # for example, the file glances_xxx.py
177
                # generate self._exports_list["xxx"] = ...
178
                self._exports[export_name] = export_module.Export(args=args,
179
                                                                  config=self.config)
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
This line is too long as per the coding-style (85/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
180
                self._exports_all[export_name] = self._exports[export_name]
181
182
        # Log plugins list
183
        logger.debug("Active exports modules list: {}".format(self.getExportsList()))
0 ignored issues
show
Coding Style introduced by Nicolas Hennion
This line is too long as per the coding-style (85/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by Nicolas Hennion
Use formatting in logging functions and pass the parameters as arguments
Loading history...
184
        return True
185
186
    def getPluginsList(self, enable=True):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getPluginsList does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
187
        """Return the plugins list.
188
189
        if enable is True, only return the active plugins (default)
190
        if enable is False, return all the plugins
191
192
        Return: list of plugin name
193
        """
194
        if enable:
0 ignored issues
show
unused-code introduced by Nicolas Hennion
Unnecessary "else" after "return"
Loading history...
195
            return [p for p in self._plugins if self._plugins[p].is_enable()]
196
        else:
197
            return [p for p in self._plugins]
198
199
    def getExportsList(self, enable=True):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getExportsList does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
200
        """Return the exports list.
201
202
        if enable is True, only return the active exporters (default)
203
        if enable is False, return all the exporters
204
205
        Return: list of export module name
206
        """
207
        if enable:
0 ignored issues
show
unused-code introduced by Nicolas Hennion
Unnecessary "else" after "return"
Loading history...
208
            return [e for e in self._exports]
209
        else:
210
            return [e for e in self._exports_all]
211
212
    def load_limits(self, config=None):
213
        """Load the stats limits (except the one in the exclude list)."""
214
        # For each plugins, call the load_limits method
215
        for p in self._plugins:
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name p does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
216
            self._plugins[p].load_limits(config)
217
218
    def update(self):
219
        """Wrapper method to update the stats."""
220
        # For standalone and server modes
221
        # For each plugins, call the update method
222
        for p in self._plugins:
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name p does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
223
            if self._plugins[p].is_disable():
224
                # If current plugin is disable
225
                # then continue to next plugin
226
                continue
227
            # Update the stats...
228
            self._plugins[p].update()
229
            # ... the history
230
            self._plugins[p].update_stats_history()
231
            # ... and the views
232
            self._plugins[p].update_views()
233
234
    def export(self, input_stats=None):
235
        """Export all the stats.
236
237
        Each export module is ran in a dedicated thread.
238
        """
239
        # threads = []
240
        input_stats = input_stats or {}
241
242
        for e in self._exports:
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
243
            logger.debug("Export stats using the %s module" % e)
0 ignored issues
show
Coding Style Best Practice introduced by Nicolas Hennion
Specify string format arguments as logging function parameters
Loading history...
244
            thread = threading.Thread(target=self._exports[e].update,
245
                                      args=(input_stats,))
246
            # threads.append(thread)
247
            thread.start()
248
249
    def getAll(self):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAll does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
250
        """Return all the stats (list)."""
251
        return [self._plugins[p].get_raw() for p in self._plugins]
252
253
    def getAllAsDict(self):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAllAsDict does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
254
        """Return all the stats (dict)."""
255
        return {p: self._plugins[p].get_raw() for p in self._plugins}
256
257
    def getAllExports(self, plugin_list=None):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAllExports does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
258
        """
259
        Return all the stats to be exported (list).
260
        Default behavor is to export all the stat
261
        if plugin_list is provided, only export stats of given plugin (list)
262
        """
263
        if plugin_list is None:
264
            # All plugins should be exported
265
            plugin_list = self._plugins
266
        return [self._plugins[p].get_export() for p in self._plugins]
267
268
    def getAllExportsAsDict(self, plugin_list=None):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAllExportsAsDict does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
269
        """
270
        Return all the stats to be exported (list).
271
        Default behavor is to export all the stat
272
        if plugin_list is provided, only export stats of given plugin (list)
273
        """
274
        if plugin_list is None:
275
            # All plugins should be exported
276
            plugin_list = self._plugins
277
        return {p: self._plugins[p].get_export() for p in plugin_list}
278
279
    def getAllLimits(self):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAllLimits does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
280
        """Return the plugins limits list."""
281
        return [self._plugins[p].limits for p in self._plugins]
282
283
    def getAllLimitsAsDict(self, plugin_list=None):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAllLimitsAsDict does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
284
        """
285
        Return all the stats limits (dict).
286
        Default behavor is to export all the limits
287
        if plugin_list is provided, only export limits of given plugin (list)
288
        """
289
        if plugin_list is None:
290
            # All plugins should be exported
291
            plugin_list = self._plugins
292
        return {p: self._plugins[p].limits for p in plugin_list}
293
294
    def getAllViews(self):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAllViews does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
295
        """Return the plugins views."""
296
        return [self._plugins[p].get_views() for p in self._plugins]
297
298
    def getAllViewsAsDict(self):
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name getAllViewsAsDict does not conform to the method naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
299
        """Return all the stats views (dict)."""
300
        return {p: self._plugins[p].get_views() for p in self._plugins}
301
302
    def get_plugin_list(self):
303
        """Return the plugin list."""
304
        return self._plugins
305
306
    def get_plugin(self, plugin_name):
307
        """Return the plugin name."""
308
        if plugin_name in self._plugins:
0 ignored issues
show
unused-code introduced by Alessio Sergi
Unnecessary "else" after "return"
Loading history...
309
            return self._plugins[plugin_name]
310
        else:
311
            return None
312
313
    def end(self):
314
        """End of the Glances stats."""
315
        # Close export modules
316
        for e in self._exports:
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
317
            self._exports[e].exit()
318
        # Close plugins
319
        for p in self._plugins:
0 ignored issues
show
Coding Style Naming introduced by Nicolas Hennion
The name p does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
320
            self._plugins[p].exit()
321