Test Failed
Push — master ( 7cfc0c...4c3161 )
by Nicolas
04:02
created

glances.plugins.mem.PluginModel.update()   F

Complexity

Conditions 14

Size

Total Lines 95
Code Lines 50

Duplication

Lines 32
Ratio 33.68 %

Importance

Changes 0
Metric Value
cc 14
eloc 50
nop 1
dl 32
loc 95
rs 3.6
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like glances.plugins.mem.PluginModel.update() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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