Completed
Push — master ( 2b80fa...6ea077 )
by Nicolas
01:22
created

Plugin.get_proc()   A

Complexity

Conditions 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
c 2
b 0
f 0
dl 0
loc 6
rs 9.4285
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2017 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.logger import logger
23
from glances.plugins.glances_plugin import GlancesPlugin
24
25
try:
26
    import pynvml
27
except ImportError:
28
    logger.debug("Could not import pynvml.  NVIDIA stats will not be collected.")
29
    gpu_nvidia_tag = False
30
else:
31
    gpu_nvidia_tag = True
32
33
34
class Plugin(GlancesPlugin):
35
36
    """Glances GPU plugin (limited to NVIDIA chipsets).
37
38
    stats is a list of dictionaries with one entry per GPU
39
    """
40
41
    def __init__(self, args=None):
42
        """Init the plugin"""
43
        super(Plugin, self).__init__(args=args)
44
45
        # Init the NVidia API
46
        self.init_nvidia()
47
48
        # We want to display the stat in the curse interface
49
        self.display_curse = True
50
51
        # Init the stats
52
        self.reset()
53
54
    def reset(self):
55
        """Reset/init the stats."""
56
        self.stats = []
57
58
    def init_nvidia(self):
59
        """Init the NVIDIA API"""
60
        if not gpu_nvidia_tag:
61
            self.nvml_ready = False
62
63
        try:
64
            pynvml.nvmlInit()
65
            self.device_handles = self.get_device_handles()
66
            self.nvml_ready = True
67
        except Exception:
68
            logger.debug("pynvml could not be initialized.")
69
            self.nvml_ready = False
70
71
        return self.nvml_ready
72
73
    def get_key(self):
74
        """Return the key of the list."""
75
        return 'gpu_id'
76
77
    @GlancesPlugin._check_decorator
78
    @GlancesPlugin._log_result_decorator
79
    def update(self):
80
        """Update the GPU stats"""
81
82
        self.reset()
83
84
        # !!! JUST FOR TEST
85
        # self.stats = [{"key": "gpu_id", "mem": None, "proc": 60, "gpu_id": 0, "name": "GeForce GTX 560 Ti"}]
86
        # self.stats = [{"key": "gpu_id", "mem": 10, "proc": 60, "gpu_id": 0, "name": "GeForce GTX 560 Ti"}]
87
        # self.stats = [{"key": "gpu_id", "mem": 48.64645, "proc": 60.73, "gpu_id": 0, "name": "GeForce GTX 560 Ti"},
88
        #               {"key": "gpu_id", "mem": 70.743, "proc": 80.28, "gpu_id": 1, "name": "GeForce GTX 560 Ti"},
89
        #               {"key": "gpu_id", "mem": 0, "proc": 0, "gpu_id": 2, "name": "GeForce GTX 560 Ti"}]
90
        # self.stats = [{"key": "gpu_id", "mem": 48.64645, "proc": 60.73, "gpu_id": 0, "name": "GeForce GTX 560 Ti"},
91
        #               {"key": "gpu_id", "mem": None, "proc": 80.28, "gpu_id": 1, "name": "GeForce GTX 560 Ti"},
92
        #               {"key": "gpu_id", "mem": 0, "proc": 0, "gpu_id": 2, "name": "ANOTHER GPU"}]
93
        # !!! TO BE COMMENTED
94
95
        if not self.nvml_ready:
96
            return self.stats
97
98
        if self.input_method == 'local':
99
            self.stats = self.get_device_stats()
100
        elif self.input_method == 'snmp':
101
            # not available
102
            pass
103
104
        return self.stats
105
106
    def update_views(self):
107
        """Update stats views."""
108
        # Call the father's method
109
        super(Plugin, self).update_views()
110
111
        # Add specifics informations
112
        # Alert
113
        for i in self.stats:
114
            # Init the views for the current GPU
115
            self.views[i[self.get_key()]] = {'proc': {}, 'mem': {}}
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
125
        return True
126
127
    def msg_curse(self, args=None):
128
        """Return the dict to display in the curse interface."""
129
        # Init the return message
130
        ret = []
131
132
        # Only process if stats exist, not empty (issue #871) and plugin not disabled
133
        if not self.stats or (self.stats == []) or self.is_disable():
134
            return ret
135
136
        # Check if all GPU have the same name
137
        same_name = all(s['name'] == self.stats[0]['name'] for s in self.stats)
138
139
        # gpu_stats contain the first GPU in the list
140
        gpu_stats = self.stats[0]
141
142
        # Header
143
        header = ''
144
        if len(self.stats) > 1:
145
            header += '{} '.format(len(self.stats))
146
        if same_name:
147
            header += '{} {}'.format('GPU', gpu_stats['name'])
148
        else:
149
            header += '{}'.format('GPU')
150
        msg = header[:17]
151
        ret.append(self.curse_add_line(msg, "TITLE"))
152
153
        # Build the string message
154
        if len(self.stats) == 1 or args.meangpu:
155
            # GPU stat summary or mono GPU
156
            # New line
157
            ret.append(self.curse_new_line())
158
            # GPU PROC
159
            try:
160
                mean_proc = sum(s['proc'] for s in self.stats if s is not None) / len(self.stats)
161
            except TypeError:
162
                mean_proc_msg = '{:>4}'.format('N/A')
163
            else:
164
                mean_proc_msg = '{:>3.0f}%'.format(mean_proc)
165
            if len(self.stats) > 1:
166
                msg = '{:13}'.format('proc mean:')
167
            else:
168
                msg = '{:13}'.format('proc:')
169
            ret.append(self.curse_add_line(msg))
170
            ret.append(self.curse_add_line(
171
                mean_proc_msg, self.get_views(item=gpu_stats[self.get_key()],
172
                                              key='proc',
173
                                              option='decoration')))
174
            # New line
175
            ret.append(self.curse_new_line())
176
            # GPU MEM
177
            try:
178
                mean_mem = sum(s['mem'] for s in self.stats if s is not None) / len(self.stats)
179
            except TypeError:
180
                mean_mem_msg = '{:>4}'.format('N/A')
181
            else:
182
                mean_mem_msg = '{:>3.0f}%'.format(mean_mem)
183
            if len(self.stats) > 1:
184
                msg = '{:13}'.format('mem mean:')
185
            else:
186
                msg = '{:13}'.format('mem:')
187
            ret.append(self.curse_add_line(msg))
188
            ret.append(self.curse_add_line(
189
                mean_mem_msg, self.get_views(item=gpu_stats[self.get_key()],
190
                                             key='mem',
191
                                             option='decoration')))
192
        else:
193
            # Multi GPU
194
            for gpu_stats in self.stats:
195
                # New line
196
                ret.append(self.curse_new_line())
197
                # GPU ID + PROC + MEM
198
                id_msg = '{}'.format(gpu_stats['gpu_id'])
199
                try:
200
                    proc_msg = '{:>3.0f}%'.format(gpu_stats['proc'])
201
                except ValueError:
202
                    proc_msg = '{:>4}'.format('N/A')
203
                try:
204
                    mem_msg = '{:>3.0f}%'.format(gpu_stats['mem'])
205
                except ValueError:
206
                    mem_msg = '{:>4}'.format('N/A')
207
                msg = '{}: {} mem: {}'.format(id_msg, proc_msg, mem_msg)
208
                ret.append(self.curse_add_line(msg))
209
210
        return ret
211
212
    def get_device_handles(self):
213
        """
214
        Returns a list of NVML device handles, one per device.  Can throw NVMLError.
215
        """
216
        return [pynvml.nvmlDeviceGetHandleByIndex(i) for i in range(pynvml.nvmlDeviceGetCount())]
217
218
    def get_device_stats(self):
219
        """Get GPU stats"""
220
        stats = []
221
222
        for index, device_handle in enumerate(self.device_handles):
223
            device_stats = {}
224
            # Dictionnary key is the GPU_ID
225
            device_stats['key'] = self.get_key()
226
            # GPU id (for multiple GPU, start at 0)
227
            device_stats['gpu_id'] = index
228
            # GPU name
229
            device_stats['name'] = self.get_device_name(device_handle)
230
            # Memory consumption in % (not available on all GPU)
231
            device_stats['mem'] = self.get_mem(device_handle)
232
            # Processor consumption in %
233
            device_stats['proc'] = self.get_proc(device_handle)
234
            stats.append(device_stats)
235
236
        return stats
237
238
    def get_device_name(self, device_handle):
239
        """Get GPU device name"""
240
        try:
241
            return pynvml.nvmlDeviceGetName(device_handle)
242
        except pynvml.NVMlError:
243
            return "NVIDIA"
244
245
    def get_mem(self, device_handle):
246
        """Get GPU device memory consumption in percent"""
247
        try:
248
            memory_info = pynvml.nvmlDeviceGetMemoryInfo(device_handle)
249
            return memory_info.used * 100.0 / memory_info.total
250
        except pynvml.NVMLError:
251
            return None
252
253
    def get_proc(self, device_handle):
254
        """Get GPU device CPU consumption in percent"""
255
        try:
256
            return pynvml.nvmlDeviceGetUtilizationRates(device_handle).gpu
257
        except pynvml.NVMLError:
258
            return None
259
260
    def exit(self):
261
        """Overwrite the exit method to close the GPU API"""
262
        if self.nvml_ready:
263
            try:
264
                pynvml.nvmlShutdown()
265
            except Exception as e:
266
                logger.debug("pynvml failed to shutdown correctly ({})".format(e))
267
268
        # Call the father exit method
269
        super(Plugin, self).exit()
270