glances_processlist.py 15.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/>.

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

N
Nicolas Hennion 已提交
22
# Import sys libs
23
import os
A
Alessio Sergi 已提交
24 25
from datetime import timedelta

N
Nicolas Hennion 已提交
26
# Import Glances libs
27
from glances.core.glances_globals import glances_processes, is_linux, is_bsd, is_mac, is_windows, logger
A
Alessio Sergi 已提交
28
from glances.plugins.glances_plugin import GlancesPlugin
A
Alessio Sergi 已提交
29 30 31


class Plugin(GlancesPlugin):
A
PEP 257  
Alessio Sergi 已提交
32 33

    """Glances' processes plugin.
A
Alessio Sergi 已提交
34 35 36 37

    stats is a list
    """

38
    def __init__(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
39
        """Init the plugin."""
40
        GlancesPlugin.__init__(self, args=args)
A
Alessio Sergi 已提交
41 42 43 44

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

45
        # Note: 'glances_processes' is already init in the glances_processes.py script
46

47
    def reset(self):
A
PEP 257  
Alessio Sergi 已提交
48
        """Reset/init the stats."""
49
        self.stats = []
50 51

    def update(self):
A
PEP 257  
Alessio Sergi 已提交
52
        """Update processes stats using the input method."""
53 54
        # Reset stats
        self.reset()
55

56
        if self.get_input() == 'local':
57 58 59
            # Update stats using the standard system lib
            # Note: Update is done in the processcount plugin
            # Just return the processes list
N
Nicolargo 已提交
60
            self.stats = glances_processes.getlist()            
61
        elif self.get_input() == 'snmp':
N
Nicolargo 已提交
62
            # No SNMP grab for processes
63
            pass
64

65
        return self.stats
66

A
Alessio Sergi 已提交
67
    def msg_curse(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
68
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
69 70 71
        # Init the return message
        ret = []

72
        # Only process if stats exist and display plugin enable...
73
        if self.stats == [] or args.disable_process:
74 75
            return ret

A
Alessio Sergi 已提交
76
        # Compute the sort key
N
Nicolargo 已提交
77 78
        if glances_processes.getmanualsortkey() is None:
            process_sort_key = glances_processes.getautosortkey()
A
Alessio Sergi 已提交
79
        else:
N
Nicolargo 已提交
80
            process_sort_key = glances_processes.getmanualsortkey()
81
        sort_style = 'SORT'
A
Alessio Sergi 已提交
82 83

        # Header
A
Alessio Sergi 已提交
84
        msg = '{0:>6}'.format(_("CPU%"))
A
Alessio Sergi 已提交
85
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
A
Alessio Sergi 已提交
86
        msg = '{0:>6}'.format(_("MEM%"))
A
Alessio Sergi 已提交
87
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
A
Alessio Sergi 已提交
88
        msg = '{0:>6}'.format(_("VIRT"))
A
Alessio Sergi 已提交
89
        ret.append(self.curse_add_line(msg, optional=True))
A
Alessio Sergi 已提交
90
        msg = '{0:>6}'.format(_("RES"))
A
Alessio Sergi 已提交
91
        ret.append(self.curse_add_line(msg, optional=True))
A
Alessio Sergi 已提交
92
        msg = '{0:>6}'.format(_("PID"))
93
        ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
94
        msg = ' {0:10}'.format(_("USER"))
95
        ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
96
        msg = '{0:>4}'.format(_("NI"))
97
        ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
98
        msg = '{0:>2}'.format(_("S"))
99
        ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
100
        msg = '{0:>9}'.format(_("TIME+"))
A
Alessio Sergi 已提交
101
        ret.append(self.curse_add_line(msg, optional=True))
A
Alessio Sergi 已提交
102
        msg = '{0:>6}'.format(_("IOR/s"))
103
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
A
Alessio Sergi 已提交
104
        msg = '{0:>6}'.format(_("IOW/s"))
105
        ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True))
A
Alessio Sergi 已提交
106
        msg = ' {0:8}'.format(_("Command"))
107
        ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
108 109 110 111 112

        # Trying to display proc time
        tag_proc_time = True

        # Loop over processes (sorted by the sort key previously compute)
N
Nicolargo 已提交
113
        first = True
114
        for p in self.sortlist(process_sort_key):
A
Alessio Sergi 已提交
115 116
            ret.append(self.curse_new_line())
            # CPU
117
            if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '':
N
Nicolargo 已提交
118 119 120 121 122 123
                msg = '{0:>6.1f}'.format(p['cpu_percent'])
                ret.append(self.curse_add_line(msg,
                                               self.get_alert(p['cpu_percent'], header="cpu")))
            else:
                msg = '{0:>6}'.format('?')
                ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
124
            # MEM
125
            if 'memory_percent' in p and p['memory_percent'] is not None and p['memory_percent'] != '':
N
Nicolargo 已提交
126 127 128 129 130 131 132
                msg = '{0:>6.1f}'.format(p['memory_percent'])
                ret.append(self.curse_add_line(msg,
                                               self.get_alert(p['memory_percent'], header="mem")))
            else:
                msg = '{0:>6}'.format('?')
                ret.append(self.curse_add_line(msg))
            # VMS/RSS
133
            if 'memory_info' in p and p['memory_info'] is not None and p['memory_info'] != '':
N
Nicolargo 已提交
134 135 136 137 138 139 140 141 142 143
                # VMS
                msg = '{0:>6}'.format(self.auto_unit(p['memory_info'][1], low_precision=False))
                ret.append(self.curse_add_line(msg, optional=True))
                # RSS
                msg = '{0:>6}'.format(self.auto_unit(p['memory_info'][0], low_precision=False))
                ret.append(self.curse_add_line(msg, optional=True))
            else:
                msg = '{0:>6}'.format('?')
                ret.append(self.curse_add_line(msg))
                ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
144
            # PID
A
Alessio Sergi 已提交
145
            msg = '{0:>6}'.format(p['pid'])
146
            ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
147
            # USER
N
Nicolargo 已提交
148 149 150 151 152 153 154
            if 'username' in p:
                # docker internal users are displayed as ints only, therefore str()
                msg = ' {0:9}'.format(str(p['username'])[:9])
                ret.append(self.curse_add_line(msg))
            else:
                msg = ' {0:9}'.format('?')
                ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
155
            # NICE
N
Nicolargo 已提交
156 157 158 159 160 161 162 163 164 165
            if 'nice' in p:
                nice = p['nice']
                if nice is None:
                    nice = '?'
                msg = '{0:>5}'.format(nice)
                if isinstance(nice, int) and ((is_windows and nice != 32) or
                                              (not is_windows and nice != 0)):
                    ret.append(self.curse_add_line(msg, decoration='NICE'))
                else:
                    ret.append(self.curse_add_line(msg))
166
            else:
N
Nicolargo 已提交
167
                msg = '{0:>5}'.format('?')
168
                ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
169
            # STATUS
N
Nicolargo 已提交
170 171 172 173 174 175 176
            if 'status' in p:
                status = p['status']
                msg = '{0:>2}'.format(status)
                if status == 'R':
                    ret.append(self.curse_add_line(msg, decoration='STATUS'))
                else:
                    ret.append(self.curse_add_line(msg))
177
            else:
N
Nicolargo 已提交
178
                msg = '{0:>2}'.format('?')
179
                ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
180
            # TIME+
181
            if tag_proc_time:
A
Alessio Sergi 已提交
182 183 184 185 186 187 188
                try:
                    dtime = timedelta(seconds=sum(p['cpu_times']))
                except Exception:
                    # Catched on some Amazon EC2 server
                    # See https://github.com/nicolargo/glances/issues/87
                    tag_proc_time = False
                else:
A
Alessio Sergi 已提交
189
                    msg = '{0}:{1}.{2}'.format(str(dtime.seconds // 60 % 60),
A
Alessio Sergi 已提交
190 191 192
                                               str(dtime.seconds % 60).zfill(2),
                                               str(dtime.microseconds)[:2].zfill(2))
            else:
A
Alessio Sergi 已提交
193 194
                msg = ' '
            msg = '{0:>9}'.format(msg)
A
Alessio Sergi 已提交
195
            ret.append(self.curse_add_line(msg, optional=True))
N
Nicolas Hennion 已提交
196
            # IO read/write
197
            if 'io_counters' in p:
N
Nicolas Hennion 已提交
198 199
                # IO read
                io_rs = (p['io_counters'][0] - p['io_counters'][2]) / p['time_since_update']
200
                if io_rs == 0:
A
Alessio Sergi 已提交
201
                    msg = '{0:>6}'.format("0")
N
Nicolas Hennion 已提交
202
                else:
A
Alessio Sergi 已提交
203
                    msg = '{0:>6}'.format(self.auto_unit(io_rs, low_precision=False))
204
                ret.append(self.curse_add_line(msg, optional=True, additional=True))
N
Nicolas Hennion 已提交
205 206
                # IO write
                io_ws = (p['io_counters'][1] - p['io_counters'][3]) / p['time_since_update']
207
                if io_ws == 0:
A
Alessio Sergi 已提交
208
                    msg = '{0:>6}'.format("0")
N
Nicolas Hennion 已提交
209
                else:
A
Alessio Sergi 已提交
210
                    msg = '{0:>6}'.format(self.auto_unit(io_ws, low_precision=False))
211
                ret.append(self.curse_add_line(msg, optional=True, additional=True))
A
Alessio Sergi 已提交
212
            else:
A
Alessio Sergi 已提交
213
                msg = '{0:>6}'.format("?")
214 215
                ret.append(self.curse_add_line(msg, optional=True, additional=True))
                ret.append(self.curse_add_line(msg, optional=True, additional=True))
216

A
Alessio Sergi 已提交
217
            # Command line
218 219 220
            # If no command line for the process is available, fallback to
            # the bare process name instead
            cmdline = p['cmdline']
221
            if cmdline == "" or args.process_short_name:
A
Alessio Sergi 已提交
222
                msg = ' {0}'.format(p['name'])
223
                ret.append(self.curse_add_line(msg, splittable=True))
224 225 226
            else:
                try:
                    cmd = cmdline.split()[0]
227
                    argument = ' '.join(cmdline.split()[1:])
228 229
                    path, basename = os.path.split(cmd)
                    if os.path.isdir(path):
A
Alessio Sergi 已提交
230
                        msg = ' {0}'.format(path) + os.sep
231 232
                        ret.append(self.curse_add_line(msg, splittable=True))
                        ret.append(self.curse_add_line(basename, decoration='PROCESS', splittable=True))
233
                    else:
A
Alessio Sergi 已提交
234
                        msg = ' {0}'.format(basename)
235
                        ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True))
236
                    msg = " {0}".format(argument)
237
                    ret.append(self.curse_add_line(msg, splittable=True))
238
                except UnicodeEncodeError:
239
                    ret.append(self.curse_add_line("", splittable=True))
A
Alessio Sergi 已提交
240

N
Nicolargo 已提交
241
            # Add extended stats but only for the top processes
N
Nicolargo 已提交
242 243
            # !!! CPU consumption ???
            # TODO: extended stats into the web interface
N
Nicolas Hennion 已提交
244
            if first and 'extended_stats' in p:
N
Nicolargo 已提交
245 246 247
                # Left padding
                xpad = ' ' * 13
                # First line is CPU affinity
248
                if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
N
Nicolargo 已提交
249 250
                    ret.append(self.curse_new_line())
                    msg = xpad + _('CPU affinity: ') + str(len(p['cpu_affinity'])) + _(' cores')
251
                    ret.append(self.curse_add_line(msg, splittable=True))
N
Nicolargo 已提交
252
                # Second line is memory info
253
                if 'memory_info_ex' in p and p['memory_info_ex'] is not None:
N
Nicolargo 已提交
254 255 256 257 258 259
                    ret.append(self.curse_new_line())
                    msg = xpad + _('Memory info: ')
                    for k, v in p['memory_info_ex']._asdict().items():
                        # 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) + ' '
260
                    if 'memory_swap' in p and p['memory_swap'] is not None:
N
Nicolargo 已提交
261
                        msg += _('swap ') + self.auto_unit(p['memory_swap'], low_precision=False)
262
                    ret.append(self.curse_add_line(msg, splittable=True))
N
Nicolas Hennion 已提交
263 264 265
                # Third line is for openned files/network sessions
                ret.append(self.curse_new_line())
                msg = xpad + _('Openned: ')
266
                if 'num_threads' in p and p['num_threads'] is not None:
N
Nicolas Hennion 已提交
267
                    msg += _('threads ') + str(p['num_threads']) + ' '
268
                if 'num_fds' in p and p['num_fds'] is not None:
N
Nicolas Hennion 已提交
269
                    msg += _('files ') + str(p['num_fds']) + ' '
270
                if 'num_handles' in p and p['num_handles'] is not None:
N
Nicolargo 已提交
271
                    msg += _('handles ') + str(p['num_handles']) + ' '
272
                if 'tcp' in p and p['tcp'] is not None:
N
Nicolas Hennion 已提交
273
                    msg += _('TCP ') + str(p['tcp']) + ' '
274
                if 'udp' in p and p['udp'] is not None:
N
Nicolas Hennion 已提交
275
                    msg += _('UDP ') + str(p['udp']) + ' '
276
                ret.append(self.curse_add_line(msg, splittable=True))
N
Nicolargo 已提交
277
                # Fouth line is IO nice level (only Linux and Windows OS)
278
                if 'ionice' in p and p['ionice'] is not None:
N
Nicolargo 已提交
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
                    ret.append(self.curse_new_line())
                    msg = xpad + _('IO nice: ')
                    k = _('Class is ')
                    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). 
                    if is_windows:
                        if v == 0:
                            msg += k + 'Very Low'
                        elif v == 1:
                            msg += k + 'Low'
                        elif v == 2:
                            msg += _('No specific I/O priority')
                        else:
                            msg += k + str(v)                        
                    else:
                        if v == 0:
                            msg += _('No specific I/O priority')
                        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:
                        msg += _(' (value %s/7)') % str(p['ionice'].value)
309
                    ret.append(self.curse_add_line(msg, splittable=True))                
N
Nicolargo 已提交
310 311 312
                # End of extended stats
                first = False

A
Alessio Sergi 已提交
313 314
        # Return the message with decoration
        return ret
315 316

    def sortlist(self, sortedby=None):
A
PEP 257  
Alessio Sergi 已提交
317
        """Return the stats sorted by sortedby variable."""
318
        if sortedby is None:
319 320 321 322
            # No need to sort...
            return self.stats

        sortedreverse = True
323
        if sortedby == 'name':
324 325
            sortedreverse = False

326
        if sortedby == 'io_counters':
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
            # 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.stats,
                                    key=lambda process: process[sortedby][0] -
                                    process[sortedby][2] + process[sortedby][1] -
                                    process[sortedby][3],
                                    reverse=sortedreverse)
            except Exception:
                listsorted = sorted(self.stats,
                                    key=lambda process: process['cpu_percent'],
                                    reverse=sortedreverse)
        else:
            # Others sorts
N
Nicolargo 已提交
342 343 344 345
            try:
                listsorted = sorted(self.stats,
                                    key=lambda process: process[sortedby],
                                    reverse=sortedreverse)
346
            except KeyError, TypeError:
N
Nicolargo 已提交
347 348 349
                listsorted = sorted(self.stats,
                                    key=lambda process: process['name'],
                                    reverse=False)
350 351 352

        self.stats = listsorted

A
Alessio Sergi 已提交
353
        return self.stats