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 | """Virtual memory plugin.""" |
||
10 | |||
11 | import psutil |
||
12 | |||
13 | from glances.plugins.plugin.model import GlancesPluginModel |
||
14 | |||
15 | # Fields description |
||
16 | fields_description = { |
||
17 | 'total': {'description': 'Total physical memory available.', 'unit': 'bytes', 'min_symbol': 'K'}, |
||
18 | 'available': { |
||
19 | 'description': 'The actual amount of available memory that can be given instantly \ |
||
20 | to processes that request more memory in bytes; this is calculated by summing \ |
||
21 | different memory values depending on the platform (e.g. free + buffers + cached on Linux) \ |
||
22 | and it is supposed to be used to monitor actual memory usage in a cross platform fashion.', |
||
23 | 'unit': 'bytes', |
||
24 | 'min_symbol': 'K', |
||
25 | }, |
||
26 | 'percent': { |
||
27 | 'description': 'The percentage usage calculated as (total - available) / total * 100.', |
||
28 | 'unit': 'percent', |
||
29 | }, |
||
30 | 'used': { |
||
31 | 'description': 'Memory used, calculated differently depending on the platform and \ |
||
32 | designed for informational purposes only.', |
||
33 | 'unit': 'bytes', |
||
34 | 'min_symbol': 'K', |
||
35 | }, |
||
36 | 'free': { |
||
37 | 'description': 'Memory not being used at all (zeroed) that is readily available; \ |
||
38 | note that this doesn\'t reflect the actual memory available (use \'available\' instead).', |
||
39 | 'unit': 'bytes', |
||
40 | 'min_symbol': 'K', |
||
41 | }, |
||
42 | 'active': { |
||
43 | 'description': '*(UNIX)*: memory currently in use or very recently used, and so it is in RAM.', |
||
44 | 'unit': 'bytes', |
||
45 | 'min_symbol': 'K', |
||
46 | }, |
||
47 | 'inactive': { |
||
48 | 'description': '*(UNIX)*: memory that is marked as not used.', |
||
49 | 'unit': 'bytes', |
||
50 | 'min_symbol': 'K', |
||
51 | 'short_name': 'inacti', |
||
52 | }, |
||
53 | 'buffers': { |
||
54 | 'description': '*(Linux, BSD)*: cache for things like file system metadata.', |
||
55 | 'unit': 'bytes', |
||
56 | 'min_symbol': 'K', |
||
57 | 'short_name': 'buffer', |
||
58 | }, |
||
59 | 'cached': {'description': '*(Linux, BSD)*: cache for various things.', 'unit': 'bytes', 'min_symbol': 'K'}, |
||
60 | 'wired': { |
||
61 | 'description': '*(BSD, macOS)*: memory that is marked to always stay in RAM. It is never moved to disk.', |
||
62 | 'unit': 'bytes', |
||
63 | 'min_symbol': 'K', |
||
64 | }, |
||
65 | 'shared': { |
||
66 | 'description': '*(BSD)*: memory that may be simultaneously accessed by multiple processes.', |
||
67 | 'unit': 'bytes', |
||
68 | 'min_symbol': 'K', |
||
69 | }, |
||
70 | } |
||
71 | |||
72 | # SNMP OID |
||
73 | # Total RAM in machine: .1.3.6.1.4.1.2021.4.5.0 |
||
74 | # Total RAM used: .1.3.6.1.4.1.2021.4.6.0 |
||
75 | # Total RAM Free: .1.3.6.1.4.1.2021.4.11.0 |
||
76 | # Total RAM Shared: .1.3.6.1.4.1.2021.4.13.0 |
||
77 | # Total RAM Buffered: .1.3.6.1.4.1.2021.4.14.0 |
||
78 | # Total Cached Memory: .1.3.6.1.4.1.2021.4.15.0 |
||
79 | # Note: For Windows, stats are in the FS table |
||
80 | snmp_oid = { |
||
81 | 'default': { |
||
82 | 'total': '1.3.6.1.4.1.2021.4.5.0', |
||
83 | 'free': '1.3.6.1.4.1.2021.4.11.0', |
||
84 | 'shared': '1.3.6.1.4.1.2021.4.13.0', |
||
85 | 'buffers': '1.3.6.1.4.1.2021.4.14.0', |
||
86 | 'cached': '1.3.6.1.4.1.2021.4.15.0', |
||
87 | }, |
||
88 | 'windows': { |
||
89 | 'mnt_point': '1.3.6.1.2.1.25.2.3.1.3', |
||
90 | 'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4', |
||
91 | 'size': '1.3.6.1.2.1.25.2.3.1.5', |
||
92 | 'used': '1.3.6.1.2.1.25.2.3.1.6', |
||
93 | }, |
||
94 | 'esxi': { |
||
95 | 'mnt_point': '1.3.6.1.2.1.25.2.3.1.3', |
||
96 | 'alloc_unit': '1.3.6.1.2.1.25.2.3.1.4', |
||
97 | 'size': '1.3.6.1.2.1.25.2.3.1.5', |
||
98 | 'used': '1.3.6.1.2.1.25.2.3.1.6', |
||
99 | }, |
||
100 | } |
||
101 | |||
102 | # Define the history items list |
||
103 | # All items in this list will be historised if the --enable-history tag is set |
||
104 | items_history_list = [{'name': 'percent', 'description': 'RAM memory usage', 'y_unit': '%'}] |
||
105 | |||
106 | |||
107 | class PluginModel(GlancesPluginModel): |
||
108 | """Glances' memory plugin. |
||
109 | |||
110 | stats is a dict |
||
111 | """ |
||
112 | |||
113 | def __init__(self, args=None, config=None): |
||
114 | """Init the plugin.""" |
||
115 | super().__init__( |
||
116 | args=args, config=config, items_history_list=items_history_list, fields_description=fields_description |
||
117 | ) |
||
118 | |||
119 | # We want to display the stat in the curse interface |
||
120 | self.display_curse = True |
||
121 | |||
122 | @GlancesPluginModel._check_decorator |
||
123 | @GlancesPluginModel._log_result_decorator |
||
124 | def update(self): |
||
125 | """Update RAM memory stats using the input method.""" |
||
126 | # Init new stats |
||
127 | stats = self.get_init_value() |
||
128 | |||
129 | if self.input_method == 'local': |
||
130 | # Update stats using the standard system lib |
||
131 | # Grab MEM using the psutil virtual_memory method |
||
132 | vm_stats = psutil.virtual_memory() |
||
133 | |||
134 | # Get all the memory stats (copy/paste of the psutil documentation) |
||
135 | # total: total physical memory available. |
||
136 | # available: the actual amount of available memory that can be given instantly |
||
137 | # to processes that request more memory in bytes; this is calculated by summing |
||
138 | # different memory values depending on the platform (e.g. free + buffers + cached on Linux) |
||
139 | # and it is supposed to be used to monitor actual memory usage in a cross platform fashion. |
||
140 | # percent: the percentage usage calculated as (total - available) / total * 100. |
||
141 | # used: memory used, calculated differently depending on the platform and designed for informational |
||
142 | # purposes only. |
||
143 | # free: memory not being used at all (zeroed) that is readily available; note that this doesn't |
||
144 | # reflect the actual memory available (use ‘available’ instead). |
||
145 | # Platform-specific fields: |
||
146 | # active: (UNIX): memory currently in use or very recently used, and so it is in RAM. |
||
147 | # inactive: (UNIX): memory that is marked as not used. |
||
148 | # buffers: (Linux, BSD): cache for things like file system metadata. |
||
149 | # cached: (Linux, BSD): cache for various things. |
||
150 | # wired: (BSD, macOS): memory that is marked to always stay in RAM. It is never moved to disk. |
||
151 | # shared: (BSD): memory that may be simultaneously accessed by multiple processes. |
||
152 | self.reset() |
||
153 | for mem in [ |
||
154 | 'total', |
||
155 | 'available', |
||
156 | 'percent', |
||
157 | 'used', |
||
158 | 'free', |
||
159 | 'active', |
||
160 | 'inactive', |
||
161 | 'buffers', |
||
162 | 'cached', |
||
163 | 'wired', |
||
164 | 'shared', |
||
165 | ]: |
||
166 | if hasattr(vm_stats, mem): |
||
167 | stats[mem] = getattr(vm_stats, mem) |
||
168 | |||
169 | # Use the 'free'/htop calculation |
||
170 | # free=available+buffer+cached |
||
171 | stats['free'] = stats['available'] |
||
172 | if hasattr(stats, 'buffers'): |
||
173 | stats['free'] += stats['buffers'] |
||
174 | if hasattr(stats, 'cached'): |
||
175 | stats['free'] += stats['cached'] |
||
176 | # used=total-free |
||
177 | stats['used'] = stats['total'] - stats['free'] |
||
178 | elif self.input_method == 'snmp': |
||
179 | # Update stats using SNMP |
||
180 | View Code Duplication | if self.short_system_name in ('windows', 'esxi'): |
|
0 ignored issues
–
show
Duplication
introduced
by
Loading history...
|
|||
181 | # Mem stats for Windows|Vmware Esxi are stored in the FS table |
||
182 | try: |
||
183 | fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True) |
||
184 | except KeyError: |
||
185 | self.reset() |
||
186 | else: |
||
187 | for fs in fs_stat: |
||
188 | # The Physical Memory (Windows) or Real Memory (VMware) |
||
189 | # gives statistics on RAM usage and availability. |
||
190 | if fs in ('Physical Memory', 'Real Memory'): |
||
191 | stats['total'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit']) |
||
192 | stats['used'] = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit']) |
||
193 | stats['percent'] = float(stats['used'] * 100 / stats['total']) |
||
194 | stats['free'] = stats['total'] - stats['used'] |
||
195 | break |
||
196 | else: |
||
197 | # Default behavior for others OS |
||
198 | stats = self.get_stats_snmp(snmp_oid=snmp_oid['default']) |
||
199 | |||
200 | if stats['total'] == '': |
||
201 | self.reset() |
||
202 | return self.stats |
||
203 | |||
204 | for k in stats: |
||
205 | stats[k] = int(stats[k]) * 1024 |
||
206 | |||
207 | # used=total-free |
||
208 | stats['used'] = stats['total'] - stats['free'] |
||
209 | |||
210 | # percent: the percentage usage calculated as (total - available) / total * 100. |
||
211 | stats['percent'] = float((stats['total'] - stats['free']) / stats['total'] * 100) |
||
212 | |||
213 | # Update the stats |
||
214 | self.stats = stats |
||
215 | |||
216 | return self.stats |
||
217 | |||
218 | def update_views(self): |
||
219 | """Update stats views.""" |
||
220 | # Call the father's method |
||
221 | super().update_views() |
||
222 | |||
223 | # Add specifics information |
||
224 | # Alert and log |
||
225 | self.views['percent']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total']) |
||
226 | # Optional |
||
227 | for key in ['active', 'inactive', 'buffers', 'cached']: |
||
228 | if key in self.stats: |
||
229 | self.views[key]['optional'] = True |
||
230 | |||
231 | def msg_curse(self, args=None, max_width=None): |
||
232 | """Return the dict to display in the curse interface.""" |
||
233 | # Init the return message |
||
234 | ret = [] |
||
235 | |||
236 | # Only process if stats exist and plugin not disabled |
||
237 | if not self.stats or self.is_disabled(): |
||
238 | return ret |
||
239 | |||
240 | # First line |
||
241 | # total% + active |
||
242 | msg = '{}'.format('MEM') |
||
243 | ret.append(self.curse_add_line(msg, "TITLE")) |
||
244 | msg = ' {:2}'.format(self.trend_msg(self.get_trend('percent'))) |
||
245 | ret.append(self.curse_add_line(msg)) |
||
246 | # Percent memory usage |
||
247 | msg = '{:>7.1%}'.format(self.stats['percent'] / 100) |
||
248 | ret.append(self.curse_add_line(msg, self.get_views(key='percent', option='decoration'))) |
||
249 | # Active memory usage |
||
250 | ret.extend(self.curse_add_stat('active', width=16, header=' ')) |
||
251 | |||
252 | # Second line |
||
253 | # total + inactive |
||
254 | ret.append(self.curse_new_line()) |
||
255 | # Total memory usage |
||
256 | ret.extend(self.curse_add_stat('total', width=15)) |
||
257 | # Inactive memory usage |
||
258 | ret.extend(self.curse_add_stat('inactive', width=16, header=' ')) |
||
259 | |||
260 | # Third line |
||
261 | # used + buffers |
||
262 | ret.append(self.curse_new_line()) |
||
263 | # Used memory usage |
||
264 | ret.extend(self.curse_add_stat('used', width=15)) |
||
265 | # Buffers memory usage |
||
266 | ret.extend(self.curse_add_stat('buffers', width=16, header=' ')) |
||
267 | |||
268 | # Fourth line |
||
269 | # free + cached |
||
270 | ret.append(self.curse_new_line()) |
||
271 | # Free memory usage |
||
272 | ret.extend(self.curse_add_stat('free', width=15)) |
||
273 | # Cached memory usage |
||
274 | ret.extend(self.curse_add_stat('cached', width=16, header=' ')) |
||
275 | |||
276 | return ret |
||
277 |