You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

310 lines
11 KiB

4 years ago
  1. # -*- coding: utf-8 -*-
  2. #
  3. # This file is part of Glances.
  4. #
  5. # Copyright (C) 2018 Nicolargo <nicolas@nicolargo.com>
  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. """The stats manager."""
  20. import collections
  21. import os
  22. import sys
  23. import threading
  24. import traceback
  25. from glances.globals import exports_path, plugins_path, sys_path
  26. from glances.logger import logger
  27. class GlancesStats(object):
  28. """This class stores, updates and gives stats."""
  29. # Script header constant
  30. header = "glances_"
  31. def __init__(self, config=None, args=None):
  32. # Set the config instance
  33. self.config = config
  34. # Set the argument instance
  35. self.args = args
  36. # Load plugins and exports modules
  37. self.load_modules(self.args)
  38. # Load the limits (for plugins)
  39. self.load_limits(self.config)
  40. def __getattr__(self, item):
  41. """Overwrite the getattr method in case of attribute is not found.
  42. The goal is to dynamically generate the following methods:
  43. - getPlugname(): return Plugname stat in JSON format
  44. - getViewsPlugname(): return views of the Plugname stat in JSON format
  45. """
  46. # Check if the attribute starts with 'get'
  47. if item.startswith('getViews'):
  48. # Get the plugin name
  49. plugname = item[len('getViews'):].lower()
  50. # Get the plugin instance
  51. plugin = self._plugins[plugname]
  52. if hasattr(plugin, 'get_json_views'):
  53. # The method get_views exist, return it
  54. return getattr(plugin, 'get_json_views')
  55. else:
  56. # The method get_views is not found for the plugin
  57. raise AttributeError(item)
  58. elif item.startswith('get'):
  59. # Get the plugin name
  60. plugname = item[len('get'):].lower()
  61. # Get the plugin instance
  62. plugin = self._plugins[plugname]
  63. if hasattr(plugin, 'get_stats'):
  64. # The method get_stats exist, return it
  65. return getattr(plugin, 'get_stats')
  66. else:
  67. # The method get_stats is not found for the plugin
  68. raise AttributeError(item)
  69. else:
  70. # Default behavior
  71. raise AttributeError(item)
  72. def load_modules(self, args):
  73. """Wrapper to load: plugins and export modules."""
  74. # Init the plugins dict
  75. # Active plugins dictionnary
  76. self._plugins = collections.defaultdict(dict)
  77. # Load the plugins
  78. self.load_plugins(args=args)
  79. # Init the export modules dict
  80. # Active exporters dictionnary
  81. self._exports = collections.defaultdict(dict)
  82. # All available exporters dictionnary
  83. self._exports_all = collections.defaultdict(dict)
  84. # Load the export modules
  85. self.load_exports(args=args)
  86. # Restoring system path
  87. sys.path = sys_path
  88. def _load_plugin(self, plugin_script, args=None, config=None):
  89. """Load the plugin (script), init it and add to the _plugin dict."""
  90. # The key is the plugin name
  91. # for example, the file glances_xxx.py
  92. # generate self._plugins_list["xxx"] = ...
  93. name = plugin_script[len(self.header):-3].lower()
  94. try:
  95. # Import the plugin
  96. plugin = __import__(plugin_script[:-3])
  97. # Init and add the plugin to the dictionary
  98. if name in ('help', 'amps', 'ports'):
  99. self._plugins[name] = plugin.Plugin(args=args, config=config)
  100. else:
  101. self._plugins[name] = plugin.Plugin(args=args)
  102. # Set the disable_<name> to False by default
  103. if self.args is not None:
  104. setattr(self.args,
  105. 'disable_' + name,
  106. getattr(self.args, 'disable_' + name, False))
  107. except Exception as e:
  108. # If a plugin can not be log, display a critical message
  109. # on the console but do not crash
  110. logger.critical("Error while initializing the {} plugin ({})".format(name, e))
  111. logger.error(traceback.format_exc())
  112. def load_plugins(self, args=None):
  113. """Load all plugins in the 'plugins' folder."""
  114. for item in os.listdir(plugins_path):
  115. if (item.startswith(self.header) and
  116. item.endswith(".py") and
  117. item != (self.header + "plugin.py")):
  118. # Load the plugin
  119. self._load_plugin(os.path.basename(item),
  120. args=args, config=self.config)
  121. # Log plugins list
  122. logger.debug("Active plugins list: {}".format(self.getPluginsList()))
  123. def load_exports(self, args=None):
  124. """Load all export modules in the 'exports' folder."""
  125. if args is None:
  126. return False
  127. header = "glances_"
  128. # Build the export module available list
  129. args_var = vars(locals()['args'])
  130. for item in os.listdir(exports_path):
  131. export_name = os.path.basename(item)[len(header):-3].lower()
  132. if (item.startswith(header) and
  133. item.endswith(".py") and
  134. item != (header + "export.py") and
  135. item != (header + "history.py")):
  136. self._exports_all[export_name] = os.path.basename(item)[:-3]
  137. # Set the disable_<name> to False by default
  138. setattr(self.args,
  139. 'export_' + export_name,
  140. getattr(self.args, 'export_' + export_name, False))
  141. # Aim is to check if the export module should be loaded
  142. for export_name in self._exports_all:
  143. if getattr(self.args, 'export_' + export_name, False):
  144. # Import the export module
  145. export_module = __import__(self._exports_all[export_name])
  146. # Add the export to the dictionary
  147. # The key is the module name
  148. # for example, the file glances_xxx.py
  149. # generate self._exports_list["xxx"] = ...
  150. self._exports[export_name] = export_module.Export(args=args,
  151. config=self.config)
  152. self._exports_all[export_name] = self._exports[export_name]
  153. # Log plugins list
  154. logger.debug("Active exports modules list: {}".format(self.getExportsList()))
  155. return True
  156. def getPluginsList(self, enable=True):
  157. """Return the plugins list.
  158. if enable is True, only return the active plugins (default)
  159. if enable is False, return all the plugins
  160. Return: list of plugin name
  161. """
  162. if enable:
  163. return [p for p in self._plugins if self._plugins[p].is_enable()]
  164. else:
  165. return [p for p in self._plugins]
  166. def getExportsList(self, enable=True):
  167. """Return the exports list.
  168. if enable is True, only return the active exporters (default)
  169. if enable is False, return all the exporters
  170. Return: list of export module name
  171. """
  172. if enable:
  173. return [e for e in self._exports]
  174. else:
  175. return [e for e in self._exports_all]
  176. def load_limits(self, config=None):
  177. """Load the stats limits (except the one in the exclude list)."""
  178. # For each plugins, call the load_limits method
  179. for p in self._plugins:
  180. self._plugins[p].load_limits(config)
  181. def update(self):
  182. """Wrapper method to update the stats."""
  183. # For standalone and server modes
  184. # For each plugins, call the update method
  185. for p in self._plugins:
  186. if self._plugins[p].is_disable():
  187. # If current plugin is disable
  188. # then continue to next plugin
  189. continue
  190. # Update the stats...
  191. self._plugins[p].update()
  192. # ... the history
  193. self._plugins[p].update_stats_history()
  194. # ... and the views
  195. self._plugins[p].update_views()
  196. def export(self, input_stats=None):
  197. """Export all the stats.
  198. Each export module is ran in a dedicated thread.
  199. """
  200. # threads = []
  201. input_stats = input_stats or {}
  202. for e in self._exports:
  203. logger.debug("Export stats using the %s module" % e)
  204. thread = threading.Thread(target=self._exports[e].update,
  205. args=(input_stats,))
  206. # threads.append(thread)
  207. thread.start()
  208. def getAll(self):
  209. """Return all the stats (list)."""
  210. return [self._plugins[p].get_raw() for p in self._plugins]
  211. def getAllAsDict(self):
  212. """Return all the stats (dict)."""
  213. return {p: self._plugins[p].get_raw() for p in self._plugins}
  214. def getAllExports(self):
  215. """
  216. Return all the stats to be exported (list).
  217. Default behavor is to export all the stat
  218. """
  219. return [self._plugins[p].get_export() for p in self._plugins]
  220. def getAllExportsAsDict(self, plugin_list=None):
  221. """
  222. Return all the stats to be exported (list).
  223. Default behavor is to export all the stat
  224. if plugin_list is provided, only export stats of given plugin (list)
  225. """
  226. if plugin_list is None:
  227. # All plugins should be exported
  228. plugin_list = self._plugins
  229. return {p: self._plugins[p].get_export() for p in plugin_list}
  230. def getAllLimits(self):
  231. """Return the plugins limits list."""
  232. return [self._plugins[p].limits for p in self._plugins]
  233. def getAllLimitsAsDict(self, plugin_list=None):
  234. """
  235. Return all the stats limits (dict).
  236. Default behavor is to export all the limits
  237. if plugin_list is provided, only export limits of given plugin (list)
  238. """
  239. if plugin_list is None:
  240. # All plugins should be exported
  241. plugin_list = self._plugins
  242. return {p: self._plugins[p].limits for p in plugin_list}
  243. def getAllViews(self):
  244. """Return the plugins views."""
  245. return [self._plugins[p].get_views() for p in self._plugins]
  246. def getAllViewsAsDict(self):
  247. """Return all the stats views (dict)."""
  248. return {p: self._plugins[p].get_views() for p in self._plugins}
  249. def get_plugin_list(self):
  250. """Return the plugin list."""
  251. return self._plugins
  252. def get_plugin(self, plugin_name):
  253. """Return the plugin name."""
  254. if plugin_name in self._plugins:
  255. return self._plugins[plugin_name]
  256. else:
  257. return None
  258. def end(self):
  259. """End of the Glances stats."""
  260. # Close export modules
  261. for e in self._exports:
  262. self._exports[e].exit()
  263. # Close plugins
  264. for p in self._plugins:
  265. self._plugins[p].exit()