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

Complexity

Conditions 1

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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