glances.plugins.gpu.GpuPlugin.msg_curse()   F
last analyzed

Complexity

Conditions 22

Size

Total Lines 115
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

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