glances_processlist.py 28.4 KB
Newer Older
A
Alessio Sergi 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
A
Alessio Sergi 已提交
4
#
A
Alessio Sergi 已提交
5
# Copyright (C) 2017 Nicolargo <nicolas@nicolargo.com>
A
Alessio Sergi 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

A
PEP 257  
Alessio Sergi 已提交
20 21
"""Process list plugin."""

22
import os
23
import shlex
N
nicolargo 已提交
24
import copy
A
Alessio Sergi 已提交
25 26
from datetime import timedelta

27
from glances.compat import iteritems
28
from glances.globals import WINDOWS
29
from glances.logger import logger
30
from glances.processes import glances_processes, sort_stats
31
from glances.plugins.glances_core import Plugin as CorePlugin
A
Alessio Sergi 已提交
32
from glances.plugins.glances_plugin import GlancesPlugin
A
Alessio Sergi 已提交
33

D
desbma 已提交
34

35 36 37 38 39 40 41 42 43 44 45
def convert_timedelta(delta):
    """Convert timedelta to human-readable time."""
    days, total_seconds = delta.days, delta.seconds
    hours = days * 24 + total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    seconds = str(total_seconds % 60).zfill(2)
    microseconds = str(delta.microseconds)[:2].zfill(2)

    return hours, minutes, seconds, microseconds


46 47
def split_cmdline(cmdline):
    """Return path, cmd and arguments for a process cmdline."""
48 49 50 51 52 53 54 55 56 57
    # There is an issue in PsUtil for Electron/Atom processes (maybe others...)
    # Tracked by https://github.com/nicolargo/glances/issues/1192
    #            https://github.com/giampaolo/psutil/issues/1179
    # Add this dirty workarround (to be removed when the PsUtil is solved)
    if len(cmdline) == 1:
        cmdline = shlex.split(cmdline[0])
    # /End of the direty workarround

    path, cmd = os.path.split(cmdline[0])
    arguments = ' '.join(cmdline[1:])
58 59 60
    return path, cmd, arguments


A
Alessio Sergi 已提交
61
class Plugin(GlancesPlugin):
A
PEP 257  
Alessio Sergi 已提交
62
    """Glances' processes plugin.
A
Alessio Sergi 已提交
63 64 65 66

    stats is a list
    """

67
    def __init__(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
68
        """Init the plugin."""
A
Alessio Sergi 已提交
69
        super(Plugin, self).__init__(args=args)
A
Alessio Sergi 已提交
70 71 72 73

        # We want to display the stat in the curse interface
        self.display_curse = True

74 75 76
        # Trying to display proc time
        self.tag_proc_time = True

77 78 79 80 81 82
        # Call CorePlugin to get the core number (needed when not in IRIX mode / Solaris mode)
        try:
            self.nb_log_core = CorePlugin(args=self.args).update()["log"]
        except Exception:
            self.nb_log_core = 0

83
        # Get the max values (dict)
N
nicolargo 已提交
84
        self.max_values = copy.deepcopy(glances_processes.max_values())
85

86 87 88 89
        # Get the maximum PID number
        # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)
        self.pid_max = glances_processes.pid_max

90
        # Note: 'glances_processes' is already init in the processes.py script
91

92
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
93
        """Return the key of the list."""
94 95
        return 'pid'

96
    def reset(self):
A
PEP 257  
Alessio Sergi 已提交
97
        """Reset/init the stats."""
98
        self.stats = []
99 100

    def update(self):
A
PEP 257  
Alessio Sergi 已提交
101
        """Update processes stats using the input method."""
102 103
        # Reset stats
        self.reset()
104

105
        if self.input_method == 'local':
106 107 108
            # Update stats using the standard system lib
            # Note: Update is done in the processcount plugin
            # Just return the processes list
109
            if glances_processes.is_tree_enabled():
D
desbma 已提交
110 111 112
                self.stats = glances_processes.gettree()
            else:
                self.stats = glances_processes.getlist()
113 114

            # Get the max values (dict)
N
nicolargo 已提交
115 116
            # Use Deep copy to avoid change between update and display
            self.max_values = copy.deepcopy(glances_processes.max_values())
117

118
        elif self.input_method == 'snmp':
N
Nicolargo 已提交
119
            # No SNMP grab for processes
120
            pass
121

122
        return self.stats
123

124
    def get_process_tree_curses_data(self, node, args, first_level=True, max_node_count=None):
A
PEP 257  
Alessio Sergi 已提交
125
        """Get curses data to display for a process tree."""
D
desbma 已提交
126
        ret = []
127
        node_count = 0
N
nicolargo 已提交
128
        if not node.is_root and ((max_node_count is None) or (max_node_count > 0)):
D
desbma 已提交
129
            node_data = self.get_process_curses_data(node.stats, False, args)
130
            node_count += 1
D
desbma 已提交
131
            ret.extend(node_data)
A
Alessio Sergi 已提交
132
        for child in node.iter_children():
133
            # stop if we have enough nodes to display
N
nicolargo 已提交
134
            if max_node_count is not None and node_count >= max_node_count:
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
                break

            if max_node_count is None:
                children_max_node_count = None
            else:
                children_max_node_count = max_node_count - node_count
            child_data = self.get_process_tree_curses_data(child,
                                                           args,
                                                           first_level=node.is_root,
                                                           max_node_count=children_max_node_count)
            if max_node_count is None:
                node_count += len(child)
            else:
                node_count += min(children_max_node_count, len(child))

D
desbma 已提交
150
            if not node.is_root:
D
desbma 已提交
151
                child_data = self.add_tree_decoration(child_data, child is node.children[-1], first_level)
D
desbma 已提交
152 153 154
            ret.extend(child_data)
        return ret

D
desbma 已提交
155
    def add_tree_decoration(self, child_data, is_last_child, first_level):
A
PEP 257  
Alessio Sergi 已提交
156
        """Add tree curses decoration and indentation to a subtree."""
D
desbma 已提交
157 158 159
        # find process command indices in messages
        pos = []
        for i, m in enumerate(child_data):
D
desbma 已提交
160 161 162
            if m.get("_tree_decoration", False):
                del m["_tree_decoration"]
                pos.append(i)
D
desbma 已提交
163

D
desbma 已提交
164 165 166 167 168 169 170
        # add new curses items for tree decoration
        new_child_data = []
        new_pos = []
        for i, m in enumerate(child_data):
            if i in pos:
                new_pos.append(len(new_child_data))
                new_child_data.append(self.curse_add_line(""))
D
desbma 已提交
171
                new_child_data[-1]["_tree_decoration"] = True
D
desbma 已提交
172 173 174 175
            new_child_data.append(m)
        child_data = new_child_data
        pos = new_pos

D
desbma 已提交
176 177 178 179 180 181 182 183 184 185 186
        if pos:
            # draw node prefix
            if is_last_child:
                prefix = "└─"
            else:
                prefix = "├─"
            child_data[pos[0]]["msg"] = prefix

            # add indentation
            for i in pos:
                spacing = 2
D
desbma 已提交
187
                if first_level:
D
desbma 已提交
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
                    spacing = 1
                elif is_last_child and (i is not pos[0]):
                    # compensate indentation for missing '│' char
                    spacing = 3
                child_data[i]["msg"] = "%s%s" % (" " * spacing, child_data[i]["msg"])

            if not is_last_child:
                # add '│' tree decoration
                for i in pos[1:]:
                    old_str = child_data[i]["msg"]
                    if first_level:
                        child_data[i]["msg"] = " │" + old_str[2:]
                    else:
                        child_data[i]["msg"] = old_str[:2] + "│" + old_str[3:]

D
desbma 已提交
203 204
        return child_data

205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    def get_nice_alert(self, value):
        """Return the alert relative to the Nice configuration list"""
        value = str(value)
        try:
            if value in self.get_limit('nice_critical'):
                return 'CRITICAL'
        except KeyError:
            pass
        try:
            if value in self.get_limit('nice_warning'):
                return 'WARNING'
        except KeyError:
            pass
        try:
            if value in self.get_limit('nice_careful'):
                return 'CAREFUL'
        except KeyError:
            pass
        return 'DEFAULT'

D
desbma 已提交
225
    def get_process_curses_data(self, p, first, args):
226
        """Get curses data to display for a process.
227

228 229 230
        - p is the process to display
        - first is a tag=True if the process is the first on the list
        """
231
        ret = [self.curse_new_line()]
D
desbma 已提交
232 233
        # CPU
        if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '':
234
            if args.disable_irix and self.nb_log_core != 0:
235
                msg = '{:>6.1f}'.format(p['cpu_percent'] / float(self.nb_log_core))
236
            else:
237
                msg = '{:>6.1f}'.format(p['cpu_percent'])
238 239
            alert = self.get_alert(p['cpu_percent'],
                                   highlight_zero=False,
N
Nicolargo 已提交
240
                                   is_max=(p['cpu_percent'] == self.max_values['cpu_percent']),
241 242
                                   header="cpu")
            ret.append(self.curse_add_line(msg, alert))
D
desbma 已提交
243
        else:
244
            msg = '{:>6}'.format('?')
D
desbma 已提交
245 246 247
            ret.append(self.curse_add_line(msg))
        # MEM
        if 'memory_percent' in p and p['memory_percent'] is not None and p['memory_percent'] != '':
248
            msg = '{:>6.1f}'.format(p['memory_percent'])
249 250
            alert = self.get_alert(p['memory_percent'],
                                   highlight_zero=False,
N
Nicolargo 已提交
251
                                   is_max=(p['memory_percent'] == self.max_values['memory_percent']),
252 253
                                   header="mem")
            ret.append(self.curse_add_line(msg, alert))
D
desbma 已提交
254
        else:
255
            msg = '{:>6}'.format('?')
D
desbma 已提交
256 257 258 259
            ret.append(self.curse_add_line(msg))
        # VMS/RSS
        if 'memory_info' in p and p['memory_info'] is not None and p['memory_info'] != '':
            # VMS
260
            msg = '{:>6}'.format(self.auto_unit(p['memory_info'][1], low_precision=False))
D
desbma 已提交
261 262
            ret.append(self.curse_add_line(msg, optional=True))
            # RSS
263
            msg = '{:>6}'.format(self.auto_unit(p['memory_info'][0], low_precision=False))
D
desbma 已提交
264 265
            ret.append(self.curse_add_line(msg, optional=True))
        else:
266
            msg = '{:>6}'.format('?')
D
desbma 已提交
267 268 269
            ret.append(self.curse_add_line(msg))
            ret.append(self.curse_add_line(msg))
        # PID
270
        msg = '{:>{width}}'.format(p['pid'], width=self.__max_pid_size() + 1)
D
desbma 已提交
271 272 273 274
        ret.append(self.curse_add_line(msg))
        # USER
        if 'username' in p:
            # docker internal users are displayed as ints only, therefore str()
275
            # Correct issue #886 on Windows OS
276
            msg = ' {:9}'.format(str(p['username'])[:9])
D
desbma 已提交
277 278
            ret.append(self.curse_add_line(msg))
        else:
279
            msg = ' {:9}'.format('?')
D
desbma 已提交
280 281 282 283 284 285
            ret.append(self.curse_add_line(msg))
        # NICE
        if 'nice' in p:
            nice = p['nice']
            if nice is None:
                nice = '?'
286
            msg = '{:>5}'.format(nice)
287 288
            ret.append(self.curse_add_line(msg,
                                           decoration=self.get_nice_alert(nice)))
D
desbma 已提交
289
        else:
290
            msg = '{:>5}'.format('?')
D
desbma 已提交
291 292 293 294
            ret.append(self.curse_add_line(msg))
        # STATUS
        if 'status' in p:
            status = p['status']
295
            msg = '{:>2}'.format(status)
D
desbma 已提交
296 297 298 299 300
            if status == 'R':
                ret.append(self.curse_add_line(msg, decoration='STATUS'))
            else:
                ret.append(self.curse_add_line(msg))
        else:
301
            msg = '{:>2}'.format('?')
D
desbma 已提交
302 303 304 305
            ret.append(self.curse_add_line(msg))
        # TIME+
        if self.tag_proc_time:
            try:
306
                delta = timedelta(seconds=sum(p['cpu_times']))
307 308
            except (OverflowError, TypeError) as e:
                # Catch OverflowError on some Amazon EC2 server
D
desbma 已提交
309
                # See https://github.com/nicolargo/glances/issues/87
A
Alessio Sergi 已提交
310
                # Also catch TypeError on macOS
311
                # See: https://github.com/nicolargo/glances/issues/622
312
                logger.debug("Cannot get TIME+ ({})".format(e))
D
desbma 已提交
313 314
                self.tag_proc_time = False
            else:
315 316
                hours, minutes, seconds, microseconds = convert_timedelta(delta)
                if hours:
317
                    msg = '{:>4}h'.format(hours)
318
                    ret.append(self.curse_add_line(msg, decoration='CPU_TIME', optional=True))
319
                    msg = '{}:{}'.format(str(minutes).zfill(2), seconds)
320
                else:
321
                    msg = '{:>4}:{}.{}'.format(minutes, seconds, microseconds)
D
desbma 已提交
322
        else:
323
            msg = '{:>10}'.format('?')
D
desbma 已提交
324 325
        ret.append(self.curse_add_line(msg, optional=True))
        # IO read/write
326
        if 'io_counters' in p and p['io_counters'][4] == 1 and p['time_since_update'] != 0:
N
nicolargo 已提交
327
            # Display rate if stats is available and io_tag ([4]) == 1
D
desbma 已提交
328
            # IO read
329
            io_rs = int((p['io_counters'][0] - p['io_counters'][2]) / p['time_since_update'])
D
desbma 已提交
330
            if io_rs == 0:
331
                msg = '{:>6}'.format("0")
D
desbma 已提交
332
            else:
333
                msg = '{:>6}'.format(self.auto_unit(io_rs, low_precision=True))
D
desbma 已提交
334 335
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
            # IO write
336
            io_ws = int((p['io_counters'][1] - p['io_counters'][3]) / p['time_since_update'])
D
desbma 已提交
337
            if io_ws == 0:
338
                msg = '{:>6}'.format("0")
D
desbma 已提交
339
            else:
340
                msg = '{:>6}'.format(self.auto_unit(io_ws, low_precision=True))
D
desbma 已提交
341 342
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
        else:
343
            msg = '{:>6}'.format("?")
D
desbma 已提交
344 345 346 347
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
            ret.append(self.curse_add_line(msg, optional=True, additional=True))

        # Command line
348 349 350
        # If no command line for the process is available, fallback to
        # the bare process name instead
        cmdline = p['cmdline']
351
        try:
352 353
            # XXX: remove `cmdline != ['']` when we'll drop support for psutil<4.0.0
            if cmdline and cmdline != ['']:
354
                path, cmd, arguments = split_cmdline(cmdline)
355
                if os.path.isdir(path) and not args.process_short_name:
356
                    msg = ' {}'.format(path) + os.sep
D
desbma 已提交
357
                    ret.append(self.curse_add_line(msg, splittable=True))
D
desbma 已提交
358 359 360
                    if glances_processes.is_tree_enabled():
                        # mark position to add tree decoration
                        ret[-1]["_tree_decoration"] = True
361
                    ret.append(self.curse_add_line(cmd, decoration='PROCESS', splittable=True))
D
desbma 已提交
362
                else:
363
                    msg = ' {}'.format(cmd)
364
                    ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True))
D
desbma 已提交
365 366 367
                    if glances_processes.is_tree_enabled():
                        # mark position to add tree decoration
                        ret[-1]["_tree_decoration"] = True
368
                if arguments:
369
                    msg = ' {}'.format(arguments)
370
                    ret.append(self.curse_add_line(msg, splittable=True))
371
            else:
372
                msg = ' {}'.format(p['name'])
373
                ret.append(self.curse_add_line(msg, splittable=True))
374
        except UnicodeEncodeError:
375
            ret.append(self.curse_add_line('', splittable=True))
D
desbma 已提交
376 377 378 379 380 381 382 383 384 385

        # Add extended stats but only for the top processes
        # !!! CPU consumption ???
        # TODO: extended stats into the web interface
        if first and 'extended_stats' in p:
            # Left padding
            xpad = ' ' * 13
            # First line is CPU affinity
            if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
386
                msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
D
desbma 已提交
387 388
                ret.append(self.curse_add_line(msg, splittable=True))
            # Second line is memory info
389
            if 'memory_info' in p and p['memory_info'] is not None:
D
desbma 已提交
390
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
391
                msg = xpad + 'Memory info: '
392
                for k, v in iteritems(p['memory_info']._asdict()):
D
desbma 已提交
393 394 395 396
                    # Ignore rss and vms (already displayed)
                    if k not in ['rss', 'vms'] and v is not None:
                        msg += k + ' ' + self.auto_unit(v, low_precision=False) + ' '
                if 'memory_swap' in p and p['memory_swap'] is not None:
A
Alessio Sergi 已提交
397
                    msg += 'swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
D
desbma 已提交
398 399 400 401
                ret.append(self.curse_add_line(msg, splittable=True))
            # Third line is for open files/network sessions
            msg = ''
            if 'num_threads' in p and p['num_threads'] is not None:
A
Alessio Sergi 已提交
402
                msg += 'threads ' + str(p['num_threads']) + ' '
D
desbma 已提交
403
            if 'num_fds' in p and p['num_fds'] is not None:
A
Alessio Sergi 已提交
404
                msg += 'files ' + str(p['num_fds']) + ' '
D
desbma 已提交
405
            if 'num_handles' in p and p['num_handles'] is not None:
A
Alessio Sergi 已提交
406
                msg += 'handles ' + str(p['num_handles']) + ' '
D
desbma 已提交
407
            if 'tcp' in p and p['tcp'] is not None:
A
Alessio Sergi 已提交
408
                msg += 'TCP ' + str(p['tcp']) + ' '
D
desbma 已提交
409
            if 'udp' in p and p['udp'] is not None:
A
Alessio Sergi 已提交
410
                msg += 'UDP ' + str(p['udp']) + ' '
D
desbma 已提交
411 412
            if msg != '':
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
413
                msg = xpad + 'Open: ' + msg
D
desbma 已提交
414 415 416 417
                ret.append(self.curse_add_line(msg, splittable=True))
            # Fouth line is IO nice level (only Linux and Windows OS)
            if 'ionice' in p and p['ionice'] is not None:
                ret.append(self.curse_new_line())
A
Alessio Sergi 已提交
418 419
                msg = xpad + 'IO nice: '
                k = 'Class is '
D
desbma 已提交
420 421 422
                v = p['ionice'].ioclass
                # Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
                # Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
A
Alessio Sergi 已提交
423
                if WINDOWS:
D
desbma 已提交
424 425 426 427 428
                    if v == 0:
                        msg += k + 'Very Low'
                    elif v == 1:
                        msg += k + 'Low'
                    elif v == 2:
A
Alessio Sergi 已提交
429
                        msg += 'No specific I/O priority'
D
desbma 已提交
430 431 432 433
                    else:
                        msg += k + str(v)
                else:
                    if v == 0:
A
Alessio Sergi 已提交
434
                        msg += 'No specific I/O priority'
D
desbma 已提交
435 436 437 438 439 440 441 442 443 444 445
                    elif v == 1:
                        msg += k + 'Real Time'
                    elif v == 2:
                        msg += k + 'Best Effort'
                    elif v == 3:
                        msg += k + 'IDLE'
                    else:
                        msg += k + str(v)
                #  value is a number which goes from 0 to 7.
                # The higher the value, the lower the I/O priority of the process.
                if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
A
Alessio Sergi 已提交
446
                    msg += ' (value %s/7)' % str(p['ionice'].value)
D
desbma 已提交
447 448 449 450
                ret.append(self.curse_add_line(msg, splittable=True))

        return ret

451
    def msg_curse(self, args=None, max_width=None):
A
PEP 257  
Alessio Sergi 已提交
452
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
453 454 455
        # Init the return message
        ret = []

456
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
457
        if not self.stats or args.disable_process:
458 459
            return ret

A
Alessio Sergi 已提交
460
        # Compute the sort key
461
        process_sort_key = glances_processes.sort_key
A
Alessio Sergi 已提交
462 463

        # Header
464 465 466 467 468
        self.__msg_curse_header(ret, process_sort_key, args)

        # Process list
        if glances_processes.is_tree_enabled():
            ret.extend(self.get_process_tree_curses_data(
469
                self.__sort_stats(process_sort_key), args, first_level=True,
470 471 472 473
                max_node_count=glances_processes.max_processes))
        else:
            # Loop over processes (sorted by the sort key previously compute)
            first = True
474
            for p in self.__sort_stats(process_sort_key):
475 476 477 478
                ret.extend(self.get_process_curses_data(p, first, args))
                # End of extended stats
                first = False
            if glances_processes.process_filter is not None:
479 480 481
                if args.reset_minmax_tag:
                    args.reset_minmax_tag = not args.reset_minmax_tag
                    self.__mmm_reset()
482
                self.__msg_curse_sum(ret, args=args)
N
nicolargo 已提交
483 484
                self.__msg_curse_sum(ret, mmm='min', args=args)
                self.__msg_curse_sum(ret, mmm='max', args=args)
485 486 487 488 489

        # Return the message with decoration
        return ret

    def __msg_curse_header(self, ret, process_sort_key, args=None):
490
        """Build the header and add it to the ret dict."""
491 492
        sort_style = 'SORT'

493
        if args.disable_irix and 0 < self.nb_log_core < 10:
494
            msg = '{:>6}'.format('CPU%/' + str(self.nb_log_core))
495
        elif args.disable_irix and self.nb_log_core != 0:
496
            msg = '{:>6}'.format('CPU%/C')
497
        else:
498
            msg = '{:>6}'.format('CPU%')
A
Alessio Sergi 已提交
499
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
500
        msg = '{:>6}'.format('MEM%')
A
Alessio Sergi 已提交
501
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
502
        msg = '{:>6}'.format('VIRT')
A
Alessio Sergi 已提交
503
        ret.append(self.curse_add_line(msg, optional=True))
504
        msg = '{:>6}'.format('RES')
A
Alessio Sergi 已提交
505
        ret.append(self.curse_add_line(msg, optional=True))
506
        msg = '{:>{width}}'.format('PID', width=self.__max_pid_size() + 1)
507
        ret.append(self.curse_add_line(msg))
508
        msg = ' {:10}'.format('USER')
A
Alessio Sergi 已提交
509
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
510
        msg = '{:>4}'.format('NI')
511
        ret.append(self.curse_add_line(msg))
512
        msg = '{:>2}'.format('S')
513
        ret.append(self.curse_add_line(msg))
514
        msg = '{:>10}'.format('TIME+')
515
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True))
516
        msg = '{:>6}'.format('R/s')
517
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
518
        msg = '{:>6}'.format('W/s')
519
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
520
        msg = ' {:8}'.format('Command')
521
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
A
Alessio Sergi 已提交
522

N
nicolargo 已提交
523
    def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
524
        """
525 526
        Build the sum message (only when filter is on) and add it to the ret dict.

N
nicolargo 已提交
527 528 529 530
        * ret: list of string where the message is added
        * sep_char: define the line separation char
        * mmm: display min, max, mean or current (if mmm=None)
        * args: Glances args
531 532
        """
        ret.append(self.curse_new_line())
N
nicolargo 已提交
533 534 535
        if mmm is None:
            ret.append(self.curse_add_line(sep_char * 69))
            ret.append(self.curse_new_line())
536
        # CPU percent sum
537
        msg = '{:>6.1f}'.format(self.__sum_stats('cpu_percent', mmm=mmm))
N
nicolargo 已提交
538 539
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
540
        # MEM percent sum
541
        msg = '{:>6.1f}'.format(self.__sum_stats('memory_percent', mmm=mmm))
N
nicolargo 已提交
542 543
        ret.append(self.curse_add_line(msg,
                                       decoration=self.__mmm_deco(mmm)))
544 545 546
        # VIRT and RES memory sum
        if 'memory_info' in self.stats[0] and self.stats[0]['memory_info'] is not None and self.stats[0]['memory_info'] != '':
            # VMS
547
            msg = '{:>6}'.format(self.auto_unit(self.__sum_stats('memory_info', indice=1, mmm=mmm), low_precision=False))
N
nicolargo 已提交
548 549 550
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
551
            # RSS
552
            msg = '{:>6}'.format(self.auto_unit(self.__sum_stats('memory_info', indice=0, mmm=mmm), low_precision=False))
N
nicolargo 已提交
553 554 555
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True))
D
desbma 已提交
556
        else:
557
            msg = '{:>6}'.format('')
558 559 560
            ret.append(self.curse_add_line(msg))
            ret.append(self.curse_add_line(msg))
        # PID
561
        msg = '{:>6}'.format('')
562 563
        ret.append(self.curse_add_line(msg))
        # USER
564
        msg = ' {:9}'.format('')
565 566
        ret.append(self.curse_add_line(msg))
        # NICE
567
        msg = '{:>5}'.format('')
568 569
        ret.append(self.curse_add_line(msg))
        # STATUS
570
        msg = '{:>2}'.format('')
571 572
        ret.append(self.curse_add_line(msg))
        # TIME+
573
        msg = '{:>10}'.format('')
574 575
        ret.append(self.curse_add_line(msg, optional=True))
        # IO read/write
N
nicolargo 已提交
576
        if 'io_counters' in self.stats[0] and mmm is None:
577
            # IO read
N
nicolargo 已提交
578
            io_rs = int((self.__sum_stats('io_counters', 0) - self.__sum_stats('io_counters', indice=2, mmm=mmm)) / self.stats[0]['time_since_update'])
579
            if io_rs == 0:
580
                msg = '{:>6}'.format('0')
581
            else:
582
                msg = '{:>6}'.format(self.auto_unit(io_rs, low_precision=True))
N
nicolargo 已提交
583 584 585
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
586
            # IO write
N
nicolargo 已提交
587
            io_ws = int((self.__sum_stats('io_counters', 1) - self.__sum_stats('io_counters', indice=3, mmm=mmm)) / self.stats[0]['time_since_update'])
588
            if io_ws == 0:
589
                msg = '{:>6}'.format('0')
590
            else:
591
                msg = '{:>6}'.format(self.auto_unit(io_ws, low_precision=True))
N
nicolargo 已提交
592 593 594
            ret.append(self.curse_add_line(msg,
                                           decoration=self.__mmm_deco(mmm),
                                           optional=True, additional=True))
595
        else:
596
            msg = '{:>6}'.format('')
597 598
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
            ret.append(self.curse_add_line(msg, optional=True, additional=True))
N
nicolargo 已提交
599
        if mmm is None:
600
            msg = ' < {}'.format('current')
N
nicolargo 已提交
601 602
            ret.append(self.curse_add_line(msg, optional=True))
        else:
603
            msg = ' < {}'.format(mmm)
604 605
            ret.append(self.curse_add_line(msg, optional=True))
            msg = ' (\'M\' to reset)'
N
nicolargo 已提交
606 607 608
            ret.append(self.curse_add_line(msg, optional=True))

    def __mmm_deco(self, mmm):
609
        """Return the decoration string for the current mmm status."""
N
nicolargo 已提交
610 611 612 613
        if mmm is not None:
            return 'DEFAULT'
        else:
            return 'FILTER'
N
Nicolargo 已提交
614

615
    def __mmm_reset(self):
616
        """Reset the MMM stats."""
617 618 619
        self.mmm_min = {}
        self.mmm_max = {}

N
nicolargo 已提交
620
    def __sum_stats(self, key, indice=None, mmm=None):
621 622
        """Return the sum of the stats value for the given key.

N
nicolargo 已提交
623 624
        * indice: If indice is set, get the p[key][indice]
        * mmm: display min, max, mean or current (if mmm=None)
625
        """
N
nicolargo 已提交
626
        # Compute stats summary
627 628
        ret = 0
        for p in self.stats:
629 630 631
            if key not in p:
                # Correct issue #1188
                continue
632 633 634 635
            if indice is None:
                ret += p[key]
            else:
                ret += p[key][indice]
N
nicolargo 已提交
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665

        # Manage Min/Max/Mean
        mmm_key = self.__mmm_key(key, indice)
        if mmm == 'min':
            try:
                if self.mmm_min[mmm_key] > ret:
                    self.mmm_min[mmm_key] = ret
            except AttributeError:
                self.mmm_min = {}
                return 0
            except KeyError:
                self.mmm_min[mmm_key] = ret
            ret = self.mmm_min[mmm_key]
        elif mmm == 'max':
            try:
                if self.mmm_max[mmm_key] < ret:
                    self.mmm_max[mmm_key] = ret
            except AttributeError:
                self.mmm_max = {}
                return 0
            except KeyError:
                self.mmm_max[mmm_key] = ret
            ret = self.mmm_max[mmm_key]

        return ret

    def __mmm_key(self, key, indice):
        ret = key
        if indice is not None:
            ret += str(indice)
A
Alessio Sergi 已提交
666
        return ret
667

668
    def __sort_stats(self, sortedby=None):
669
        """Return the stats (dict) sorted by (sortedby)."""
670 671 672
        return sort_stats(self.stats, sortedby,
                          tree=glances_processes.is_tree_enabled(),
                          reverse=glances_processes.sort_reverse)
673 674

    def __max_pid_size(self):
675
        """Return the maximum PID size in number of char."""
676 677 678 679 680
        if self.pid_max is not None:
            return len(str(self.pid_max))
        else:
            # By default return 5 (corresponding to 99999 PID number)
            return 5