| 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 ProcesslistPlugin |
||
| 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 ProgramlistPlugin(ProcesslistPlugin): |
||
| 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 |