Test Failed
Push — develop ( d7cf39...faa4bd )
by Nicolas
04:34 queued 10s
created

glances/stats.py (45 issues)

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
import missing from __future__ import absolute_import
Loading history...
23
import os
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
24
import sys
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
25
import threading
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
26
import traceback
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
27
28
from glances.logger import logger
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
29
from glances.globals import exports_path, plugins_path, sys_path
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
30
from glances.timer import Counter
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
31
32
33
class GlancesStats(object):
34
35
    """This class stores, updates and gives stats."""
36
37
    # Script header constant
38
    header = "glances_"
39
40
    def __init__(self, config=None, args=None):
41
        # Set the config instance
42
        self.config = config
43
44
        # Set the argument instance
45
        self.args = args
46
47
        # Load plugins and exports modules
48
        self.load_modules(self.args)
49
50
        # Load the limits (for plugins)
51
        # Not necessary anymore, configuration file is loaded on init
52
        # self.load_limits(self.config)
53
54
    def __getattr__(self, item):
55
        """Overwrite the getattr method in case of attribute is not found.
56
57
        The goal is to dynamically generate the following methods:
58
        - getPlugname(): return Plugname stat in JSON format
59
        - getViewsPlugname(): return views of the Plugname stat in JSON format
60
        """
61
        # Check if the attribute starts with 'get'
62
        if item.startswith('getViews'):
63
            # Get the plugin name
64
            plugname = item[len('getViews'):].lower()
65
            # Get the plugin instance
66
            plugin = self._plugins[plugname]
67
            if hasattr(plugin, 'get_json_views'):
68
                # The method get_views exist, return it
69
                return getattr(plugin, 'get_json_views')
70
            else:
71
                # The method get_views is not found for the plugin
72
                raise AttributeError(item)
73
        elif item.startswith('get'):
74
            # Get the plugin name
75
            plugname = item[len('get'):].lower()
76
            # Get the plugin instance
77
            plugin = self._plugins[plugname]
78
            if hasattr(plugin, 'get_stats'):
79
                # The method get_stats exist, return it
80
                return getattr(plugin, 'get_stats')
81
            else:
82
                # The method get_stats is not found for the plugin
83
                raise AttributeError(item)
84
        else:
85
            # Default behavior
86
            raise AttributeError(item)
87
88
    def load_modules(self, args):
89
        """Wrapper to load: plugins and export modules."""
90
91
        # Init the plugins dict
92
        # Active plugins dictionnary
93
        self._plugins = collections.defaultdict(dict)
94
        # Load the plugins
95
        self.load_plugins(args=args)
96
97
        # Init the export modules dict
98
        # Active exporters dictionnary
99
        self._exports = collections.defaultdict(dict)
100
        # All available exporters dictionnary
101
        self._exports_all = collections.defaultdict(dict)
102
        # Load the export modules
103
        self.load_exports(args=args)
104
105
        # Restoring system path
106
        sys.path = sys_path
107
108
    def _load_plugin(self, plugin_script, args=None, config=None):
109
        """Load the plugin (script), init it and add to the _plugin dict."""
110
        # The key is the plugin name
111
        # for example, the file glances_xxx.py
112
        # generate self._plugins_list["xxx"] = ...
113
        name = plugin_script[len(self.header):-3].lower()
114
115
        # Loaf the plugin class
116
        try:
117
            # Import the plugin
118
            plugin = __import__(plugin_script[:-3])
119
            # Init and add the plugin to the dictionary
120
            self._plugins[name] = plugin.Plugin(args=args, config=config)
121
        except Exception as e:
0 ignored issues
show
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
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...
122
            # If a plugin can not be loaded, display a critical message
123
            # on the console but do not crash
124
            logger.critical("Error while initializing the {} plugin ({})".format(name, e))
0 ignored issues
show
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...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
125
            logger.error(traceback.format_exc())
126
            # Disable the plugin
127
            if args is not None:
128
                setattr(args,
129
                        'disable_' + name,
130
                        False)
131
        else:
132
            # Set the disable_<name> to False by default
133
            if args is not None:
134
                setattr(args,
135
                        'disable_' + name,
136
                        getattr(args, 'disable_' + name, False))
137
138
    def load_plugins(self, args=None):
139
        """Load all plugins in the 'plugins' folder."""
140
        start_duration = Counter()
141
        for item in os.listdir(plugins_path):
142
            if (item.startswith(self.header) and
143
                    item.endswith(".py") and
0 ignored issues
show
Wrong continued indentation (remove 4 spaces).
Loading history...
144
                    item != (self.header + "plugin.py")):
0 ignored issues
show
Wrong continued indentation (remove 4 spaces).
Loading history...
145
                # Load the plugin
146
                start_duration.reset()
147
                self._load_plugin(os.path.basename(item),
148
                                  args=args, config=self.config)
149
                logger.debug("Plugin {} started in {} seconds".format(item,
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
150
                                                                      start_duration.get()))
0 ignored issues
show
This line is too long as per the coding-style (92/80).

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

Loading history...
151
152
        # Log plugins list
153
        logger.debug("Active plugins list: {}".format(self.getPluginsList()))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
154
155
    def load_exports(self, args=None):
156
        """Load all export modules in the 'exports' folder."""
157
        if args is None:
158
            return False
159
        header = "glances_"
160
        # Build the export module available list
161
        args_var = vars(locals()['args'])
0 ignored issues
show
The variable args_var seems to be unused.
Loading history...
162
        for item in os.listdir(exports_path):
163
            export_name = os.path.basename(item)[len(header):-3].lower()
164
            if (item.startswith(header) and
165
                    item.endswith(".py") and
0 ignored issues
show
Wrong continued indentation (remove 4 spaces).
Loading history...
166
                    item != (header + "export.py") and
0 ignored issues
show
Wrong continued indentation (remove 4 spaces).
Loading history...
167
                    item != (header + "history.py")):
0 ignored issues
show
Wrong continued indentation (remove 4 spaces).
Loading history...
168
                self._exports_all[export_name] = os.path.basename(item)[:-3]
169
                # Set the disable_<name> to False by default
170
                setattr(self.args,
171
                        'export_' + export_name,
172
                        getattr(self.args, 'export_' + export_name, False))
173
174
        # Aim is to check if the export module should be loaded
175
        for export_name in self._exports_all:
176
            if getattr(self.args, 'export_' + export_name, False):
177
                # Import the export module
178
                export_module = __import__(self._exports_all[export_name])
179
                # Add the export to the dictionary
180
                # The key is the module name
181
                # for example, the file glances_xxx.py
182
                # generate self._exports_list["xxx"] = ...
183
                self._exports[export_name] = export_module.Export(args=args,
184
                                                                  config=self.config)
0 ignored issues
show
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...
185
                self._exports_all[export_name] = self._exports[export_name]
186
187
        # Log plugins list
188
        logger.debug("Active exports modules list: {}".format(self.getExportsList()))
0 ignored issues
show
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...
Use formatting in logging functions and pass the parameters as arguments
Loading history...
189
        return True
190
191
    def getPluginsList(self, enable=True):
0 ignored issues
show
Coding Style Naming introduced by
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...
192
        """Return the plugins list.
193
194
        if enable is True, only return the active plugins (default)
195
        if enable is False, return all the plugins
196
197
        Return: list of plugin name
198
        """
199
        if enable:
0 ignored issues
show
Unnecessary "else" after "return"
Loading history...
200
            return [p for p in self._plugins if self._plugins[p].is_enable()]
201
        else:
202
            return [p for p in self._plugins]
203
204
    def getExportsList(self, enable=True):
0 ignored issues
show
Coding Style Naming introduced by
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...
205
        """Return the exports list.
206
207
        if enable is True, only return the active exporters (default)
208
        if enable is False, return all the exporters
209
210
        Return: list of export module name
211
        """
212
        if enable:
0 ignored issues
show
Unnecessary "else" after "return"
Loading history...
213
            return [e for e in self._exports]
214
        else:
215
            return [e for e in self._exports_all]
216
217
    def load_limits(self, config=None):
218
        """Load the stats limits (except the one in the exclude list)."""
219
        # For each plugins, call the load_limits method
220
        for p in self._plugins:
0 ignored issues
show
Coding Style Naming introduced by
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...
221
            self._plugins[p].load_limits(config)
222
223
    def update(self):
224
        """Wrapper method to update the stats."""
225
        # For standalone and server modes
226
        # For each plugins, call the update method
227
        for p in self._plugins:
0 ignored issues
show
Coding Style Naming introduced by
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...
228
            if self._plugins[p].is_disable():
229
                # If current plugin is disable
230
                # then continue to next plugin
231
                continue
232
            start_duration = Counter()
0 ignored issues
show
The variable start_duration seems to be unused.
Loading history...
233
            # Update the stats...
234
            self._plugins[p].update()
235
            # ... the history
236
            self._plugins[p].update_stats_history()
237
            # ... and the views
238
            self._plugins[p].update_views()
239
            # logger.debug("Plugin {} update duration: {} seconds".format(p,
240
            #                                                             start_duration.get()))
0 ignored issues
show
This line is too long as per the coding-style (96/80).

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

Loading history...
241
242
    def export(self, input_stats=None):
243
        """Export all the stats.
244
245
        Each export module is ran in a dedicated thread.
246
        """
247
        # threads = []
248
        input_stats = input_stats or {}
249
250
        for e in self._exports:
0 ignored issues
show
Coding Style Naming introduced by
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...
251
            logger.debug("Export stats using the %s module" % e)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
252
            thread = threading.Thread(target=self._exports[e].update,
253
                                      args=(input_stats,))
254
            # threads.append(thread)
255
            thread.start()
256
257
    def getAll(self):
0 ignored issues
show
Coding Style Naming introduced by
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...
258
        """Return all the stats (list)."""
259
        return [self._plugins[p].get_raw() for p in self._plugins]
260
261
    def getAllAsDict(self):
0 ignored issues
show
Coding Style Naming introduced by
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...
262
        """Return all the stats (dict)."""
263
        return {p: self._plugins[p].get_raw() for p in self._plugins}
264
265
    def getAllExports(self, plugin_list=None):
0 ignored issues
show
Coding Style Naming introduced by
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...
266
        """
267
        Return all the stats to be exported (list).
268
        Default behavor is to export all the stat
269
        if plugin_list is provided, only export stats of given plugin (list)
270
        """
271
        if plugin_list is None:
272
            # All plugins should be exported
273
            plugin_list = self._plugins
274
        return [self._plugins[p].get_export() for p in self._plugins]
275
276
    def getAllExportsAsDict(self, plugin_list=None):
0 ignored issues
show
Coding Style Naming introduced by
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...
277
        """
278
        Return all the stats to be exported (list).
279
        Default behavor is to export all the stat
280
        if plugin_list is provided, only export stats of given plugin (list)
281
        """
282
        if plugin_list is None:
283
            # All plugins should be exported
284
            plugin_list = self._plugins
285
        return {p: self._plugins[p].get_export() for p in plugin_list}
286
287
    def getAllLimits(self):
0 ignored issues
show
Coding Style Naming introduced by
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...
288
        """Return the plugins limits list."""
289
        return [self._plugins[p].limits for p in self._plugins]
290
291
    def getAllLimitsAsDict(self, plugin_list=None):
0 ignored issues
show
Coding Style Naming introduced by
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...
292
        """
293
        Return all the stats limits (dict).
294
        Default behavor is to export all the limits
295
        if plugin_list is provided, only export limits of given plugin (list)
296
        """
297
        if plugin_list is None:
298
            # All plugins should be exported
299
            plugin_list = self._plugins
300
        return {p: self._plugins[p].limits for p in plugin_list}
301
302
    def getAllViews(self):
0 ignored issues
show
Coding Style Naming introduced by
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...
303
        """Return the plugins views."""
304
        return [self._plugins[p].get_views() for p in self._plugins]
305
306
    def getAllViewsAsDict(self):
0 ignored issues
show
Coding Style Naming introduced by
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...
307
        """Return all the stats views (dict)."""
308
        return {p: self._plugins[p].get_views() for p in self._plugins}
309
310
    def get_plugin_list(self):
311
        """Return the plugin list."""
312
        return self._plugins
313
314
    def get_plugin(self, plugin_name):
315
        """Return the plugin name."""
316
        if plugin_name in self._plugins:
0 ignored issues
show
Unnecessary "else" after "return"
Loading history...
317
            return self._plugins[plugin_name]
318
        else:
319
            return None
320
321
    def end(self):
322
        """End of the Glances stats."""
323
        # Close export modules
324
        for e in self._exports:
0 ignored issues
show
Coding Style Naming introduced by
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...
325
            self._exports[e].exit()
326
        # Close plugins
327
        for p in self._plugins:
0 ignored issues
show
Coding Style Naming introduced by
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...
328
            self._plugins[p].exit()
329