Test Failed
Push — develop ( d07c77...76509e )
by Nicolas
02:39 queued 16s
created

glances.plugins.gpu.PluginModel.__init__()   A

Complexity

Conditions 1

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nop 3
dl 0
loc 14
rs 9.95
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2020 Kirby Banman <[email protected]>
6
# Copyright (C) 2024 Nicolas Hennion <[email protected]>
7
#
8
# SPDX-License-Identifier: LGPL-3.0-only
9
#
10
11
"""GPU plugin for Glances.
12
13
Currently supported:
14
- NVIDIA GPU (need pynvml lib)
15
- AMD GPU (no lib needed)
16
"""
17
18
from glances.logger import logger
19
from glances.globals import to_fahrenheit
20
from glances.plugins.gpu.cards.nvidia import NvidiaGPU
21
from glances.plugins.gpu.cards.amd import AmdGPU
22
from glances.plugins.plugin.model import GlancesPluginModel
23
24
# Fields description
25
# description: human readable description
26
# short_name: shortname to use un UI
27
# unit: unit type
28
# rate: is it a rate ? If yes, // by time_since_update when displayed,
29
# min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)...
30
fields_description = {
31
    'gpu_id': {
32
        'description': 'GPU identification',
33
    },
34
    'name': {
35
        'description': 'GPU name',
36
    },
37
    'mem': {
38
        'description': 'Memory consumption',
39
        'unit': 'percent',
40
    },
41
    'proc': {
42
        'description': 'GPU processor consumption',
43
        'unit': 'percent',
44
    },
45
    'temperature': {
46
        'description': 'GPU temperature',
47
        'unit': 'celsius',
48
    },
49
    'fan_speed': {
50
        'description': 'GPU fan speed',
51
        'unit': 'roundperminute',
52
    },
53
}
54
55
# Define the history items list
56
# All items in this list will be historised if the --enable-history tag is set
57
items_history_list = [
58
    {'name': 'proc', 'description': 'GPU processor', 'y_unit': '%'},
59
    {'name': 'mem', 'description': 'Memory consumption', 'y_unit': '%'},
60
]
61
62
63
class PluginModel(GlancesPluginModel):
64
    """Glances GPU plugin.
65
66
    stats is a list of dictionaries with one entry per GPU
67
    """
68
69
    def __init__(self, args=None, config=None):
70
        """Init the plugin."""
71
        super(PluginModel, self).__init__(
72
            args=args, config=config,
73
            items_history_list=items_history_list,
74
            stats_init_value=[],
75
            fields_description=fields_description
76
        )
77
        # Init the GPU API
78
        self.nvidia = NvidiaGPU()
79
        self.amd = AmdGPU()
80
81
        # We want to display the stat in the curse interface
82
        self.display_curse = True
83
84
    def exit(self):
85
        """Overwrite the exit method to close the GPU API."""
86
        self.nvidia.exit()
87
        self.amd.exit()
88
89
        # Call the father exit method
90
        super(PluginModel, self).exit()
91
92
    def get_key(self):
93
        """Return the key of the list."""
94
        return 'gpu_id'
95
96
    @GlancesPluginModel._check_decorator
97
    @GlancesPluginModel._log_result_decorator
98
    def update(self):
99
        """Update the GPU stats."""
100
        # Init new stats
101
        stats = self.get_init_value()
102
103
        # Get the stats
104
        stats.extend(self.nvidia.get_device_stats())
105
        stats.extend(self.amd.get_device_stats())
106
107
        # !!!
108
        # Uncomment to test on computer without GPU
109
        # One GPU sample:
110
        # stats = [
111
        #     {
112
        #         "key": "gpu_id",
113
        #         "gpu_id": "nvidia0",
114
        #         "name": "Fake GeForce GTX",
115
        #         "mem": 5.792331695556641,
116
        #         "proc": 4,
117
        #         "temperature": 26,
118
        #         "fan_speed": 30
119
        #     }
120
        # ]
121
        # Two GPU sample:
122
        # stats = [
123
        #     {
124
        #         "key": "gpu_id",
125
        #         "gpu_id": "nvidia0",
126
        #         "name": "Fake GeForce GTX1",
127
        #         "mem": 5.792331695556641,
128
        #         "proc": 4,
129
        #         "temperature": 26,
130
        #         "fan_speed": 30
131
        #     },
132
        #     {
133
        #         "key": "gpu_id",
134
        #         "gpu_id": "nvidia1",
135
        #         "name": "Fake GeForce GTX2",
136
        #         "mem": 15,
137
        #         "proc": 8,
138
        #         "temperature": 65,
139
        #         "fan_speed": 75
140
        #     }
141
        # ]
142
143
        # Update the stats
144
        self.stats = stats
145
146
        return self.stats
147
148
    def update_views(self):
149
        """Update stats views."""
150
        # Call the father's method
151
        super(PluginModel, self).update_views()
152
153
        # Add specifics information
154
        # Alert
155
        for i in self.stats:
156
            # Init the views for the current GPU
157
            self.views[i[self.get_key()]] = {'proc': {}, 'mem': {}, 'temperature': {}}
158
            # Processor alert
159
            if 'proc' in i:
160
                alert = self.get_alert(i['proc'], header='proc')
161
                self.views[i[self.get_key()]]['proc']['decoration'] = alert
162
            # Memory alert
163
            if 'mem' in i:
164
                alert = self.get_alert(i['mem'], header='mem')
165
                self.views[i[self.get_key()]]['mem']['decoration'] = alert
166
            # Temperature alert
167
            if 'temperature' in i:
168
                alert = self.get_alert(i['temperature'], header='temperature')
169
                self.views[i[self.get_key()]]['temperature']['decoration'] = alert
170
171
        return True
172
173
    def msg_curse(self, args=None, max_width=None):
174
        """Return the dict to display in the curse interface."""
175
        # Init the return message
176
        ret = []
177
178
        # Only process if stats exist, not empty (issue #871) and plugin not disabled
179
        if not self.stats or (self.stats == []) or self.is_disabled():
180
            return ret
181
182
        # Check if all GPU have the same name
183
        same_name = all(s['name'] == self.stats[0]['name'] for s in self.stats)
184
185
        # gpu_stats contain the first GPU in the list
186
        gpu_stats = self.stats[0]
187
188
        # Header
189
        header = ''
190
        if len(self.stats) > 1:
191
            header += '{} {}'.format(len(self.stats),
192
                                     'GPUs' if len(self.stats) > 1 else 'GPU')
193
        if same_name:
194
            header += ' {}'.format(gpu_stats['name'])
195
        msg = header[:17]
196
        ret.append(self.curse_add_line(msg, "TITLE"))
197
198
        # Build the string message
199
        if len(self.stats) == 1 or args.meangpu:
200
            # GPU stat summary or mono GPU
201
            # New line
202
            ret.append(self.curse_new_line())
203
            # GPU PROC
204
            try:
205
                mean_proc = sum(s['proc'] for s in self.stats if s is not None) / len(self.stats)
206
            except TypeError:
207
                mean_proc_msg = '{:>4}'.format('N/A')
208
            else:
209
                mean_proc_msg = '{:>3.0f}%'.format(mean_proc)
210
            if len(self.stats) > 1:
211
                msg = '{:13}'.format('proc mean:')
212
            else:
213
                msg = '{:13}'.format('proc:')
214
            ret.append(self.curse_add_line(msg))
215
            ret.append(
216
                self.curse_add_line(
217
                    mean_proc_msg, self.get_views(item=gpu_stats[self.get_key()], key='proc', option='decoration')
218
                )
219
            )
220
            # New line
221
            ret.append(self.curse_new_line())
222
            # GPU MEM
223
            try:
224
                mean_mem = sum(s['mem'] for s in self.stats if s is not None) / len(self.stats)
225
            except TypeError:
226
                mean_mem_msg = '{:>4}'.format('N/A')
227
            else:
228
                mean_mem_msg = '{:>3.0f}%'.format(mean_mem)
229
            if len(self.stats) > 1:
230
                msg = '{:13}'.format('mem mean:')
231
            else:
232
                msg = '{:13}'.format('mem:')
233
            ret.append(self.curse_add_line(msg))
234
            ret.append(
235
                self.curse_add_line(
236
                    mean_mem_msg, self.get_views(item=gpu_stats[self.get_key()], key='mem', option='decoration')
237
                )
238
            )
239
            # New line
240
            ret.append(self.curse_new_line())
241
            # GPU TEMPERATURE
242
            try:
243
                mean_temperature = sum(s['temperature'] for s in self.stats if s is not None) / len(self.stats)
244
            except TypeError:
245
                mean_temperature_msg = '{:>4}'.format('N/A')
246
            else:
247
                unit = 'C'
248
                if args.fahrenheit:
249
                    mean_temperature = to_fahrenheit(mean_temperature)
250
                    unit = 'F'
251
                mean_temperature_msg = '{:>3.0f}{}'.format(mean_temperature, unit)
252
            if len(self.stats) > 1:
253
                msg = '{:13}'.format('temp mean:')
254
            else:
255
                msg = '{:13}'.format('temperature:')
256
            ret.append(self.curse_add_line(msg))
257
            ret.append(
258
                self.curse_add_line(
259
                    mean_temperature_msg,
260
                    self.get_views(item=gpu_stats[self.get_key()], key='temperature', option='decoration'),
261
                )
262
            )
263
        else:
264
            # Multi GPU
265
            # Temperature is not displayed in this mode...
266
            for gpu_stats in self.stats:
267
                # New line
268
                ret.append(self.curse_new_line())
269
                # GPU ID + PROC + MEM + TEMPERATURE
270
                id_msg = '{}'.format(gpu_stats['gpu_id'])
271
                try:
272
                    proc_msg = '{:>3.0f}%'.format(gpu_stats['proc'])
273
                except (ValueError, TypeError):
274
                    proc_msg = '{:>4}'.format('N/A')
275
                try:
276
                    mem_msg = '{:>3.0f}%'.format(gpu_stats['mem'])
277
                except (ValueError, TypeError):
278
                    mem_msg = '{:>4}'.format('N/A')
279
                msg = '{}: {} mem: {}'.format(id_msg, proc_msg, mem_msg)
280
                ret.append(self.curse_add_line(msg))
281
282
        return ret
283