| Total Complexity | 40 | 
| Total Lines | 328 | 
| Duplicated Lines | 47.87 % | 
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like glances.plugins.programlist 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 | # SPDX-FileCopyrightText: 2024 Nicolas Hennion <[email protected]>  | 
            ||
| 5 | #  | 
            ||
| 6 | # SPDX-License-Identifier: LGPL-3.0-only  | 
            ||
| 7 | #  | 
            ||
| 8 | |||
| 9 | """Program list plugin."""  | 
            ||
| 10 | |||
| 11 | import copy  | 
            ||
| 12 | |||
| 13 | from glances.plugins.processlist import PluginModel as GlancesProcessListPluginModel  | 
            ||
| 14 | from glances.processes import glances_processes  | 
            ||
| 15 | |||
| 16 | # Fields description  | 
            ||
| 17 | # description: human readable description  | 
            ||
| 18 | # short_name: shortname to use un UI  | 
            ||
| 19 | # unit: unit type  | 
            ||
| 20 | # rate: if True then compute and add *_gauge and *_rate_per_is fields  | 
            ||
| 21 | # min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)...  | 
            ||
| 22 | fields_description = { | 
            ||
| 23 |     'name': { | 
            ||
| 24 | 'description': 'Process name',  | 
            ||
| 25 | 'unit': 'string',  | 
            ||
| 26 | },  | 
            ||
| 27 |     'cmdline': { | 
            ||
| 28 | 'description': 'Command line with arguments',  | 
            ||
| 29 | 'unit': 'list',  | 
            ||
| 30 | },  | 
            ||
| 31 |     'username': { | 
            ||
| 32 | 'description': 'Process owner',  | 
            ||
| 33 | 'unit': 'string',  | 
            ||
| 34 | },  | 
            ||
| 35 |     'nprocs': { | 
            ||
| 36 | 'description': 'Number of children processes',  | 
            ||
| 37 | 'unit': 'number',  | 
            ||
| 38 | },  | 
            ||
| 39 |     'num_threads': { | 
            ||
| 40 | 'description': 'Number of threads',  | 
            ||
| 41 | 'unit': 'number',  | 
            ||
| 42 | },  | 
            ||
| 43 |     'cpu_percent': { | 
            ||
| 44 | 'description': 'Process CPU consumption',  | 
            ||
| 45 | 'unit': 'percent',  | 
            ||
| 46 | },  | 
            ||
| 47 |     'memory_percent': { | 
            ||
| 48 | 'description': 'Process memory consumption',  | 
            ||
| 49 | 'unit': 'percent',  | 
            ||
| 50 | },  | 
            ||
| 51 |     'memory_info': { | 
            ||
| 52 | 'description': 'Process memory information (dict with rss, vms, shared, text, lib, data, dirty keys)',  | 
            ||
| 53 | 'unit': 'byte',  | 
            ||
| 54 | },  | 
            ||
| 55 |     'status': { | 
            ||
| 56 | 'description': 'Process status',  | 
            ||
| 57 | 'unit': 'string',  | 
            ||
| 58 | },  | 
            ||
| 59 |     'nice': { | 
            ||
| 60 | 'description': 'Process nice value',  | 
            ||
| 61 | 'unit': 'number',  | 
            ||
| 62 | },  | 
            ||
| 63 |     'cpu_times': { | 
            ||
| 64 | 'description': 'Process CPU times (dict with user, system, iowait keys)',  | 
            ||
| 65 | 'unit': 'second',  | 
            ||
| 66 | },  | 
            ||
| 67 |     'gids': { | 
            ||
| 68 | 'description': 'Process group IDs (dict with real, effective, saved keys)',  | 
            ||
| 69 | 'unit': 'number',  | 
            ||
| 70 | },  | 
            ||
| 71 |     'io_counters': { | 
            ||
| 72 | 'description': 'Process IO counters (list with read_count, write_count, read_bytes, write_bytes, io_tag keys)',  | 
            ||
| 73 | 'unit': 'byte',  | 
            ||
| 74 | },  | 
            ||
| 75 | }  | 
            ||
| 76 | |||
| 77 | |||
| 78 | class PluginModel(GlancesProcessListPluginModel):  | 
            ||
| 79 | """Glances' processes plugin.  | 
            ||
| 80 | |||
| 81 | stats is a list  | 
            ||
| 82 | """  | 
            ||
| 83 | |||
| 84 | # Default list of processes stats to be grabbed / displayed  | 
            ||
| 85 | # Can be altered by glances_processes.disable_stats  | 
            ||
| 86 | enable_stats = [  | 
            ||
| 87 | 'cpu_percent',  | 
            ||
| 88 | 'memory_percent',  | 
            ||
| 89 | 'memory_info', # vms and rss  | 
            ||
| 90 | 'nprocs',  | 
            ||
| 91 | 'username',  | 
            ||
| 92 | 'cpu_times',  | 
            ||
| 93 | 'num_threads',  | 
            ||
| 94 | 'nice',  | 
            ||
| 95 | 'status',  | 
            ||
| 96 | 'io_counters', # ior and iow  | 
            ||
| 97 | 'cmdline',  | 
            ||
| 98 | ]  | 
            ||
| 99 | |||
| 100 | # Define the header layout of the processes list columns  | 
            ||
| 101 |     layout_header = { | 
            ||
| 102 |         'cpu': '{:<6} ', | 
            ||
| 103 |         'mem': '{:<5} ', | 
            ||
| 104 |         'virt': '{:<5} ', | 
            ||
| 105 |         'res': '{:<5} ', | 
            ||
| 106 |         'nprocs': '{:>7} ', | 
            ||
| 107 |         'user': '{:<10} ', | 
            ||
| 108 |         'time': '{:>8} ', | 
            ||
| 109 |         'thread': '{:<3} ', | 
            ||
| 110 |         'nice': '{:>3} ', | 
            ||
| 111 |         'status': '{:>1} ', | 
            ||
| 112 |         'ior': '{:>4} ', | 
            ||
| 113 |         'iow': '{:<4} ', | 
            ||
| 114 |         'command': '{} {}', | 
            ||
| 115 | }  | 
            ||
| 116 | |||
| 117 | # Define the stat layout of the processes list columns  | 
            ||
| 118 |     layout_stat = { | 
            ||
| 119 |         'cpu': '{:<6.1f}', | 
            ||
| 120 |         'cpu_no_digit': '{:<6.0f}', | 
            ||
| 121 |         'mem': '{:<5.1f} ', | 
            ||
| 122 |         'virt': '{:<5} ', | 
            ||
| 123 |         'res': '{:<5} ', | 
            ||
| 124 |         'nprocs': '{:>7} ', | 
            ||
| 125 |         'user': '{:<10} ', | 
            ||
| 126 |         'time': '{:>8} ', | 
            ||
| 127 |         'thread': '{:<3} ', | 
            ||
| 128 |         'nice': '{:>3} ', | 
            ||
| 129 |         'status': '{:>1} ', | 
            ||
| 130 |         'ior': '{:>4} ', | 
            ||
| 131 |         'iow': '{:<4} ', | 
            ||
| 132 |         'command': '{}', | 
            ||
| 133 |         'name': '[{}]', | 
            ||
| 134 | }  | 
            ||
| 135 | |||
| 136 | def __init__(self, args=None, config=None):  | 
            ||
| 137 | """Init the plugin."""  | 
            ||
| 138 | super().__init__(args=args, config=config)  | 
            ||
| 139 | |||
| 140 | def get_key(self):  | 
            ||
| 141 | """Return the key of the list."""  | 
            ||
| 142 | return 'name'  | 
            ||
| 143 | |||
| 144 | def update(self):  | 
            ||
| 145 | """Update processes stats using the input method."""  | 
            ||
| 146 | # Update the stats  | 
            ||
| 147 | if self.input_method == 'local':  | 
            ||
| 148 | # Update stats using the standard system lib  | 
            ||
| 149 | # Note: Update is done in the processcount plugin  | 
            ||
| 150 | # Just return the result  | 
            ||
| 151 | stats = glances_processes.get_list(as_programs=True)  | 
            ||
| 152 | else:  | 
            ||
| 153 | stats = self.get_init_value()  | 
            ||
| 154 | |||
| 155 | # Get the max values (dict)  | 
            ||
| 156 | # Use Deep copy to avoid change between update and display  | 
            ||
| 157 | self.max_values = copy.deepcopy(glances_processes.max_values())  | 
            ||
| 158 | |||
| 159 | # Update the stats  | 
            ||
| 160 | self.stats = stats  | 
            ||
| 161 | |||
| 162 | return self.stats  | 
            ||
| 163 | |||
| 164 | def _get_process_curses_nprocs(self, p, selected, args):  | 
            ||
| 165 | """Return process NPROCS curses"""  | 
            ||
| 166 | # Display the number of children processes  | 
            ||
| 167 | msg = self.layout_stat['nprocs'].format(p['nprocs'])  | 
            ||
| 168 | return self.curse_add_line(msg)  | 
            ||
| 169 | |||
| 170 | View Code Duplication | def _msg_curse_header(self, ret, process_sort_key, args=None):  | 
            |
| 
                                                                                                    
                        
                         | 
                |||
| 171 | """Build the header and add it to the ret dict."""  | 
            ||
| 172 | sort_style = 'SORT'  | 
            ||
| 173 | |||
| 174 | display_stats = [i for i in self.enable_stats if i not in glances_processes.disable_stats]  | 
            ||
| 175 | |||
| 176 | if 'cpu_percent' in display_stats:  | 
            ||
| 177 | if args.disable_irix and 0 < self.nb_log_core < 10:  | 
            ||
| 178 |                 msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core)) | 
            ||
| 179 | elif args.disable_irix and self.nb_log_core != 0:  | 
            ||
| 180 |                 msg = self.layout_header['cpu'].format('CPU%/C') | 
            ||
| 181 | else:  | 
            ||
| 182 |                 msg = self.layout_header['cpu'].format('CPU%') | 
            ||
| 183 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))  | 
            ||
| 184 | |||
| 185 | if 'memory_percent' in display_stats:  | 
            ||
| 186 |             msg = self.layout_header['mem'].format('MEM%') | 
            ||
| 187 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))  | 
            ||
| 188 | if 'memory_info' in display_stats:  | 
            ||
| 189 |             msg = self.layout_header['virt'].format('VIRT') | 
            ||
| 190 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 191 |             msg = self.layout_header['res'].format('RES') | 
            ||
| 192 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 193 | if 'nprocs' in display_stats:  | 
            ||
| 194 |             msg = self.layout_header['nprocs'].format('NPROCS') | 
            ||
| 195 | ret.append(self.curse_add_line(msg))  | 
            ||
| 196 | if 'username' in display_stats:  | 
            ||
| 197 |             msg = self.layout_header['user'].format('USER') | 
            ||
| 198 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))  | 
            ||
| 199 | if 'cpu_times' in display_stats:  | 
            ||
| 200 |             msg = self.layout_header['time'].format('TIME+') | 
            ||
| 201 | ret.append(  | 
            ||
| 202 | self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True)  | 
            ||
| 203 | )  | 
            ||
| 204 | if 'num_threads' in display_stats:  | 
            ||
| 205 |             msg = self.layout_header['thread'].format('THR') | 
            ||
| 206 | ret.append(self.curse_add_line(msg))  | 
            ||
| 207 | if 'nice' in display_stats:  | 
            ||
| 208 |             msg = self.layout_header['nice'].format('NI') | 
            ||
| 209 | ret.append(self.curse_add_line(msg))  | 
            ||
| 210 | if 'status' in display_stats:  | 
            ||
| 211 |             msg = self.layout_header['status'].format('S') | 
            ||
| 212 | ret.append(self.curse_add_line(msg))  | 
            ||
| 213 | if 'io_counters' in display_stats:  | 
            ||
| 214 |             msg = self.layout_header['ior'].format('R/s') | 
            ||
| 215 | ret.append(  | 
            ||
| 216 | self.curse_add_line(  | 
            ||
| 217 | msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True  | 
            ||
| 218 | )  | 
            ||
| 219 | )  | 
            ||
| 220 |             msg = self.layout_header['iow'].format('W/s') | 
            ||
| 221 | ret.append(  | 
            ||
| 222 | self.curse_add_line(  | 
            ||
| 223 | msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True  | 
            ||
| 224 | )  | 
            ||
| 225 | )  | 
            ||
| 226 | if args.is_standalone and not args.disable_cursor:  | 
            ||
| 227 |             shortkey = "('k' to kill)" | 
            ||
| 228 | else:  | 
            ||
| 229 | shortkey = ""  | 
            ||
| 230 | if 'cmdline' in display_stats:  | 
            ||
| 231 |             msg = self.layout_header['command'].format("Programs", shortkey) | 
            ||
| 232 | ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))  | 
            ||
| 233 | |||
| 234 | View Code Duplication | def _msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):  | 
            |
| 235 | """  | 
            ||
| 236 | Build the sum message (only when filter is on) and add it to the ret dict.  | 
            ||
| 237 | |||
| 238 | :param ret: list of string where the message is added  | 
            ||
| 239 | :param sep_char: define the line separation char  | 
            ||
| 240 | :param mmm: display min, max, mean or current (if mmm=None)  | 
            ||
| 241 | :param args: Glances args  | 
            ||
| 242 | """  | 
            ||
| 243 | ret.append(self.curse_new_line())  | 
            ||
| 244 | if mmm is None:  | 
            ||
| 245 | ret.append(self.curse_add_line(sep_char * 69))  | 
            ||
| 246 | ret.append(self.curse_new_line())  | 
            ||
| 247 | # CPU percent sum  | 
            ||
| 248 | msg = ' '  | 
            ||
| 249 |         msg += self.layout_stat['cpu'].format(self._sum_stats('cpu_percent', mmm=mmm)) | 
            ||
| 250 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm)))  | 
            ||
| 251 | # MEM percent sum  | 
            ||
| 252 |         msg = self.layout_stat['mem'].format(self._sum_stats('memory_percent', mmm=mmm)) | 
            ||
| 253 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm)))  | 
            ||
| 254 | # VIRT and RES memory sum  | 
            ||
| 255 | if (  | 
            ||
| 256 | 'memory_info' in self.stats[0]  | 
            ||
| 257 | and self.stats[0]['memory_info'] is not None  | 
            ||
| 258 | and self.stats[0]['memory_info'] != ''  | 
            ||
| 259 | ):  | 
            ||
| 260 | # VMS  | 
            ||
| 261 | msg = self.layout_stat['virt'].format(  | 
            ||
| 262 |                 self.auto_unit(self._sum_stats('memory_info', sub_key='vms', mmm=mmm), low_precision=False) | 
            ||
| 263 | )  | 
            ||
| 264 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True))  | 
            ||
| 265 | # RSS  | 
            ||
| 266 | msg = self.layout_stat['res'].format(  | 
            ||
| 267 |                 self.auto_unit(self._sum_stats('memory_info', sub_key='rss', mmm=mmm), low_precision=False) | 
            ||
| 268 | )  | 
            ||
| 269 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True))  | 
            ||
| 270 | else:  | 
            ||
| 271 |             msg = self.layout_header['virt'].format('') | 
            ||
| 272 | ret.append(self.curse_add_line(msg))  | 
            ||
| 273 |             msg = self.layout_header['res'].format('') | 
            ||
| 274 | ret.append(self.curse_add_line(msg))  | 
            ||
| 275 | # PID  | 
            ||
| 276 |         msg = self.layout_header['nprocs'].format('') | 
            ||
| 277 | ret.append(self.curse_add_line(msg))  | 
            ||
| 278 | # USER  | 
            ||
| 279 |         msg = self.layout_header['user'].format('') | 
            ||
| 280 | ret.append(self.curse_add_line(msg))  | 
            ||
| 281 | # TIME+  | 
            ||
| 282 |         msg = self.layout_header['time'].format('') | 
            ||
| 283 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 284 | # THREAD  | 
            ||
| 285 |         msg = self.layout_header['thread'].format('') | 
            ||
| 286 | ret.append(self.curse_add_line(msg))  | 
            ||
| 287 | # NICE  | 
            ||
| 288 |         msg = self.layout_header['nice'].format('') | 
            ||
| 289 | ret.append(self.curse_add_line(msg))  | 
            ||
| 290 | # STATUS  | 
            ||
| 291 |         msg = self.layout_header['status'].format('') | 
            ||
| 292 | ret.append(self.curse_add_line(msg))  | 
            ||
| 293 | # IO read/write  | 
            ||
| 294 | if 'io_counters' in self.stats[0] and mmm is None:  | 
            ||
| 295 | # IO read  | 
            ||
| 296 | io_rs = int(  | 
            ||
| 297 |                 (self._sum_stats('io_counters', 0) - self._sum_stats('io_counters', sub_key=2, mmm=mmm)) | 
            ||
| 298 | / self.stats[0]['time_since_update']  | 
            ||
| 299 | )  | 
            ||
| 300 | if io_rs == 0:  | 
            ||
| 301 |                 msg = self.layout_stat['ior'].format('0') | 
            ||
| 302 | else:  | 
            ||
| 303 | msg = self.layout_stat['ior'].format(self.auto_unit(io_rs, low_precision=True))  | 
            ||
| 304 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True, additional=True))  | 
            ||
| 305 | # IO write  | 
            ||
| 306 | io_ws = int(  | 
            ||
| 307 |                 (self._sum_stats('io_counters', 1) - self._sum_stats('io_counters', sub_key=3, mmm=mmm)) | 
            ||
| 308 | / self.stats[0]['time_since_update']  | 
            ||
| 309 | )  | 
            ||
| 310 | if io_ws == 0:  | 
            ||
| 311 |                 msg = self.layout_stat['iow'].format('0') | 
            ||
| 312 | else:  | 
            ||
| 313 | msg = self.layout_stat['iow'].format(self.auto_unit(io_ws, low_precision=True))  | 
            ||
| 314 | ret.append(self.curse_add_line(msg, decoration=self._mmm_deco(mmm), optional=True, additional=True))  | 
            ||
| 315 | else:  | 
            ||
| 316 |             msg = self.layout_header['ior'].format('') | 
            ||
| 317 | ret.append(self.curse_add_line(msg, optional=True, additional=True))  | 
            ||
| 318 |             msg = self.layout_header['iow'].format('') | 
            ||
| 319 | ret.append(self.curse_add_line(msg, optional=True, additional=True))  | 
            ||
| 320 | if mmm is None:  | 
            ||
| 321 |             msg = '< {}'.format('current') | 
            ||
| 322 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 323 | else:  | 
            ||
| 324 |             msg = f'< {mmm}' | 
            ||
| 325 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 326 | msg = '(\'M\' to reset)'  | 
            ||
| 327 | ret.append(self.curse_add_line(msg, optional=True))  | 
            ||
| 328 |