Completed
Push — master ( 793552...8b5b19 )
by Nicolas
05:48 queued 01:54
created

glances.plugins.glances_gpu   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 316
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 49
eloc 186
dl 0
loc 316
rs 8.48
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A Plugin.__init__() 0 11 1
F Plugin.msg_curse() 0 110 21
A Plugin.init_nvidia() 0 14 3
A Plugin.get_key() 0 3 1
A Plugin.get_device_stats() 0 21 2
A Plugin.update_views() 0 26 5
A Plugin.exit() 0 10 3
A Plugin.update() 0 20 4

5 Functions

Rating   Name   Duplication   Size   Complexity  
A get_device_handles() 0 6 1
A get_device_name() 0 6 2
A get_mem() 0 7 2
A get_temperature() 0 7 2
A get_proc() 0 6 2

How to fix   Complexity   

Complexity

Complex classes like glances.plugins.glances_gpu 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
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2018 Kirby Banman <[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
"""GPU plugin (limited to NVIDIA chipsets)."""
21
22
from glances.compat import nativestr, to_fahrenheit
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
23
from glances.logger import logger
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
24
from glances.plugins.glances_plugin import GlancesPlugin
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
25
26
# In Glances 3.1.4 or higher, we use the py3nvml lib (see issue #1523)
27
try:
28
    import py3nvml.py3nvml as pynvml
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
introduced by
Unable to import 'py3nvml.py3nvml'
Loading history...
29
except Exception as e:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
30
    import_error_tag = True
0 ignored issues
show
Coding Style Naming introduced by
The name import_error_tag does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
31
    # Display debu message if import KeyError
32
    logger.warning("Missing Python Lib ({}), Nvidia GPU plugin is disabled".format(e))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (86/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
33
else:
34
    import_error_tag = False
0 ignored issues
show
Coding Style Naming introduced by
The name import_error_tag does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
35
36
# Define the history items list
37
# All items in this list will be historised if the --enable-history tag is set
38
items_history_list = [{'name': 'proc',
0 ignored issues
show
Coding Style Naming introduced by
The name items_history_list does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
39
                       'description': 'GPU processor',
40
                       'y_unit': '%'},
41
                      {'name': 'mem',
42
                       'description': 'Memory consumption',
43
                       'y_unit': '%'}]
44
45
46
class Plugin(GlancesPlugin):
47
    """Glances GPU plugin (limited to NVIDIA chipsets).
48
49
    stats is a list of dictionaries with one entry per GPU
50
    """
51
52
    def __init__(self, args=None, config=None):
53
        """Init the plugin."""
54
        super(Plugin, self).__init__(args=args,
55
                                     config=config,
56
                                     stats_init_value=[])
57
58
        # Init the NVidia API
59
        self.init_nvidia()
60
61
        # We want to display the stat in the curse interface
62
        self.display_curse = True
63
64
    def init_nvidia(self):
65
        """Init the NVIDIA API."""
66
        if import_error_tag:
67
            self.nvml_ready = False
68
69
        try:
70
            pynvml.nvmlInit()
71
            self.device_handles = get_device_handles()
72
            self.nvml_ready = True
73
        except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
74
            logger.debug("pynvml could not be initialized.")
75
            self.nvml_ready = False
76
77
        return self.nvml_ready
78
79
    def get_key(self):
80
        """Return the key of the list."""
81
        return 'gpu_id'
82
83
    @GlancesPlugin._check_decorator
84
    @GlancesPlugin._log_result_decorator
85
    def update(self):
86
        """Update the GPU stats."""
87
        # Init new stats
88
        stats = self.get_init_value()
89
90
        if not self.nvml_ready:
91
            return self.stats
92
93
        if self.input_method == 'local':
94
            stats = self.get_device_stats()
95
        elif self.input_method == 'snmp':
96
            # not available
97
            pass
98
99
        # Update the stats
100
        self.stats = stats
101
102
        return self.stats
103
104
    def update_views(self):
105
        """Update stats views."""
106
        # Call the father's method
107
        super(Plugin, self).update_views()
108
109
        # Add specifics informations
110
        # Alert
111
        for i in self.stats:
112
            # Init the views for the current GPU
113
            self.views[i[self.get_key()]] = {'proc': {},
114
                                             'mem': {},
115
                                             'temperature': {}}
116
            # Processor alert
117
            if 'proc' in i:
118
                alert = self.get_alert(i['proc'], header='proc')
119
                self.views[i[self.get_key()]]['proc']['decoration'] = alert
120
            # Memory alert
121
            if 'mem' in i:
122
                alert = self.get_alert(i['mem'], header='mem')
123
                self.views[i[self.get_key()]]['mem']['decoration'] = alert
124
            # Temperature alert
125
            if 'temperature' in i:
126
                alert = self.get_alert(i['temperature'], header='temperature')
127
                self.views[i[self.get_key()]]['temperature']['decoration'] = alert
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
128
129
        return True
130
131
    def msg_curse(self, args=None, max_width=None):
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (19/15).
Loading history...
132
        """Return the dict to display in the curse interface."""
133
        # Init the return message
134
        ret = []
135
136
        # Only process if stats exist, not empty (issue #871) and plugin not disabled
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (85/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
137
        if not self.stats or (self.stats == []) or self.is_disable():
138
            return ret
139
140
        # Check if all GPU have the same name
141
        same_name = all(s['name'] == self.stats[0]['name'] for s in self.stats)
142
143
        # gpu_stats contain the first GPU in the list
144
        gpu_stats = self.stats[0]
145
146
        # Header
147
        header = ''
148
        if len(self.stats) > 1:
149
            header += '{} '.format(len(self.stats))
150
        if same_name:
151
            header += '{} {}'.format('GPU', gpu_stats['name'])
152
        else:
153
            header += '{}'.format('GPU')
154
        msg = header[:17]
155
        ret.append(self.curse_add_line(msg, "TITLE"))
156
157
        # Build the string message
158
        if len(self.stats) == 1 or args.meangpu:
159
            # GPU stat summary or mono GPU
160
            # New line
161
            ret.append(self.curse_new_line())
162
            # GPU PROC
163
            try:
164
                mean_proc = sum(s['proc'] for s in self.stats if s is not None) / len(self.stats)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (97/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by
division w/o __future__ statement
Loading history...
165
            except TypeError:
166
                mean_proc_msg = '{:>4}'.format('N/A')
167
            else:
168
                mean_proc_msg = '{:>3.0f}%'.format(mean_proc)
169
            if len(self.stats) > 1:
170
                msg = '{:13}'.format('proc mean:')
171
            else:
172
                msg = '{:13}'.format('proc:')
173
            ret.append(self.curse_add_line(msg))
174
            ret.append(self.curse_add_line(
175
                mean_proc_msg, self.get_views(item=gpu_stats[self.get_key()],
176
                                              key='proc',
177
                                              option='decoration')))
178
            # New line
179
            ret.append(self.curse_new_line())
180
            # GPU MEM
181
            try:
182
                mean_mem = sum(s['mem'] for s in self.stats if s is not None) / len(self.stats)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (95/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by
division w/o __future__ statement
Loading history...
183
            except TypeError:
184
                mean_mem_msg = '{:>4}'.format('N/A')
185
            else:
186
                mean_mem_msg = '{:>3.0f}%'.format(mean_mem)
187
            if len(self.stats) > 1:
188
                msg = '{:13}'.format('mem mean:')
189
            else:
190
                msg = '{:13}'.format('mem:')
191
            ret.append(self.curse_add_line(msg))
192
            ret.append(self.curse_add_line(
193
                mean_mem_msg, self.get_views(item=gpu_stats[self.get_key()],
194
                                             key='mem',
195
                                             option='decoration')))
196
            # New line
197
            ret.append(self.curse_new_line())
198
            # GPU TEMPERATURE
199
            try:
200
                mean_temperature = sum(s['temperature'] for s in self.stats if s is not None) / len(self.stats)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (111/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by
division w/o __future__ statement
Loading history...
201
            except TypeError:
202
                mean_temperature_msg = '{:>4}'.format('N/A')
203
            else:
204
                unit = 'C'
205
                if args.fahrenheit:
206
                    value = to_fahrenheit(i['value'])
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'i'
Loading history...
Unused Code introduced by
The variable value seems to be unused.
Loading history...
Comprehensibility Best Practice introduced by
The variable i does not seem to be defined.
Loading history...
207
                    unit = 'F'
208
                mean_temperature_msg = '{:>3.0f}{}'.format(mean_temperature,
209
                                                           unit)
210
            if len(self.stats) > 1:
211
                msg = '{:13}'.format('temp mean:')
212
            else:
213
                msg = '{:13}'.format('temperature:')
214
            ret.append(self.curse_add_line(msg))
215
            ret.append(self.curse_add_line(
216
                mean_temperature_msg, self.get_views(item=gpu_stats[self.get_key()],
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (84/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
217
                                                     key='temperature',
218
                                                     option='decoration')))
219
        else:
220
            # Multi GPU
221
            # Temperature is not displayed in this mode...
222
            for gpu_stats in self.stats:
223
                # New line
224
                ret.append(self.curse_new_line())
225
                # GPU ID + PROC + MEM + TEMPERATURE
226
                id_msg = '{}'.format(gpu_stats['gpu_id'])
227
                try:
228
                    proc_msg = '{:>3.0f}%'.format(gpu_stats['proc'])
229
                except ValueError:
230
                    proc_msg = '{:>4}'.format('N/A')
231
                try:
232
                    mem_msg = '{:>3.0f}%'.format(gpu_stats['mem'])
233
                except ValueError:
234
                    mem_msg = '{:>4}'.format('N/A')
235
                msg = '{}: {} mem: {}'.format(id_msg,
236
                                              proc_msg,
237
                                              mem_msg)
238
                ret.append(self.curse_add_line(msg))
239
240
        return ret
241
242
    def get_device_stats(self):
243
        """Get GPU stats."""
244
        stats = []
245
246
        for index, device_handle in enumerate(self.device_handles):
247
            device_stats = dict()
248
            # Dictionnary key is the GPU_ID
249
            device_stats['key'] = self.get_key()
250
            # GPU id (for multiple GPU, start at 0)
251
            device_stats['gpu_id'] = index
252
            # GPU name
253
            device_stats['name'] = get_device_name(device_handle)
254
            # Memory consumption in % (not available on all GPU)
255
            device_stats['mem'] = get_mem(device_handle)
256
            # Processor consumption in %
257
            device_stats['proc'] = get_proc(device_handle)
258
            # Processor temperature in °C
259
            device_stats['temperature'] = get_temperature(device_handle)
260
            stats.append(device_stats)
261
262
        return stats
263
264
    def exit(self):
265
        """Overwrite the exit method to close the GPU API."""
266
        if self.nvml_ready:
267
            try:
268
                pynvml.nvmlShutdown()
269
            except Exception as e:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
270
                logger.debug("pynvml failed to shutdown correctly ({})".format(e))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (82/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
271
272
        # Call the father exit method
273
        super(Plugin, self).exit()
274
275
276
def get_device_handles():
277
    """Get a list of NVML device handles, one per device.
278
279
    Can throw NVMLError.
280
    """
281
    return [pynvml.nvmlDeviceGetHandleByIndex(i) for i in range(pynvml.nvmlDeviceGetCount())]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (93/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
282
283
284
def get_device_name(device_handle):
285
    """Get GPU device name."""
286
    try:
287
        return nativestr(pynvml.nvmlDeviceGetName(device_handle))
288
    except pynvml.NVMlError:
289
        return "NVIDIA"
290
291
292
def get_mem(device_handle):
293
    """Get GPU device memory consumption in percent."""
294
    try:
295
        memory_info = pynvml.nvmlDeviceGetMemoryInfo(device_handle)
296
        return memory_info.used * 100.0 / memory_info.total
0 ignored issues
show
introduced by
division w/o __future__ statement
Loading history...
297
    except pynvml.NVMLError:
298
        return None
299
300
301
def get_proc(device_handle):
302
    """Get GPU device CPU consumption in percent."""
303
    try:
304
        return pynvml.nvmlDeviceGetUtilizationRates(device_handle).gpu
305
    except pynvml.NVMLError:
306
        return None
307
308
309
def get_temperature(device_handle):
310
    """Get GPU device CPU consumption in percent."""
311
    try:
312
        return pynvml.nvmlDeviceGetTemperature(device_handle,
313
                                               pynvml.NVML_TEMPERATURE_GPU)
314
    except pynvml.NVMLError:
315
        return None
316