Test Failed
Push — develop ( 30cc9f...48251c )
by Nicolas
02:03
created

glances.plugins.mem   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 149
dl 0
loc 286
rs 10
c 0
b 0
f 0
wmc 22

4 Methods

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