1 | # |
||
2 | # This file is part of Glances. |
||
3 | # |
||
4 | # SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]> |
||
5 | # |
||
6 | # SPDX-License-Identifier: LGPL-3.0-only |
||
7 | # |
||
8 | |||
9 | """ |
||
10 | I am your father... |
||
11 | |||
12 | ...for all Glances exports IF. |
||
13 | """ |
||
14 | |||
15 | from glances.globals import NoOptionError, NoSectionError, iteritems, iterkeys, json_dumps |
||
16 | from glances.logger import logger |
||
17 | from glances.timer import Counter |
||
18 | |||
19 | |||
20 | class GlancesExport: |
||
21 | """Main class for Glances export IF.""" |
||
22 | |||
23 | # List of non exportable plugins |
||
24 | # @TODO: remove this part and make all plugins exportable (see issue #1556) |
||
25 | # @TODO: also make this list configurable by the user (see issue #1443) |
||
26 | non_exportable_plugins = [ |
||
27 | 'alert', |
||
28 | 'amps', |
||
29 | 'help', |
||
30 | 'now', |
||
31 | 'plugin', |
||
32 | 'psutilversion', |
||
33 | 'quicklook', |
||
34 | 'version', |
||
35 | ] |
||
36 | |||
37 | def __init__(self, config=None, args=None): |
||
38 | """Init the export class.""" |
||
39 | # Export name |
||
40 | self.export_name = self.__class__.__module__ |
||
41 | logger.debug(f"Init export module {self.export_name}") |
||
42 | |||
43 | # Init the config & args |
||
44 | self.config = config |
||
45 | self.args = args |
||
46 | |||
47 | # By default export is disabled |
||
48 | # Needs to be set to True in the __init__ class of child |
||
49 | self.export_enable = False |
||
50 | |||
51 | # Mandatory for (most of) the export module |
||
52 | self.host = None |
||
53 | self.port = None |
||
54 | |||
55 | # Save last export list |
||
56 | self._last_exported_list = None |
||
57 | |||
58 | View Code Duplication | def _log_result_decorator(fct): |
|
0 ignored issues
–
show
Duplication
introduced
by
Loading history...
|
|||
59 | """Log (DEBUG) the result of the function fct.""" |
||
60 | |||
61 | def wrapper(*args, **kw): |
||
62 | counter = Counter() |
||
63 | ret = fct(*args, **kw) |
||
64 | duration = counter.get() |
||
65 | class_name = args[0].__class__.__name__ |
||
66 | class_module = args[0].__class__.__module__ |
||
67 | logger.debug(f"{class_name} {class_module} {fct.__name__} return {ret} in {duration} seconds") |
||
68 | return ret |
||
69 | |||
70 | return wrapper |
||
71 | |||
72 | def exit(self): |
||
73 | """Close the export module.""" |
||
74 | logger.debug(f"Finalise export interface {self.export_name}") |
||
75 | |||
76 | def load_conf(self, section, mandatories=['host', 'port'], options=None): |
||
77 | """Load the export <section> configuration in the Glances configuration file. |
||
78 | |||
79 | :param section: name of the export section to load |
||
80 | :param mandatories: a list of mandatory parameters to load |
||
81 | :param options: a list of optional parameters to load |
||
82 | |||
83 | :returns: Boolean -- True if section is found |
||
84 | """ |
||
85 | options = options or [] |
||
86 | |||
87 | if self.config is None: |
||
88 | return False |
||
89 | |||
90 | # By default read the mandatory host:port items |
||
91 | try: |
||
92 | for opt in mandatories: |
||
93 | setattr(self, opt, self.config.get_value(section, opt)) |
||
94 | except NoSectionError: |
||
95 | logger.error(f"No {section} configuration found") |
||
96 | return False |
||
97 | except NoOptionError as e: |
||
98 | logger.error(f"Error in the {section} configuration ({e})") |
||
99 | return False |
||
100 | |||
101 | # Load options |
||
102 | for opt in options: |
||
103 | try: |
||
104 | setattr(self, opt, self.config.get_value(section, opt)) |
||
105 | except NoOptionError: |
||
106 | pass |
||
107 | |||
108 | logger.debug(f"Load {section} from the Glances configuration file") |
||
109 | logger.debug(f"{section} parameters: {({opt: getattr(self, opt) for opt in mandatories + options})}") |
||
110 | |||
111 | return True |
||
112 | |||
113 | def get_item_key(self, item): |
||
114 | """Return the value of the item 'key'.""" |
||
115 | ret = None |
||
116 | try: |
||
117 | ret = item[item['key']] |
||
118 | except KeyError: |
||
119 | logger.error(f"No 'key' available in {item}") |
||
120 | if isinstance(ret, list): |
||
121 | return ret[0] |
||
122 | return ret |
||
123 | |||
124 | def parse_tags(self, tags): |
||
125 | """Parse tags into a dict. |
||
126 | |||
127 | :param tags: a comma-separated list of 'key:value' pairs. Example: foo:bar,spam:eggs |
||
128 | :return: a dict of tags. Example: {'foo': 'bar', 'spam': 'eggs'} |
||
129 | """ |
||
130 | d_tags = {} |
||
131 | if tags: |
||
132 | try: |
||
133 | d_tags = dict([x.split(':') for x in tags.split(',')]) |
||
134 | except ValueError: |
||
135 | # one of the 'key:value' pairs was missing |
||
136 | logger.info('Invalid tags passed: %s', tags) |
||
137 | d_tags = {} |
||
138 | |||
139 | return d_tags |
||
140 | |||
141 | def plugins_to_export(self, stats): |
||
142 | """Return the list of plugins to export. |
||
143 | |||
144 | :param stats: the stats object |
||
145 | :return: a list of plugins to export |
||
146 | """ |
||
147 | return [p for p in stats.getPluginsList() if p not in self.non_exportable_plugins] |
||
148 | |||
149 | def last_exported_list(self): |
||
150 | """Return the list of plugins last exported.""" |
||
151 | return self._last_exported_list |
||
152 | |||
153 | def update(self, stats): |
||
154 | """Update stats to a server. |
||
155 | |||
156 | The method builds two lists: names and values and calls the export method to export the stats. |
||
157 | |||
158 | Note: this class can be overwritten (for example in CSV and Graph). |
||
159 | """ |
||
160 | if not self.export_enable: |
||
161 | return False |
||
162 | |||
163 | # Get all the stats & limits |
||
164 | self._last_exported_list = self.plugins_to_export(stats) |
||
165 | all_stats = stats.getAllExportsAsDict(plugin_list=self.last_exported_list()) |
||
166 | all_limits = stats.getAllLimitsAsDict(plugin_list=self.last_exported_list()) |
||
167 | |||
168 | # Loop over plugins to export |
||
169 | for plugin in self.last_exported_list(): |
||
170 | if isinstance(all_stats[plugin], dict): |
||
171 | all_stats[plugin].update(all_limits[plugin]) |
||
172 | elif isinstance(all_stats[plugin], list): |
||
173 | # TypeError: string indices must be integers (Network plugin) #1054 |
||
174 | for i in all_stats[plugin]: |
||
175 | i.update(all_limits[plugin]) |
||
176 | else: |
||
177 | continue |
||
178 | export_names, export_values = self.build_export(all_stats[plugin]) |
||
179 | self.export(plugin, export_names, export_values) |
||
180 | |||
181 | return True |
||
182 | |||
183 | def build_export(self, stats): |
||
184 | """Build the export lists.""" |
||
185 | export_names = [] |
||
186 | export_values = [] |
||
187 | |||
188 | if isinstance(stats, dict): |
||
189 | # Stats is a dict |
||
190 | # Is there a key ? |
||
191 | if 'key' in iterkeys(stats) and stats['key'] in iterkeys(stats): |
||
192 | pre_key = '{}.'.format(stats[stats['key']]) |
||
193 | else: |
||
194 | pre_key = '' |
||
195 | # Walk through the dict |
||
196 | for key, value in sorted(iteritems(stats)): |
||
197 | if isinstance(value, bool): |
||
198 | value = json_dumps(value) |
||
199 | |||
200 | if isinstance(value, list): |
||
201 | value = ' '.join([str(v) for v in value]) |
||
202 | |||
203 | if isinstance(value, dict): |
||
204 | item_names, item_values = self.build_export(value) |
||
205 | item_names = [pre_key + key.lower() + str(i) for i in item_names] |
||
206 | export_names += item_names |
||
207 | export_values += item_values |
||
208 | else: |
||
209 | export_names.append(pre_key + key.lower()) |
||
210 | export_values.append(value) |
||
211 | elif isinstance(stats, list): |
||
212 | # Stats is a list (of dict) |
||
213 | # Recursive loop through the list |
||
214 | for item in stats: |
||
215 | item_names, item_values = self.build_export(item) |
||
216 | export_names += item_names |
||
217 | export_values += item_values |
||
218 | return export_names, export_values |
||
219 | |||
220 | def export(self, name, columns, points): |
||
221 | # This method should be implemented by each exporter |
||
222 | pass |
||
223 |