stats.py 12.0 KB
Newer Older
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2021 Nicolargo <nicolas@nicolargo.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

A
PEP 257  
Alessio Sergi 已提交
20 21
"""The stats manager."""

A
Alessio Sergi 已提交
22
import collections
23
import os
24
import sys
N
Nicolargo 已提交
25
import threading
26
import traceback
27

28
from glances.logger import logger
29 30
from glances.globals import exports_path, plugins_path, sys_path
from glances.timer import Counter
A
Alessio Sergi 已提交
31

N
Nicolas Hennion 已提交
32

33
class GlancesStats(object):
34

A
PEP 257  
Alessio Sergi 已提交
35
    """This class stores, updates and gives stats."""
36

37 38 39
    # Script header constant
    header = "glances_"

40
    def __init__(self, config=None, args=None):
41 42 43
        # Set the config instance
        self.config = config

N
nicolargo 已提交
44 45 46 47
        # Set the argument instance
        self.args = args

        # Load plugins and exports modules
48
        self.first_export = True
N
nicolargo 已提交
49
        self.load_modules(self.args)
50

51
    def __getattr__(self, item):
A
PEP 257  
Alessio Sergi 已提交
52 53
        """Overwrite the getattr method in case of attribute is not found.

A
Alessio Sergi 已提交
54
        The goal is to dynamically generate the following methods:
55
        - getPlugname(): return Plugname stat in JSON format
56
        - getViewsPlugname(): return views of the Plugname stat in JSON format
57 58
        """
        # Check if the attribute starts with 'get'
59 60 61 62 63 64 65 66 67 68 69 70
        if item.startswith('getViews'):
            # Get the plugin name
            plugname = item[len('getViews'):].lower()
            # Get the plugin instance
            plugin = self._plugins[plugname]
            if hasattr(plugin, 'get_json_views'):
                # The method get_views exist, return it
                return getattr(plugin, 'get_json_views')
            else:
                # The method get_views is not found for the plugin
                raise AttributeError(item)
        elif item.startswith('get'):
71
            # Get the plugin name
72
            plugname = item[len('get'):].lower()
73 74
            # Get the plugin instance
            plugin = self._plugins[plugname]
75
            if hasattr(plugin, 'get_stats'):
76 77 78 79 80 81 82 83 84
                # The method get_stats exist, return it
                return getattr(plugin, 'get_stats')
            else:
                # The method get_stats is not found for the plugin
                raise AttributeError(item)
        else:
            # Default behavior
            raise AttributeError(item)

N
nicolargo 已提交
85 86 87
    def load_modules(self, args):
        """Wrapper to load: plugins and export modules."""

88
        # Init the plugins dict
N
nicolargo 已提交
89
        # Active plugins dictionnary
90 91 92 93 94
        self._plugins = collections.defaultdict(dict)
        # Load the plugins
        self.load_plugins(args=args)

        # Init the export modules dict
N
nicolargo 已提交
95
        # Active exporters dictionnary
96
        self._exports = collections.defaultdict(dict)
N
nicolargo 已提交
97 98
        # All available exporters dictionnary
        self._exports_all = collections.defaultdict(dict)
99 100 101 102 103 104
        # Load the export modules
        self.load_exports(args=args)

        # Restoring system path
        sys.path = sys_path

105
    def _load_plugin(self, plugin_script, args=None, config=None):
N
nicolargo 已提交
106
        """Load the plugin (script), init it and add to the _plugin dict."""
107 108 109 110
        # The key is the plugin name
        # for example, the file glances_xxx.py
        # generate self._plugins_list["xxx"] = ...
        name = plugin_script[len(self.header):-3].lower()
111 112

        # Loaf the plugin class
113 114 115 116
        try:
            # Import the plugin
            plugin = __import__(plugin_script[:-3])
            # Init and add the plugin to the dictionary
117
            self._plugins[name] = plugin.Plugin(args=args, config=config)
118
        except Exception as e:
119
            # If a plugin can not be loaded, display a critical message
120
            # on the console but do not crash
D
Drew Bonasera 已提交
121
            logger.critical("Error while initializing the {} plugin ({})".format(name, e))
122
            logger.error(traceback.format_exc())
123 124 125 126 127 128 129 130 131 132 133
            # Disable the plugin
            if args is not None:
                setattr(args,
                        'disable_' + name,
                        False)
        else:
            # Set the disable_<name> to False by default
            if args is not None:
                setattr(args,
                        'disable_' + name,
                        getattr(args, 'disable_' + name, False))
134

135
    def load_plugins(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
136
        """Load all plugins in the 'plugins' folder."""
137
        start_duration = Counter()
A
Alessio Sergi 已提交
138
        for item in os.listdir(plugins_path):
139
            if (item.startswith(self.header) and
A
Alessio Sergi 已提交
140
                    item.endswith(".py") and
141 142
                    item != (self.header + "plugin.py")):
                # Load the plugin
143
                start_duration.reset()
144 145
                self._load_plugin(os.path.basename(item),
                                  args=args, config=self.config)
146 147
                logger.debug("Plugin {} started in {} seconds".format(item,
                                                                      start_duration.get()))
148

N
Nicolas Hennion 已提交
149
        # Log plugins list
N
nicolargo 已提交
150
        logger.debug("Active plugins list: {}".format(self.getPluginsList()))
151

152
    def load_exports(self, args=None):
153
        """Load all export modules in the 'exports' folder."""
154 155
        if args is None:
            return False
156
        header = "glances_"
N
nicolargo 已提交
157
        # Build the export module available list
158 159 160 161 162 163
        args_var = vars(locals()['args'])
        for item in os.listdir(exports_path):
            export_name = os.path.basename(item)[len(header):-3].lower()
            if (item.startswith(header) and
                    item.endswith(".py") and
                    item != (header + "export.py") and
N
nicolargo 已提交
164
                    item != (header + "history.py")):
165 166 167 168 169
                self._exports_all[export_name] = os.path.basename(item)[:-3]
                # Set the disable_<name> to False by default
                setattr(self.args,
                        'export_' + export_name,
                        getattr(self.args, 'export_' + export_name, False))
N
nicolargo 已提交
170 171 172

        # Aim is to check if the export module should be loaded
        for export_name in self._exports_all:
173
            if getattr(self.args, 'export_' + export_name, False):
174
                # Import the export module
175
                export_module = __import__(self._exports_all[export_name])
176 177 178 179
                # Add the export to the dictionary
                # The key is the module name
                # for example, the file glances_xxx.py
                # generate self._exports_list["xxx"] = ...
N
nicolargo 已提交
180 181 182
                self._exports[export_name] = export_module.Export(args=args,
                                                                  config=self.config)
                self._exports_all[export_name] = self._exports[export_name]
183

184
        # Log plugins list
N
nicolargo 已提交
185
        logger.debug("Active exports modules list: {}".format(self.getExportsList()))
186
        return True
187

N
nicolargo 已提交
188 189 190 191 192 193 194 195
    def getPluginsList(self, enable=True):
        """Return the plugins list.

        if enable is True, only return the active plugins (default)
        if enable is False, return all the plugins

        Return: list of plugin name
        """
N
nicolargo 已提交
196 197 198
        if enable:
            return [p for p in self._plugins if self._plugins[p].is_enable()]
        else:
199
            return [p for p in self._plugins]
200

N
nicolargo 已提交
201
    def getExportsList(self, enable=True):
202
        """Return the exports list.
N
nicolargo 已提交
203 204 205 206 207 208 209 210 211 212

        if enable is True, only return the active exporters (default)
        if enable is False, return all the exporters

        Return: list of export module name
        """
        if enable:
            return [e for e in self._exports]
        else:
            return [e for e in self._exports_all]
213

214
    def load_limits(self, config=None):
215
        """Load the stats limits (except the one in the exclude list)."""
N
nicolargo 已提交
216
        # For each plugins, call the load_limits method
217 218 219
        for p in self._plugins:
            self._plugins[p].load_limits(config)

N
Nicolargo 已提交
220
    def update(self):
A
PEP 257  
Alessio Sergi 已提交
221
        """Wrapper method to update the stats."""
N
Nicolargo 已提交
222 223 224
        # For standalone and server modes
        # For each plugins, call the update method
        for p in self._plugins:
N
nicolargo 已提交
225 226 227 228
            if self._plugins[p].is_disable():
                # If current plugin is disable
                # then continue to next plugin
                continue
229
            # Update the stats...
N
Nicolargo 已提交
230
            self._plugins[p].update()
231 232 233 234
            # ... the history
            self._plugins[p].update_stats_history()
            # ... and the views
            self._plugins[p].update_views()
235

236
    def export(self, input_stats=None):
N
Nicolargo 已提交
237
        """Export all the stats.
A
PEP 257  
Alessio Sergi 已提交
238 239 240

        Each export module is ran in a dedicated thread.
        """
241
        if self.first_export:
242
            logger.debug("Do not export stats during the first iteration because some information are missing")
243 244 245
            self.first_export = False
            return False

246 247
        input_stats = input_stats or {}

248
        for e in self._exports:
N
Nicolargo 已提交
249 250 251 252
            logger.debug("Export stats using the %s module" % e)
            thread = threading.Thread(target=self._exports[e].update,
                                      args=(input_stats,))
            thread.start()
253

254 255
        return True

256
    def getAll(self):
A
PEP 257  
Alessio Sergi 已提交
257
        """Return all the stats (list)."""
A
Alessio Sergi 已提交
258
        return [self._plugins[p].get_raw() for p in self._plugins]
259

260 261 262 263
    def getAllAsDict(self):
        """Return all the stats (dict)."""
        return {p: self._plugins[p].get_raw() for p in self._plugins}

264
    def getAllExports(self, plugin_list=None):
265 266 267
        """
        Return all the stats to be exported (list).
        Default behavor is to export all the stat
268
        if plugin_list is provided, only export stats of given plugin (list)
269
        """
270 271 272
        if plugin_list is None:
            # All plugins should be exported
            plugin_list = self._plugins
273 274
        return [self._plugins[p].get_export() for p in self._plugins]

275 276 277 278 279 280 281 282 283 284
    def getAllExportsAsDict(self, plugin_list=None):
        """
        Return all the stats to be exported (list).
        Default behavor is to export all the stat
        if plugin_list is provided, only export stats of given plugin (list)
        """
        if plugin_list is None:
            # All plugins should be exported
            plugin_list = self._plugins
        return {p: self._plugins[p].get_export() for p in plugin_list}
285

N
Nicolargo 已提交
286 287
    def getAllLimits(self):
        """Return the plugins limits list."""
288
        return [self._plugins[p].limits for p in self._plugins]
N
Nicolargo 已提交
289

290 291 292 293 294 295 296 297 298 299
    def getAllLimitsAsDict(self, plugin_list=None):
        """
        Return all the stats limits (dict).
        Default behavor is to export all the limits
        if plugin_list is provided, only export limits of given plugin (list)
        """
        if plugin_list is None:
            # All plugins should be exported
            plugin_list = self._plugins
        return {p: self._plugins[p].limits for p in plugin_list}
300

301
    def getAllViews(self):
A
PEP 257  
Alessio Sergi 已提交
302
        """Return the plugins views."""
303 304 305
        return [self._plugins[p].get_views() for p in self._plugins]

    def getAllViewsAsDict(self):
A
PEP 257  
Alessio Sergi 已提交
306
        """Return all the stats views (dict)."""
307
        return {p: self._plugins[p].get_views() for p in self._plugins}
308

N
Nicolas Hennion 已提交
309
    def get_plugin_list(self):
A
PEP 257  
Alessio Sergi 已提交
310
        """Return the plugin list."""
A
Alessio Sergi 已提交
311
        return self._plugins
N
Nicolas Hennion 已提交
312 313

    def get_plugin(self, plugin_name):
A
PEP 257  
Alessio Sergi 已提交
314
        """Return the plugin name."""
315
        if plugin_name in self._plugins:
N
Nicolas Hennion 已提交
316 317 318 319
            return self._plugins[plugin_name]
        else:
            return None

320
    def end(self):
A
PEP 257  
Alessio Sergi 已提交
321
        """End of the Glances stats."""
N
Nicolargo 已提交
322
        # Close export modules
323 324
        for e in self._exports:
            self._exports[e].exit()
N
Nicolargo 已提交
325 326 327
        # Close plugins
        for p in self._plugins:
            self._plugins[p].exit()