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