Test Failed
Push — develop ( 829ae9...5e1a08 )
by Nicolas
01:04 queued 16s
created

PluginModel._update_for_other_oses()   A

Complexity

Conditions 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nop 2
dl 0
loc 17
rs 9.9
c 0
b 0
f 0
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
    def _update_for_local(self, stats):
123
        # Update stats using the standard system lib
124
        # Grab MEM using the psutil virtual_memory method
125
        vm_stats = psutil.virtual_memory()
126
127
        # Get all the memory stats (copy/paste of the psutil documentation)
128
        # total: total physical memory available.
129
        # available: the actual amount of available memory that can be given instantly
130
        # to processes that request more memory in bytes; this is calculated by summing
131
        # different memory values depending on the platform (e.g. free + buffers + cached on Linux)
132
        # and it is supposed to be used to monitor actual memory usage in a cross platform fashion.
133
        # percent: the percentage usage calculated as (total - available) / total * 100.
134
        # used: memory used, calculated differently depending on the platform and designed for informational
135
        # purposes only.
136
        # free: memory not being used at all (zeroed) that is readily available; note that this doesn't
137
        # reflect the actual memory available (use ‘available’ instead).
138
        # Platform-specific fields:
139
        # active: (UNIX): memory currently in use or very recently used, and so it is in RAM.
140
        # inactive: (UNIX): memory that is marked as not used.
141
        # buffers: (Linux, BSD): cache for things like file system metadata.
142
        # cached: (Linux, BSD): cache for various things.
143
        # wired: (BSD, macOS): memory that is marked to always stay in RAM. It is never moved to disk.
144
        # shared: (BSD): memory that may be simultaneously accessed by multiple processes.
145
        self.reset()
146
        for mem in [
147
            'total',
148
            'available',
149
            'percent',
150
            'used',
151
            'free',
152
            'active',
153
            'inactive',
154
            'buffers',
155
            'cached',
156
            'wired',
157
            'shared',
158
        ]:
159
            if hasattr(vm_stats, mem):
160
                stats[mem] = getattr(vm_stats, mem)
161
162
        # Use the 'free'/htop calculation
163
        # free=available+buffer+cached
164
        stats['free'] = stats['available']
165
        if hasattr(stats, 'buffers'):
166
            stats['free'] += stats['buffers']
167
        if hasattr(stats, 'cached'):
168
            stats['free'] += stats['cached']
169
        # used=total-free
170
        stats['used'] = stats['total'] - stats['free']
171
172
        return stats
173
174
    def _update_for_win_os_esxi(self, stats):
175
        # Mem stats for Windows|Vmware Esxi are stored in the FS table
176
        try:
177
            fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True)
178
        except KeyError:
179
            self.reset()
180
        else:
181
            for fs in fs_stat:
182
                # The Physical Memory (Windows) or Real Memory (VMware)
183
                # gives statistics on RAM usage and availability.
184
                if fs in ('Physical Memory', 'Real Memory'):
185
                    stats['total'] = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
186
                    stats['used'] = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
187
                    stats['percent'] = float(stats['used'] * 100 / stats['total'])
188
                    stats['free'] = stats['total'] - stats['used']
189
                    break
190
191
        return stats
192
193
    def _update_for_other_oses(self, stats):
194
        stats = self.get_stats_snmp(snmp_oid=snmp_oid['default'])
195
196
        if stats['total'] == '':
197
            self.reset()
198
            return 'reset'
199
200
        for k in stats:
201
            stats[k] = int(stats[k]) * 1024
202
203
        # used=total-free
204
        stats['used'] = stats['total'] - stats['free']
205
206
        # percent: the percentage usage calculated as (total - available) / total * 100.
207
        stats['percent'] = float((stats['total'] - stats['free']) / stats['total'] * 100)
208
209
        return stats
210
211
    @GlancesPluginModel._check_decorator
212
    @GlancesPluginModel._log_result_decorator
213
    def update(self):
214
        """Update RAM memory stats using the input method."""
215
        init = self.get_init_value()
216
217
        if self.input_method == 'local':
218
            stats = self._update_for_local(init)
219
        elif self.input_method == 'snmp' and self.short_system_name in ('windows', 'esxi'):
220
            stats = self._update_for_win_os_esxi(init)
221
        elif self.input_method == 'snmp':
222
            stats = self._update_for_other_oses(init)
223
224
        if stats in ['reset']:
0 ignored issues
show
introduced by
The variable stats does not seem to be defined for all execution paths.
Loading history...
225
            return self.stats
226
227
        # Update the stats
228
        self.stats = stats
229
230
        return self.stats
231
232
    def update_views(self):
233
        """Update stats views."""
234
        # Call the father's method
235
        super().update_views()
236
237
        # Add specifics information
238
        # Alert and log
239
        self.views['percent']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total'])
240
        # Optional
241
        for key in ['active', 'inactive', 'buffers', 'cached']:
242
            if key in self.stats:
243
                self.views[key]['optional'] = True
244
245
    def msg_curse(self, args=None, max_width=None):
246
        """Return the dict to display in the curse interface."""
247
        # Init the return message
248
        ret = []
249
250
        # Only process if stats exist and plugin not disabled
251
        if not self.stats or self.is_disabled():
252
            return ret
253
254
        # First line
255
        # total% + active
256
        msg = '{}'.format('MEM')
257
        ret.append(self.curse_add_line(msg, "TITLE"))
258
        msg = ' {:2}'.format(self.trend_msg(self.get_trend('percent')))
259
        ret.append(self.curse_add_line(msg))
260
        # Percent memory usage
261
        msg = '{:>7.1%}'.format(self.stats['percent'] / 100)
262
        ret.append(self.curse_add_line(msg, self.get_views(key='percent', option='decoration')))
263
        # Active memory usage
264
        ret.extend(self.curse_add_stat('active', width=16, header='  '))
265
266
        # Second line
267
        # total + inactive
268
        ret.append(self.curse_new_line())
269
        # Total memory usage
270
        ret.extend(self.curse_add_stat('total', width=15))
271
        # Inactive memory usage
272
        ret.extend(self.curse_add_stat('inactive', width=16, header='  '))
273
274
        # Third line
275
        # used + buffers
276
        ret.append(self.curse_new_line())
277
        # Used memory usage
278
        ret.extend(self.curse_add_stat('used', width=15))
279
        # Buffers memory usage
280
        ret.extend(self.curse_add_stat('buffers', width=16, header='  '))
281
282
        # Fourth line
283
        # free + cached
284
        ret.append(self.curse_new_line())
285
        # Free memory usage
286
        ret.extend(self.curse_add_stat('free', width=15))
287
        # Cached memory usage
288
        ret.extend(self.curse_add_stat('cached', width=16, header='  '))
289
290
        return ret
291