glances_processes.py 28.8 KB
Newer Older
A
Alessio Sergi 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
A
Alessio Sergi 已提交
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# Copyright (C) 2014 Nicolargo <nicolas@nicolargo.com>
#
# 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/>.

N
Nicolargo 已提交
20
# Import Python lib
D
desbma 已提交
21
import collections
N
Nicolargo 已提交
22
import re
A
Alessio Sergi 已提交
23

24 25 26 27 28 29 30
# Import psutil
import psutil

# Import Glances lib
from glances.core.glances_globals import is_bsd, is_linux, is_mac, is_windows, logger
from glances.core.glances_timer import getTimeSinceLastUpdate, Timer

D
desbma 已提交
31 32 33

class ProcessTreeNode(object):

34 35 36 37 38 39
    """
    Represent a process tree.

    We avoid recursive algorithm to manipulate the tree because function calls are expensive with CPython.
    """

D
desbma 已提交
40 41 42
    def __init__(self, process=None, stats=None, sort_key=None, root=False):
        self.process = process
        self.stats = stats
D
desbma 已提交
43
        self.children = []
D
desbma 已提交
44
        self.children_sorted = False
D
desbma 已提交
45
        self.sort_key = sort_key
46
        self.reverse_sorting = (self.sort_key != "name")
D
desbma 已提交
47
        self.is_root = root
D
desbma 已提交
48 49

    def __str__(self):
50
        """ Return the tree as a string for debugging. """
D
desbma 已提交
51
        lines = []
52 53 54 55 56 57 58 59 60
        nodes_to_print = collections.deque([collections.deque([("#", self)])])
        while nodes_to_print:
            indent_str, current_node = nodes_to_print[-1].pop()
            if not nodes_to_print[-1]:
                nodes_to_print.pop()
            if current_node.is_root:
                lines.append(indent_str)
            else:
                lines.append("%s[%s]" % (indent_str, current_node.process.name()))
D
PEP8  
desbma 已提交
61
            indent_str = " " * (len(lines[-1]) - 1)
62 63 64 65
            children_nodes_to_print = collections.deque()
            for child in current_node.children:
                if child is current_node.children[-1]:
                    tree_char = "└─"
D
desbma 已提交
66
                else:
67 68 69 70
                    tree_char = "├─"
                children_nodes_to_print.appendleft((indent_str + tree_char, child))
            if children_nodes_to_print:
                nodes_to_print.append(children_nodes_to_print)
D
desbma 已提交
71
        return "\n".join(lines)
D
desbma 已提交
72

D
desbma 已提交
73
    def setSorting(self, key, reverse):
74
        """ Set sorting key or func for user with __iter__ (affects the whole tree from this node). """
D
desbma 已提交
75
        if (self.sort_key != key) or (self.reverse_sorting != reverse):
76 77 78 79 80 81 82
            nodes_to_flag_unsorted = collections.deque([self])
            while nodes_to_flag_unsorted:
                current_node = nodes_to_flag_unsorted.pop()
                current_node.children_sorted = False
                current_node.sort_key = key
                current_node.reverse_sorting = reverse
                nodes_to_flag_unsorted.extend(current_node.children)
D
desbma 已提交
83

D
desbma 已提交
84
    def getWeight(self):
85 86 87
        """ Return "weight" of a process and all its children for sorting. """
        if self.sort_key == "name":
            return self.stats[self.sort_key]
D
desbma 已提交
88

D
desbma 已提交
89
        # sum ressource usage for self and children
D
desbma 已提交
90 91 92 93
        total = 0
        nodes_to_sum = collections.deque([self])
        while nodes_to_sum:
            current_node = nodes_to_sum.pop()
94 95 96 97 98 99 100
            if callable(self.sort_key):
                total += self.sort_key(current_node.stats)
            elif self.sort_key == "io_counters":
                stats = current_node.stats[self.sort_key]
                total += stats[0] - stats[2] + stats[1] - stats[3]
            else:
                total += current_node.stats[self.sort_key]
D
desbma 已提交
101 102 103 104
            nodes_to_sum.extend(current_node.children)

        return total

105
    def __len__(self):
106
        """Return the number of nodes in the tree."""
107 108 109 110 111 112 113 114 115
        total = 0
        nodes_to_sum = collections.deque([self])
        while nodes_to_sum:
            current_node = nodes_to_sum.pop()
            if not current_node.is_root:
                total += 1
            nodes_to_sum.extend(current_node.children)
        return total

D
desbma 已提交
116
    def __iter__(self):
117
        """ Iterator returning ProcessTreeNode in sorted order, recursively. """
D
desbma 已提交
118 119 120
        if not self.is_root:
            yield self
        if not self.children_sorted:
121 122
            # optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
            # and once before displaying)
D
desbma 已提交
123
            self.children.sort(key=self.__class__.getWeight, reverse=self.reverse_sorting)
D
desbma 已提交
124 125
            self.children_sorted = True
        for child in self.children:
D
desbma 已提交
126 127
            for n in iter(child):
                yield n
D
desbma 已提交
128

129 130 131 132 133 134 135 136 137 138 139
    def iterChildren(self, exclude_incomplete_stats=True):
        """
        Iterator returning ProcessTreeNode in sorted order (only children of this node, non recursive).

        If exclude_incomplete_stats is True, exclude processes not having full statistics.
        It can happen after a resort (change of sort key) because process stats are not grabbed immediately,
        but only at next full update.
        """
        if not self.children_sorted:
            # optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for,
            # and once before displaying)
D
desbma 已提交
140
            self.children.sort(key=self.__class__.getWeight, reverse=self.reverse_sorting)
141 142 143 144 145
            self.children_sorted = True
        for child in self.children:
            if (not exclude_incomplete_stats) or ("time_since_update" in child.stats):
                yield child

D
desbma 已提交
146
    def findProcess(self, process):
D
desbma 已提交
147
        """ Search in tree for the ProcessTreeNode owning process, return it or None if not found. """
148 149 150 151 152 153
        nodes_to_search = collections.deque([self])
        while nodes_to_search:
            current_node = nodes_to_search.pop()
            if (not current_node.is_root) and (current_node.process.pid == process.pid):
                return current_node
            nodes_to_search.extend(current_node.children)
D
desbma 已提交
154 155

    @staticmethod
156
    def buildTree(process_dict, sort_key, hide_kernel_threads):
D
desbma 已提交
157 158
        """ Build a process tree using using parent/child relationships, and return the tree root node. """
        tree_root = ProcessTreeNode(root=True)
159 160
        nodes_to_add_last = collections.deque()

D
desbma 已提交
161
        # first pass: add nodes whose parent are in the tree
D
desbma 已提交
162 163
        for process, stats in process_dict.items():
            new_node = ProcessTreeNode(process, stats, sort_key)
164 165 166 167 168
            try:
                parent_process = process.parent()
            except psutil.NoSuchProcess:
                # parent is dead, consider no parent
                parent_process = None
D
desbma 已提交
169 170 171
            if parent_process is None:
                # no parent, add this node at the top level
                tree_root.children.append(new_node)
172 173 174
            elif hide_kernel_threads and (not is_windows) and (parent_process.gids().real == 0):
                # parent is a kernel thread, add this node at the top level
                tree_root.children.append(new_node)
D
desbma 已提交
175 176 177 178
            else:
                parent_node = tree_root.findProcess(parent_process)
                if parent_node is not None:
                    # parent is already in the tree, add a new child
D
desbma 已提交
179
                    parent_node.children.append(new_node)
D
desbma 已提交
180
                else:
181 182 183 184 185
                    # parent is not in tree, add this node later
                    nodes_to_add_last.append(new_node)

        # next pass(es): add nodes to their parents if it could not be done in previous pass
        while nodes_to_add_last:
186
            node_to_add = nodes_to_add_last.popleft()  # pop from left and append to right to avoid infinite loop
187 188 189 190 191 192
            try:
                parent_process = node_to_add.process.parent()
            except psutil.NoSuchProcess:
                # parent is dead, consider no parent, add this node at the top level
                tree_root.children.append(node_to_add)
            else:
D
desbma 已提交
193 194 195 196
                if parent_process is None:
                    # parent is None now, but was not at previous pass (can occur on Windows only)
                    # consider no parent, add this node at the top level
                    tree_root.children.append(node_to_add)
197
                else:
D
desbma 已提交
198 199 200 201 202 203 204
                    parent_node = tree_root.findProcess(parent_process)
                    if parent_node is not None:
                        # parent is already in the tree, add a new child
                        parent_node.children.append(node_to_add)
                    else:
                        # parent is not in tree, add this node later
                        nodes_to_add_last.append(node_to_add)
205

D
desbma 已提交
206 207 208
        return tree_root


A
Alessio Sergi 已提交
209
class GlancesProcesses(object):
A
PEP 257  
Alessio Sergi 已提交
210 211

    """Get processed stats using the psutil library."""
A
Alessio Sergi 已提交
212 213

    def __init__(self, cache_timeout=60):
A
PEP 257  
Alessio Sergi 已提交
214
        """Init the class to collect stats about processes."""
A
Alessio Sergi 已提交
215 216 217 218
        # Add internals caches because PSUtil do not cache all the stats
        # See: https://code.google.com/p/psutil/issues/detail?id=462
        self.username_cache = {}
        self.cmdline_cache = {}
A
Alessio Sergi 已提交
219

A
Alessio Sergi 已提交
220 221 222
        # The internals caches will be cleaned each 'cache_timeout' seconds
        self.cache_timeout = cache_timeout
        self.cache_timer = Timer(self.cache_timeout)
A
Alessio Sergi 已提交
223

A
Alessio Sergi 已提交
224 225 226 227
        # Init the io dict
        # key = pid
        # value = [ read_bytes_old, write_bytes_old ]
        self.io_old = {}
A
Alessio Sergi 已提交
228

229 230
        # Wether or not to enable process tree
        self._enable_tree = False
231
        self.process_tree = None
232 233

        # Init stats
N
Nicolargo 已提交
234
        self.resetsort()
A
Alessio Sergi 已提交
235
        self.processlist = []
236 237
        self.processcount = {
            'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
A
Alessio Sergi 已提交
238

D
desbma 已提交
239
        # Tag to enable/disable the processes stats (to reduce the Glances CPU consumption)
240 241 242
        # Default is to enable the processes stats
        self.disable_tag = False

N
Nicolargo 已提交
243 244 245
        # Extended stats for top process is enable by default
        self.disable_extended_tag = False

246 247 248 249
        # Maximum number of processes showed in the UI interface
        # None if no limit
        self.max_processes = None

N
Nicolargo 已提交
250 251 252 253
        # Process filter is a regular expression
        self.process_filter = None
        self.process_filter_re = None

254 255 256
        # Whether or not to hide kernel threads
        self.no_kernel_threads = False

257
    def enable(self):
A
PEP 257  
Alessio Sergi 已提交
258
        """Enable process stats."""
259 260 261 262
        self.disable_tag = False
        self.update()

    def disable(self):
A
PEP 257  
Alessio Sergi 已提交
263
        """Disable process stats."""
264 265
        self.disable_tag = True

N
Nicolargo 已提交
266 267 268 269 270 271 272 273 274
    def enable_extended(self):
        """Enable extended process stats."""
        self.disable_extended_tag = False
        self.update()

    def disable_extended(self):
        """Disable extended process stats."""
        self.disable_extended_tag = True

275 276 277 278 279 280 281 282 283
    def set_max_processes(self, value):
        """Set the maximum number of processes showed in the UI interfaces"""
        self.max_processes = value
        return self.max_processes

    def get_max_processes(self):
        """Get the maximum number of processes showed in the UI interfaces"""
        return self.max_processes

N
Nicolargo 已提交
284 285 286 287 288 289 290
    def set_process_filter(self, value):
        """Set the process filter"""
        logger.info(_("Set process filter to %s") % value)
        self.process_filter = value
        if value is not None:
            try:
                self.process_filter_re = re.compile(value)
291 292
                logger.debug(
                    _("Process filter regular expression compilation OK: %s") % self.get_process_filter())
A
Alessio Sergi 已提交
293
            except Exception:
294 295
                logger.error(
                    _("Can not compile process filter regular expression: %s") % value)
N
Nicolargo 已提交
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
                self.process_filter_re = None
        else:
            self.process_filter_re = None
        return self.process_filter

    def get_process_filter(self):
        """Get the process filter"""
        return self.process_filter

    def get_process_filter_re(self):
        """Get the process regular expression compiled"""
        return self.process_filter_re

    def is_filtered(self, value):
        """Return True if the value should be filtered"""
        if self.get_process_filter() is None:
            # No filter => Not filtered
            return False
        else:
N
Nicolargo 已提交
315
            # logger.debug(self.get_process_filter() + " <> " + value + " => " + str(self.get_process_filter_re().match(value) is None))
N
Nicolargo 已提交
316 317
            return self.get_process_filter_re().match(value) is None

318 319 320 321
    def disable_kernel_threads(self):
        """ Ignore kernel threads in process list. """
        self.no_kernel_threads = True

322 323 324 325 326 327 328 329
    def enable_tree(self):
        """ Enable process tree. """
        self._enable_tree = True

    def is_tree_enabled(self):
        """ Return True if process tree is enabled, False instead. """
        return self._enable_tree

330 331
    def __get_process_stats(self, proc,
                            mandatory_stats=True,
332
                            standard_stats=True,
333 334 335
                            extended_stats=False):
        """
        Get process stats of the proc processes (proc is returned psutil.process_iter())
N
Nicolargo 已提交
336 337
        mandatory_stats: need for the sorting/filter step
        => cpu_percent, memory_percent, io_counters, name, cmdline
338
        standard_stats: for all the displayed processes
N
Nicolargo 已提交
339
        => username, status, memory_info, cpu_times
340 341
        extended_stats: only for top processes (see issue #403)
        => connections (UDP/TCP), memory_swap...
342 343 344 345 346 347
        """

        # Process ID (always)
        procstat = proc.as_dict(attrs=['pid'])

        if mandatory_stats:
348
            procstat['mandatory_stats'] = True
N
Nicolargo 已提交
349

350
            # Process CPU, MEM percent and name
351 352
            try:
                procstat.update(
353
                    proc.as_dict(attrs=['cpu_percent', 'memory_percent', 'name', 'cpu_times'], ad_value=''))
354 355 356
            except psutil.NoSuchProcess:
                # Correct issue #414
                return None
357
            if procstat['cpu_percent'] == '' or procstat['memory_percent'] == '':
358 359
                # Do not display process if we can not get the basic
                # cpu_percent or memory_percent stats
360
                return None
361

N
Nicolargo 已提交
362 363 364 365 366 367
            # Process command line (cached with internal cache)
            try:
                self.cmdline_cache[procstat['pid']]
            except KeyError:
                # Patch for issue #391
                try:
368 369
                    self.cmdline_cache[
                        procstat['pid']] = ' '.join(proc.cmdline())
370
                except (AttributeError, UnicodeDecodeError, psutil.AccessDenied, psutil.NoSuchProcess):
N
Nicolargo 已提交
371 372 373
                    self.cmdline_cache[procstat['pid']] = ""
            procstat['cmdline'] = self.cmdline_cache[procstat['pid']]

374 375 376 377 378 379 380
            # Process IO
            # procstat['io_counters'] is a list:
            # [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag]
            # If io_tag = 0 > Access denied (display "?")
            # If io_tag = 1 > No access denied (display the IO rate)
            # Note Disk IO stat not available on Mac OS
            if not is_mac:
A
Alessio Sergi 已提交
381
                try:
382 383 384
                    # Get the process IO counters
                    proc_io = proc.io_counters()
                    io_new = [proc_io.read_bytes, proc_io.write_bytes]
385
                except (psutil.AccessDenied, psutil.NoSuchProcess):
386
                    # Access denied to process IO (no root account)
387
                    # NoSuchProcess (process die between first and second grab)
388 389
                    # Put 0 in all values (for sort) and io_tag = 0 (for
                    # display)
390 391 392 393 394 395
                    procstat['io_counters'] = [0, 0] + [0, 0]
                    io_tag = 0
                else:
                    # For IO rate computation
                    # Append saved IO r/w bytes
                    try:
396 397
                        procstat['io_counters'] = io_new + \
                            self.io_old[procstat['pid']]
398 399 400 401 402 403 404 405 406 407
                    except KeyError:
                        procstat['io_counters'] = io_new + [0, 0]
                    # then save the IO r/w bytes
                    self.io_old[procstat['pid']] = io_new
                    io_tag = 1

                # Append the IO tag (for display)
                procstat['io_counters'] += [io_tag]

        if standard_stats:
408
            procstat['standard_stats'] = True
N
Nicolargo 已提交
409

410
            # Process username (cached with internal cache)
N
Nicolas Hennion 已提交
411
            try:
412 413 414 415
                self.username_cache[procstat['pid']]
            except KeyError:
                try:
                    self.username_cache[procstat['pid']] = proc.username()
416
                except psutil.NoSuchProcess:
417
                    self.username_cache[procstat['pid']] = "?"
418 419 420 421 422 423 424 425
                except (KeyError, psutil.AccessDenied):
                    try:
                        self.username_cache[procstat['pid']] = proc.uids().real
                    except (KeyError, AttributeError, psutil.AccessDenied):
                        self.username_cache[procstat['pid']] = "?"
            procstat['username'] = self.username_cache[procstat['pid']]

            # Process status, nice, memory_info and cpu_times
426
            try:
427 428
                procstat.update(
                    proc.as_dict(attrs=['status', 'nice', 'memory_info', 'cpu_times']))
429 430 431 432
            except psutil.NoSuchProcess:
                pass
            else:
                procstat['status'] = str(procstat['status'])[:1].upper()
433

N
Nicolargo 已提交
434
        if extended_stats and not self.disable_extended_tag:
435
            procstat['extended_stats'] = True
N
Nicolargo 已提交
436

437 438 439
            # CPU affinity (Windows and Linux only)
            try:
                procstat.update(proc.as_dict(attrs=['cpu_affinity']))
440 441
            except psutil.NoSuchProcess:
                pass
442 443
            except AttributeError:
                procstat['cpu_affinity'] = None
444
            # Memory extended
445 446
            try:
                procstat.update(proc.as_dict(attrs=['memory_info_ex']))
447 448
            except psutil.NoSuchProcess:
                pass
449 450
            except AttributeError:
                procstat['memory_info_ex'] = None
451
            # Number of context switch
452 453
            try:
                procstat.update(proc.as_dict(attrs=['num_ctx_switches']))
454 455
            except psutil.NoSuchProcess:
                pass
456 457
            except AttributeError:
                procstat['num_ctx_switches'] = None
458
            # Number of file descriptors (Unix only)
459 460
            try:
                procstat.update(proc.as_dict(attrs=['num_fds']))
461 462
            except psutil.NoSuchProcess:
                pass
463 464
            except AttributeError:
                procstat['num_fds'] = None
465
            # Threads number
466 467
            try:
                procstat.update(proc.as_dict(attrs=['num_threads']))
468 469
            except psutil.NoSuchProcess:
                pass
470 471
            except AttributeError:
                procstat['num_threads'] = None
472 473 474

            # Number of handles (Windows only)
            if is_windows:
475 476 477 478
                try:
                    procstat.update(proc.as_dict(attrs=['num_handles']))
                except psutil.NoSuchProcess:
                    pass
N
Nicolargo 已提交
479 480
            else:
                procstat['num_handles'] = None
481 482 483 484 485

            # SWAP memory (Only on Linux based OS)
            # http://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
            if is_linux:
                try:
486 487
                    procstat['memory_swap'] = sum(
                        [v.swap for v in proc.memory_maps()])
488 489
                except psutil.NoSuchProcess:
                    pass
490 491
                except psutil.AccessDenied:
                    procstat['memory_swap'] = None
A
Alessio Sergi 已提交
492
                except Exception:
493 494
                    # Add a dirty except to handle the PsUtil issue #413
                    procstat['memory_swap'] = None
495 496

            # Process network connections (TCP and UDP)
497 498 499
            try:
                procstat['tcp'] = len(proc.connections(kind="tcp"))
                procstat['udp'] = len(proc.connections(kind="udp"))
A
Alessio Sergi 已提交
500
            except Exception:
501 502
                procstat['tcp'] = None
                procstat['udp'] = None
503

N
Nicolargo 已提交
504 505 506
            # IO Nice
            # http://pythonhosted.org/psutil/#psutil.Process.ionice
            if is_linux or is_windows:
507 508 509 510
                try:
                    procstat.update(proc.as_dict(attrs=['ionice']))
                except psutil.NoSuchProcess:
                    pass
N
Nicolargo 已提交
511 512
            else:
                procstat['ionice'] = None
513

514
            # logger.debug(procstat)
N
Nicolargo 已提交
515

A
Alessio Sergi 已提交
516 517 518
        return procstat

    def update(self):
519 520 521
        """
        Update the processes stats
        """
522
        # Reset the stats
A
Alessio Sergi 已提交
523
        self.processlist = []
524 525
        self.processcount = {
            'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0}
A
Alessio Sergi 已提交
526

527
        # Do not process if disable tag is set
528
        if self.disable_tag:
A
Alessio Sergi 已提交
529
            return
530

A
Alessio Sergi 已提交
531 532 533
        # Get the time since last update
        time_since_update = getTimeSinceLastUpdate('process_disk')

534 535
        # Build an internal dict with only mandatories stats (sort keys)
        processdict = {}
A
Alessio Sergi 已提交
536
        for proc in psutil.process_iter():
537 538 539 540 541
            # Ignore kernel threads if needed
            if (self.no_kernel_threads and (not is_windows)  # gids() is only available on unix
                    and (proc.gids().real == 0)):
                continue

542
            # If self.get_max_processes() is None: Only retreive mandatory stats
543
            # Else: retreive mandatory and standard stats
544 545
            s = self.__get_process_stats(proc,
                                         mandatory_stats=True,
N
Nicolargo 已提交
546
                                         standard_stats=self.get_max_processes() is None)
N
Nicolargo 已提交
547
            # Continue to the next process if it has to be filtered
548
            if s is None or (self.is_filtered(s['cmdline']) and self.is_filtered(s['name'])):
N
Nicolargo 已提交
549
                continue
N
Nicolargo 已提交
550
            # Ok add the process to the list
N
Nicolargo 已提交
551
            processdict[proc] = s
552 553 554 555
            # ignore the 'idle' process on Windows and *BSD
            # ignore the 'kernel_task' process on OS X
            # waiting for upstream patch from psutil
            if (is_bsd and processdict[proc]['name'] == 'idle' or
556 557
                    is_windows and processdict[proc]['name'] == 'System Idle Process' or
                    is_mac and processdict[proc]['name'] == 'kernel_task'):
558 559 560 561 562 563
                continue
            # Update processcount (global statistics)
            try:
                self.processcount[str(proc.status())] += 1
            except KeyError:
                # Key did not exist, create it
564 565 566 567
                try:
                    self.processcount[str(proc.status())] = 1
                except psutil.NoSuchProcess:
                    pass
N
Nicolargo 已提交
568 569
            except psutil.NoSuchProcess:
                pass
570 571 572
            else:
                self.processcount['total'] += 1
            # Update thread number (global statistics)
A
Alessio Sergi 已提交
573
            try:
574
                self.processcount['thread'] += proc.num_threads()
A
Alessio Sergi 已提交
575
            except Exception:
576 577
                pass

578
        if self._enable_tree:
579 580 581
            self.process_tree = ProcessTreeNode.buildTree(processdict,
                                                          self.getsortkey(),
                                                          self.no_kernel_threads)
D
desbma 已提交
582

D
desbma 已提交
583 584
            for i, node in enumerate(self.process_tree):
                # Only retreive stats for visible processes (get_max_processes)
585 586
                if (self.get_max_processes() is not None) and (i >= self.get_max_processes()):
                    break
D
desbma 已提交
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604

                # add standard stats
                new_stats = self.__get_process_stats(node.process,
                                                     mandatory_stats=False,
                                                     standard_stats=True,
                                                     extended_stats=False)
                if new_stats is not None:
                    node.stats.update(new_stats)

                # Add a specific time_since_update stats for bitrate
                node.stats['time_since_update'] = time_since_update

        else:
            # Process optimization
            # Only retreive stats for visible processes (get_max_processes)
            if self.get_max_processes() is not None:
                # Sort the internal dict and cut the top N (Return a list of tuple)
                # tuple=key (proc), dict (returned by __get_process_stats)
D
desbma 已提交
605 606 607
                try:
                    processiter = sorted(
                        processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True)
608 609 610
                except (KeyError, TypeError) as e:
                    logger.error("Can not sort process list by %s (%s)" % (self.getsortkey(), e))
                    logger.error("%s" % str(processdict.items()[0]))
D
desbma 已提交
611 612 613 614 615 616
                    # Fallback to all process (issue #423)
                    processloop = processdict.items()
                    first = False
                else:
                    processloop = processiter[0:self.get_max_processes()]
                    first = True
D
desbma 已提交
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
            else:
                # Get all processes stats
                processloop = processdict.items()
                first = False
            for i in processloop:
                # Already existing mandatory stats
                procstat = i[1]
                if self.get_max_processes() is not None:
                    # Update with standard stats
                    # and extended stats but only for TOP (first) process
                    s = self.__get_process_stats(i[0],
                                                 mandatory_stats=False,
                                                 standard_stats=True,
                                                 extended_stats=first)
                    if s is None:
                        continue
                    procstat.update(s)
                # Add a specific time_since_update stats for bitrate
                procstat['time_since_update'] = time_since_update
                # Update process list
                self.processlist.append(procstat)
                # Next...
                first = False
A
Alessio Sergi 已提交
640 641

        # Clean internals caches if timeout is reached
642
        if self.cache_timer.finished():
A
Alessio Sergi 已提交
643 644 645 646 647 648
            self.username_cache = {}
            self.cmdline_cache = {}
            # Restart the timer
            self.cache_timer.reset()

    def getcount(self):
A
PEP 257  
Alessio Sergi 已提交
649
        """Get the number of processes."""
A
Alessio Sergi 已提交
650 651 652
        return self.processcount

    def getlist(self, sortedby=None):
A
PEP 257  
Alessio Sergi 已提交
653
        """Get the processlist."""
654 655
        return self.processlist

D
desbma 已提交
656 657 658 659
    def gettree(self):
        """Get the process tree."""
        return self.process_tree

N
Nicolas Hennion 已提交
660
    def getsortkey(self):
N
Nicolargo 已提交
661 662 663 664 665 666 667 668 669 670 671
        """Get the current sort key"""
        if self.getmanualsortkey() is not None:
            return self.getmanualsortkey()
        else:
            return self.getautosortkey()

    def getmanualsortkey(self):
        """Get the current sort key for manual sort."""
        return self.processmanualsort

    def getautosortkey(self):
A
PEP 257  
Alessio Sergi 已提交
672
        """Get the current sort key for automatic sort."""
N
Nicolargo 已提交
673
        return self.processautosort
N
Nicolas Hennion 已提交
674

N
Nicolargo 已提交
675 676 677
    def setmanualsortkey(self, sortedby):
        """Set the current sort key for manual sort."""
        self.processmanualsort = sortedby
678
        if self._enable_tree and (self.process_tree is not None):
679
            self.process_tree.setSorting(sortedby, sortedby != "name")
N
Nicolargo 已提交
680 681 682
        return self.processmanualsort

    def setautosortkey(self, sortedby):
A
PEP 257  
Alessio Sergi 已提交
683
        """Set the current sort key for automatic sort."""
N
Nicolargo 已提交
684 685 686 687 688 689 690
        self.processautosort = sortedby
        return self.processautosort

    def resetsort(self):
        """Set the default sort: Auto"""
        self.setmanualsortkey(None)
        self.setautosortkey('cpu_percent')
N
Nicolas Hennion 已提交
691

692
    def getsortlist(self, sortedby=None):
A
PEP 257  
Alessio Sergi 已提交
693
        """Get the sorted processlist."""
694
        if sortedby is None:
A
Alessio Sergi 已提交
695 696 697 698
            # No need to sort...
            return self.processlist

        sortedreverse = True
699
        if sortedby == 'name':
A
Alessio Sergi 已提交
700 701
            sortedreverse = False

702
        if sortedby == 'io_counters':
A
Alessio Sergi 已提交
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
            # Specific case for io_counters
            # Sum of io_r + io_w
            try:
                # Sort process by IO rate (sum IO read + IO write)
                listsorted = sorted(self.processlist,
                                    key=lambda process: process[sortedby][0] -
                                    process[sortedby][2] + process[sortedby][1] -
                                    process[sortedby][3],
                                    reverse=sortedreverse)
            except Exception:
                listsorted = sorted(self.processlist,
                                    key=lambda process: process['cpu_percent'],
                                    reverse=sortedreverse)
        else:
            # Others sorts
            listsorted = sorted(self.processlist,
                                key=lambda process: process[sortedby],
                                reverse=sortedreverse)

        self.processlist = listsorted

        return self.processlist