#!/bin/env python pylint: disable=C0302
# -*- coding: utf-8 -*
# Authors: Giampaolo Rodola of psutil
# The class [Process] is back ported from python open-source project
# psutil. Guanna Ma back ported it to py-cup.
# If any concern, plz contact mythmgn@gmail.com
# Here is the original license applied.
# :Copyright - (Psutil)
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file.
# pylint: disable=unspecified-encoding, consider-using-f-string
"""
Provie Linux Resource/State Info Query
"""
from __future__ import print_function
import os
import re
import sys
import time
import errno
import socket
import base64
import struct
import threading
import warnings
import collections
from functools import wraps
import cup
from cup import err
from cup import decorators
__all__ = [
'boot_time', 'get_boottime_since_epoch',
# disk info
'get_disk_usage_all', 'get_disk_info',
# cpu info
'CPUInfo', 'get_cpu_usage', 'get_cpu_nums', 'get_cpu_core_usage',
'MemInfo', 'get_meminfo',
'SWAPINFO', 'get_swapinfo',
'net_io_counters', 'get_net_through', 'get_net_transmit_speed',
'pids', 'process_iter', 'Process',
'get_kernel_version',
]
_CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
_PAGESIZE = os.sysconf("SC_PAGE_SIZE")
_PROC_STATUSES = {
"R": 'STATUS_RUNNING',
"S": 'STATUS_SLEEPING',
"D": 'STATUS_DISK_SLEEP',
"T": 'STATUS_STOPPED',
"t": 'STATUS_TRACING_STOP',
"Z": 'STATUS_ZOMBIE',
"X": 'STATUS_DEAD',
"x": 'STATUS_DEAD',
"K": 'STATUS_WAKE_KILL',
"W": 'STATUS_WAKING'
}
_CONN_NONE = 'CONN_NONE'
_TCP_STATUSES = {
"01": 'CONN_ESTABLISHED',
"02": 'CONN_SYN_SENT',
"03": 'CONN_SYN_RECV',
"04": 'CONN_FIN_WAIT1',
"05": 'CONN_FIN_WAIT2',
"06": 'CONN_TIME_WAIT',
"07": 'CONN_CLOSE',
"08": 'CONN_CLOSE_WAIT',
"09": 'CONN_LAST_ACK',
"0A": 'CONN_LISTEN',
"0B": 'CONN_CLOSING'
}
CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
[docs]def boot_time():
"""Return the system boot time expressed in seconds since the epoch.
"""
decorators.needlinux(True)
with open('/proc/stat', 'rb') as fhandle:
for line in fhandle:
if line.startswith(b'btime'):
ret = float(line.strip().split()[1])
return ret
raise RuntimeError("line 'btime' not found in /proc/stat")
# user
# (1) Time spent in user mode.
# nice
# (2) Time spent in user mode with low priority (nice).
# system
# (3) Time spent in system mode.
# idle
# (4) Time spent in the idle task.
# This value should be USER_HZ times the
# second entry in the /proc/uptime pseudo-file.
# iowait (since Linux 2.5.41)
# (5) Time waiting for I/O to complete.
# irq (since Linux 2.6.0-test4)
# (6) Time servicing interrupts.
# softirq (since Linux 2.6.0-test4)
# (7) Time servicing softirqs.
# steal (since Linux 2.6.11)
# (8) Stolen time, which is the time spent in other
# operating systems when
# running in a virtualized environment
# guest (since Linux 2.6.24)
# (9) Time spent running a virtual CPU for
# guest operating systems under the
# control of the Linux kernel.
# guest_nice (since Linux 2.6.33)
# (10) Time spent running a niced guest
# (virtual CPU for guest operating systems under the
# control of the Linux kernel).
_CPU_COLUMNS = [
'usr',
'nice',
'system',
'idle',
'iowait',
'irq',
'softirq',
'steal',
'guest'
]
[docs]class CPUInfo(collections.namedtuple('CPUInfo', _CPU_COLUMNS)):
"""
CPUInfo is used for get_cpu_usage function. The following attr will be
in the namedtuple:
usr,
nice,
system,
idle,
iowait,
irq,
softirq,
steal,
guest
I.g.
::
import cup
# count cpu usage
from cup.res import linux
cpuinfo = linux.get_cpu_usage(intvl_in_sec=60)
print cpuinfo.usr
"""
[docs]def get_kernel_version():
"""
get linux kernel verions, e.g.('2', '6', '32'):
"""
decorators.needlinux(True)
versions = os.uname()[2]
# version = versions[0: versions.find('_')]
return tuple([info for info in versions.split('.')])
_COLUMN_LOCK = threading.Lock()
_COLUMN_LOCK.acquire()
if sys.platform.startswith('linux'):
if get_kernel_version() >= ('2', '6', '33') and \
_CPU_COLUMNS.count('guest_nice') <= 0:
_CPU_COLUMNS.append('guest_nice')
_COLUMN_LOCK.release()
# # # # begin system infos # # # #
[docs]def get_boottime_since_epoch():
"""
:return:
return boot time (seconds) since epoch
"""
decorators.needlinux(True)
fhandle = open('/proc/stat', 'r')
try:
for line in fhandle:
if line.startswith('btime'):
return float(line.strip().split()[1])
raise RuntimeError("line 'btime' not found")
finally:
fhandle.close()
[docs]def get_cpu_nums():
"""
get cpu nums
"""
decorators.needlinux(True)
try:
return os.sysconf("SC_NPROCESSORS_ONLN")
except ValueError:
# as a second fallback we try to parse /proc/cpuinfo
num = 0
fhandle = open('/proc/cpuinfo', 'r')
try:
lines = fhandle.readlines()
finally:
fhandle.close()
for line in lines:
if line.lower().startswith('processor'):
num += 1
# unknown format (e.g. amrel/sparc architectures), see:
# http://code.google.com/p/psutil/issues/detail?id=200
# try to parse /proc/stat as a last resort
if num == 0:
fhandle = open('/proc/stat', 'r')
try:
lines = fhandle.readlines()
finally:
fhandle.close()
search = re.compile(r'cpu\d')
for line in lines:
line = line.split(' ')[0]
if search.match(line):
num += 1
if num == 0:
raise RuntimeError("couldn't determine platform's NUM_CPUS")
return num
[docs]def get_disk_usage_all(raw=False):
"""
:param raw:
if raw is True, will use Byte as the measure. Automatically use
MB/GB otherwise.
:return:
return a dict of disk usage
"""
decorators.needlinux(True)
byte2gb = 1024 * 1024 * 1024
byte2mb = 1024 * 1024
st = os.statvfs("/")
free = st.f_bavail * st.f_frsize
total = st.f_blocks * st.f_frsize
unit = "Byte"
if not raw:
if total > byte2gb:
free, total = \
free / byte2gb, total / byte2gb
unit = "GB"
elif total > byte2mb:
free, total = \
free / byte2mb, total / byte2mb
unit = "MB"
return {
"totalSpace": total,
"usedSpace": total - free,
"freeSpace": free,
"unit": unit
}
[docs]def get_disk_info():
"""
:return:
get disk info of the system
"""
decorators.needlinux(True)
info = os.popen("df -lh")
all_diskinfo = []
for line in enumerate(info.readlines()):
if line[0] != 0:
blockinfo = []
for block in line[1].split(" "):
if len(block) != 0:
blockinfo.append(block)
all_diskinfo.append({
"FileSystem": blockinfo[0],
"Size": blockinfo[1],
"Used": blockinfo[2],
"Available": blockinfo[3],
"Percentage": blockinfo[4],
})
else:
continue
try:
return all_diskinfo
except Exception as exc:
raise RuntimeError("couldn't find disk") from exc
[docs]class MemInfo(collections.namedtuple('vmem', ' '.join([
# all platforms
'total', 'available', 'percent', 'used', 'free',
# linux specific
'active',
'inactive',
'buffers',
'cached']))):
"""
get_meminfo will get memory info (a namedtuple returned:
total, available, percent, used, free,
active,
inactive,
buffers,
cached)
E.g.:
::
from cup.res import linux
meminfo = linux.get_meminfo()
print(meminfo.total)
print(meminfo.available)
"""
def _get_cput_by_stat(coreindex=None):
"""
:param coreindex:
None for all cpu cores
"""
usr = nice = system = idle = iowait = irq = softirq = steal = \
guest = guest_nice = float(0.0)
fp = open('/proc/stat', 'r')
if coreindex is None:
match_str = 'cpu'
else:
match_str = 'cpu{0}'.format(coreindex)
try:
for line in fp:
if line.startswith(match_str) and line.split()[0] == match_str:
if _CPU_COLUMNS.count('guest_nice') > 0:
(usr, nice, system, idle, iowait, irq,
softirq, steal, guest, guest_nice) = line.split()[1:]
else:
(usr, nice, system, idle, iowait, irq,
softirq, steal, guest) = line.split()[1:]
break
# pylint: disable=W0703
except Exception:
warnings.warn('Get cpuinfo failed', RuntimeWarning)
finally:
fp.close()
if _CPU_COLUMNS.count('guest_nice') > 0:
return CPUInfo(
float(usr), float(nice), float(system),
float(idle), float(iowait), float(irq),
float(softirq), float(steal), float(guest), float(guest_nice)
)
else:
return CPUInfo(
usr, nice, system, idle, iowait, irq,
softirq, steal, guest
)
[docs]def get_cpu_usage(intvl_in_sec=1):
"""
get cpu usage statistics during a time period (intvl_in_sec), return a
namedtuple CPUInfo
"""
decorators.needlinux(True)
cup.unittest.assert_gt(intvl_in_sec, 0)
ret = []
for i in range(0, len(_CPU_COLUMNS)):
ret.append(0)
cpu_info0 = _get_cput_by_stat()
time.sleep(intvl_in_sec)
cpu_info1 = _get_cput_by_stat()
total = float(0.0)
for i in range(0, len(cpu_info1)):
minus = float(cpu_info1[i]) - float(cpu_info0[i])
total = total + minus
ret[i] = minus
for i in range(0, len(ret)):
ret[i] = ret[i] * 100 / total
return CPUInfo(*ret)
[docs]def get_cpu_core_usage(coreindex, intvl_in_sec=1):
"""
:param index:
cpu core index
"""
decorators.needlinux(True)
cup.unittest.assert_gt(intvl_in_sec, 0)
ret = []
ret = [0 for _ in _CPU_COLUMNS]
cpu_info0 = _get_cput_by_stat(coreindex)
time.sleep(intvl_in_sec)
cpu_info1 = _get_cput_by_stat(coreindex)
total = float(0.0)
for i in range(0, len(cpu_info1)):
minus = float(cpu_info1[i]) - float(cpu_info0[i])
total = total + minus
ret[i] = minus
for i in range(0, len(ret)):
ret[i] = ret[i] * 100 / total
return CPUInfo(*ret)
[docs]def get_meminfo():
"""
get system memory info
"""
decorators.needlinux(True)
total = free = buffers = cached = active = inactive = None
fp = open('/proc/meminfo', 'r')
try:
for line in fp:
if line.startswith('MemTotal'):
total = int(line.split()[1]) * 1024
elif line.startswith('MemFree'):
free = int(line.split()[1]) * 1024
elif line.startswith('Buffers'):
buffers = int(line.split()[1]) * 1024
elif line.startswith('Cached:'):
cached = int(line.split()[1]) * 1024
elif line.startswith('Active:'):
active = int(line.split()[1]) * 1024
elif line.startswith('Inactive:'):
inactive = int(line.split()[1]) * 1024
if cached is not None \
and active is not None \
and inactive is not None:
break
else:
# we might get here when dealing with exotic Linux flavors, see:
# http://code.google.com/p/psutil/issues/detail?id=313
msg = "'cached', 'active' and 'inactive' memory stats couldn't " \
"be determined and were set to 0"
warnings.warn(msg, RuntimeWarning)
total = free = buffers = cached = active = inactive = 0
finally:
fp.close()
avail = free + buffers + cached
used = total - free
percent = int((total - avail) * 100 / total)
return MemInfo(
total,
avail,
percent,
used,
free,
active,
inactive,
buffers,
cached
)
[docs]class SWAPINFO(
collections.namedtuple(
'SwapInfo',
[
'total',
'free',
'used',
'sin',
'sout'
]
)
):
"""
get_swapinfo will return a SWAPINFO
"""
[docs]def get_swapinfo():
"""
get swamp info of the system
"""
decorators.needlinux(True)
fp = open('/proc/swaps', 'r')
reg = '([\\/A-Za-z0-9]+)[\\s]+([a-z]+)[\\s]+([0-9]+)'\
'[\\s]+([0-9]+)[\\s]+([\\-0-9]+).*'
regobj = re.compile(reg)
total = used = 0
try:
for line in fp:
if regobj.search(line) is not None:
sp = line.split()
total += int(sp[2])
used += int(sp[3])
finally:
fp.close()
if total == 0:
total = used = 0
msg = 'Failed to get total from /proc/swaps or '\
'the system does not have swap mounted.' \
' Total and used were set to 0'
warnings.warn(msg, RuntimeWarning)
sin = sout = None
fp = open('/proc/vmstat', 'r')
try:
for line in fp:
# values are expressed in 4 kilo bytes, we want bytes instead
if line.startswith('pswpin'):
sin = int(line.split(' ')[1]) * 4 * 1024
elif line.startswith('pswpout'):
sout = int(line.split(' ')[1]) * 4 * 1024
if sin is not None and sout is not None:
break
else:
# we might get here when dealing with exotic Linux flavors, see:
# http://code.google.com/p/psutil/issues/detail?id=313
msg = "'sin' and 'sout' swap memory stats couldn't " \
"be determined and were set to 0"
warnings.warn(msg, RuntimeWarning)
sin = sout = 0
finally:
fp.close()
free = total - used
return SWAPINFO(total, free, used, sin, sout)
[docs]def net_io_counters():
"""
get network statistics with a list of namedtuple
(bytes_sent, bytes_recv, packets_sent, packets_recv,
errin, errout, dropin, dropout)
:return:
::
# return a dict like below:
{
'lo':
(
235805206817, 235805206817, 315060887, 315060887, 0, 0, 0, 0
),
'eth1':
(
18508976300272, 8079464483699, 32776530804,
32719666038, 0, 0, 708015, 0
),
'eth0':
(
0, 0, 0, 0, 0, 0, 0, 0
)
}
"""
decorators.needlinux(True)
fhandle = open("/proc/net/dev", "r")
try:
lines = fhandle.readlines()
finally:
fhandle.close()
retdict = {}
for line in lines[2:]:
colon = line.rfind(':')
assert colon > 0, repr(line)
name = line[:colon].strip()
fields = line[colon + 1:].strip().split()
bytes_recv = int(fields[0])
packets_recv = int(fields[1])
errin = int(fields[2])
dropin = int(fields[3])
bytes_sent = int(fields[8])
packets_sent = int(fields[9])
errout = int(fields[10])
dropout = int(fields[11])
retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv,
errin, errout, dropin, dropout)
return retdict
[docs]def get_net_through(str_interface):
"""
get network interface statistics by a interface (eth0, e,g,)
"""
decorators.needlinux(True)
rx_bytes = tx_bytes = -1
fp = open('/proc/net/dev', 'r')
try:
for line in fp:
if str_interface in line:
data = line.split('%s:' % str_interface)[1].split()
rx_bytes, tx_bytes = (data[0], data[8])
finally:
fp.close()
if rx_bytes < 0 or tx_bytes < 0:
msg = 'Failed to parse /proc/net/dev'
warnings.warn(msg, RuntimeWarning)
cup.unittest.assert_ge(rx_bytes, 0)
cup.unittest.assert_ge(tx_bytes, 0)
return (int(rx_bytes), int(tx_bytes))
[docs]def get_net_transmit_speed(str_interface, intvl_in_sec=1):
"""
get network interface write/read speed
E.g.
::
from cup.res import linux
print(linux.get_net_transmit_speed('eth1', 5))
"""
decorators.needlinux(True)
cup.unittest.assert_gt(intvl_in_sec, 0)
rx_bytes0 = get_net_through(str_interface)[0]
time.sleep(intvl_in_sec)
rx_bytes1 = get_net_through(str_interface)[0]
return (rx_bytes1 - rx_bytes0) / intvl_in_sec
def get_net_recv_speed(str_interface, intvl_in_sec):
"""
get average network recv-speed during a time period (intvl_in_sec)
"""
decorators.needlinux(True)
cup.unittest.assert_gt(intvl_in_sec, 0)
tx_bytes0 = get_net_through(str_interface)[1]
time.sleep(intvl_in_sec)
tx_bytes1 = get_net_through(str_interface)[1]
return (tx_bytes1 - tx_bytes0) / intvl_in_sec
def wrap_exceptions(fun):
"""
Decorator which translates bare OSError and IOError exceptions
into err.NoSuchProcess and err.AccessDenied.
"""
@wraps(fun)
def wrapper(self, *args, **kwargs):
"""
internal wrapper for wrap_exceptions
"""
try:
return fun(self, *args, **kwargs)
except EnvironmentError as error:
# ENOENT (no such file or directory) gets raised on open().
# ESRCH (no such process) can get raised on read() if
# process is gone in meantime.
errinfo = sys.exc_info()[1]
if errinfo.errno in (errno.ENOENT, errno.ESRCH):
# pylint: disable=W0212
raise errinfo.NoSuchProcess(self._pid, self._process_name)
if errinfo.errno in (errno.EPERM, errno.EACCES):
raise err.ResException('Permission denied') from error
raise error
return wrapper
[docs]def pids():
"""Returns a list of PIDs currently running on the system."""
decorators.needlinux(True)
return [int(x) for x in os.listdir(b'/proc') if x.isdigit()]
_pmap = {}
[docs]def process_iter():
"""
Return a generator yielding a Process instance for all
running processes.
Every new Process instance is only created once and then cached
into an internal table which is updated every time this is used.
Cached Process instances are checked for identity so that you're
safe in case a PID has been reused by another process, in which
case the cached instance is updated.
yuetian:
1. the origion use a check_running function to check whether
PID has been reused by another process in which case yield a new
Process instance
hint:i did not use check_running function because the container of
pid is set
2. the origion use a sorted list(_pmap.items()) +
list(dict.fromkeys(new_pids).items() to get pid and proc to make
res.proc is only a instance of a pid Process
hint(bugs):i did not use fromkeys(new_pids) because
i did not get the meanning
of using proc
"""
decorators.needlinux(True)
pid_set = set(pids())
for pid in pid_set:
try:
check_process = Process(pid)
_ = check_process.get_process_name() # have a try getting the name
except err.NoSuchProcess:
pass
else:
yield check_process
# pylint: disable=R0904
[docs]class Process(object):
"""
Process info query (given a pid)
"""
__slots__ = ["pid", "_process_name", "_create_time"]
def __init__(self, pid):
decorators.needlinux(True)
self.pid = pid
self._process_name = None
self._create_time = None
if not os.path.lexists("/proc/%s/exe" % self.pid):
raise err.NoSuchProcess(self.pid, self._process_name)
else:
self._create_time = self.create_time()
[docs] def create_time(self):
"""return -1 represent the process does not exists
"""
# if not os.path.lexists("/proc/%s/exe" % self._pid):
# raise err.NoSuchProcess(self._pid, self._process_name)
# else:
try:
with open("/proc/%s/stat" % self.pid, 'rb') as f:
stripped = f.read().strip()
except IOError:
return -1
else:
stripped = stripped[stripped.rfind(b')') + 2:]
stripped = stripped[stripped.rfind(b')') + 2:]
values = stripped.split(b' ')
bt = boot_time()
return (float(values[19]) / CLOCK_TICKS) + bt
[docs] def children(self, recursive=False):
"""Return the children of this process as a list of Process
instances, pre-emptively checking whether PID has been reused.
If recursive is True return all the parent descendants.
Example (A == this process):
::
A ─┐
│
├─ B (child) ─┐
│ └─ X (grandchild) ─┐
│ └─ Y (great grandchild)
├─ C (child)
└─ D (child)
"""
# ppid_map = None
ret = []
if not recursive:
# 'slow' version, common to all platforms except Windows
for p in process_iter():
if p.get_process_ppid() == self.pid:
# if child happens to be older than its parent
# (self) it means child's PID has been reused
if self.create_time() <= p.create_time():
ret.append(p.pid)
else:
# construct a dict where 'values' are all the processes
# having 'key' as their parent
table = collections.defaultdict(list)
for p in process_iter():
try:
table[p.get_process_ppid()].append(p)
except (err.NoSuchProcess):
pass
# At this point we have a mapping table where table[self.pid]
# are the current process' children.
# Below, we look for all descendants recursively, similarly
# to a recursive function call.
checkpids = [self.pid]
for pid in checkpids:
for child in table[pid]:
try:
# if child happens to be older than its parent
# (self) it means child's PID has been reused
intime = self.create_time() <= child.create_time()
except (err.NoSuchProcess):
pass
else:
if intime:
ret.append(child.pid)
if child.pid not in checkpids:
checkpids.append(child.pid)
return ret
[docs] @wrap_exceptions
def get_process_name(self):
"""
get process name of the process (for daemon process only)
"""
fhandle = open("/proc/%s/stat" % self.pid)
try:
name = fhandle.read().split(' ')[1].replace('(', '').replace(
')', ''
)
finally:
fhandle.close()
return name
[docs] def get_process_exe(self):
"""
get executable info of the process. If the process is a daemon, use
get_process_name instead!
"""
try:
exe = os.readlink("/proc/%s/exe" % self.pid)
except (OSError, IOError) as error:
errinfo = sys.exc_info()[1]
if errinfo.errno == errno.ENOENT:
# no such file error; might be raised also if the
# path actually exists for system processes with
# low pids (about 0-20)
if os.path.lexists("/proc/%s/exe" % self.pid):
return ""
else:
# ok, it is a process which has gone away
raise err.NoSuchProcess(self.pid, self._process_name)
if errinfo.errno in (errno.EPERM, errno.EACCES):
raise err.AccessDenied(self._process_name)
raise error
# readlink() might return paths containing null bytes causing
# problems when used with other fs-related functions (os.*,
# open(), ...)
exe = exe.replace('\x00', '')
# Certain names have ' (deleted)' appended. Usually this is
# bogus as the file actually exists. Either way that's not
# important as we don't want to discriminate executables which
# have been deleted.
if exe.endswith(" (deleted)") and not os.path.exists(exe):
exe = exe[:-10]
return exe
[docs] @wrap_exceptions
def get_process_cmdline(self):
"""
get cmdline
"""
fhandle = open("/proc/%s/cmdline" % self.pid)
try:
# return the args as a list
return [x for x in fhandle.read().split('\x00') if x]
finally:
fhandle.close()
__nt_io = collections.namedtuple(
'nt_io',
[
'rcount',
'wcount',
'rbytes',
'wbytes'
]
)
[docs] @wrap_exceptions
def get_process_io_counters(self):
"""
get io statistics info of network adapters.
"""
if not os.path.exists('/proc/%s/io' % os.getpid()):
raise err.NotSupportedError(
"couldn't find /proc/%s/io (kernel too old?)" % self.pid
)
fname = "/proc/%s/io" % self.pid
# pylint: disable=c0103
f = open(fname)
try:
rcount = wcount = rbytes = wbytes = None
for line in f:
if rcount is None and line.startswith("syscr"):
rcount = int(line.split()[1])
elif wcount is None and line.startswith("syscw"):
wcount = int(line.split()[1])
elif rbytes is None and line.startswith("read_bytes"):
rbytes = int(line.split()[1])
elif wbytes is None and line.startswith("write_bytes"):
wbytes = int(line.split()[1])
for _ in (rcount, wcount, rbytes, wbytes):
if _ is None:
raise err.NotSupportedError(
"couldn't read all necessary info from %r" % fname)
return self.__nt_io(rcount, wcount, rbytes, wbytes)
finally:
f.close()
_nt_cputimes = collections.namedtuple(
'nt_cputimes',
[
'utime',
'stime'
]
)
[docs] @wrap_exceptions
def get_cpu_times(self):
"""
get cpu times, return with a namedtuple (utime, stime)
"""
fdhandle = open("/proc/%s/stat" % self.pid)
try:
stripped = fdhandle.read().strip()
finally:
fdhandle.close()
# ignore the first two values ("pid (exe)")
stripped = stripped[stripped.find(')') + 2:]
values = stripped.split(' ')
utime = float(values[11]) / _CLOCK_TICKS
stime = float(values[12]) / _CLOCK_TICKS
return self._nt_cputimes(utime, stime)
[docs] @wrap_exceptions
def get_cpu_usage(self, interval=0.5):
"""get cpu usage"""
now = self.get_cpu_times()
time.sleep(interval)
then = self.get_cpu_times()
return float(
(then[0] - now[0] + then[1] - now[1]) * 100 / interval
)
[docs] @wrap_exceptions
def get_process_create_time(self):
"""
get process create time
"""
f = open("/proc/%s/stat" % self.pid)
try:
stripped = f.read().strip()
finally:
f.close()
# ignore the first two values ("pid (exe)")
stripped = stripped[stripped.rfind(')') + 2:]
values = stripped.split(' ')
# According to documentation, starttime is in field 21 and the
# unit is jiffies (clock ticks).
# We first divide it for clock ticks and then add uptime returning
# seconds since the epoch, in UTC.
starttime = (float(values[19]) / _CLOCK_TICKS) + \
get_boottime_since_epoch()
return starttime
_nt_meminfo = collections.namedtuple(
'nt_meminfo',
[
'rss',
'vms'
]
)
[docs] @wrap_exceptions
def get_memory_info(self):
"""
get memory info, return with a namedtuple (
rss vms shared text lib data dirty)
"""
f = open("/proc/%s/statm" % self.pid)
try:
vms, rss = f.readline().split()[:2]
return self._nt_meminfo(
int(rss) * _PAGESIZE,
int(vms) * _PAGESIZE
)
finally:
f.close()
_nt_ext_mem = collections.namedtuple(
'meminfo',
'rss vms shared text lib data dirty'
)
[docs] @wrap_exceptions
def get_ext_memory_info(self):
"""
return namedtuple with FIELDs below:
Example: ::
from cup.res import linux
process = linux.Process(pid)
print process.rss, process.dirty
+--------+-------------------------------------+------+------+
| FIELD | DESCRIPTION | AKA | TOP |
+========+=====================================+======+======+
| rss | resident set size | / | RES |
+--------+-------------------------------------+------+------+
| vms | total program size | size | VIRT |
+--------+-------------------------------------+------+------+
| shared | shared pages (from shared mappings) | / | SHR |
+--------+-------------------------------------+------+------+
| text | text ('code') | trs | CODE |
+--------+-------------------------------------+------+------+
| lib | library (unused in Linux 2.6) | lrs | / |
+--------+-------------------------------------+------+------+
| data | data + stack | drs | DATA |
+--------+-------------------------------------+------+------+
| dirty | dirty pages (unused in Linux 2.6) | dt | / |
+--------+-------------------------------------+------+------+
"""
f = open("/proc/%s/statm" % self.pid)
try:
vms, rss, shared, text, lib, data, dirty = \
[int(x) * _PAGESIZE for x in f.readline().split()[:7]]
finally:
f.close()
return self._nt_ext_mem(rss, vms, shared, text, lib, data, dirty)
_mmap_base_fields = ['path', 'rss', 'size', 'pss', 'shared_clean',
'shared_dirty', 'private_clean', 'private_dirty',
'referenced', 'anonymous', 'swap', ]
nt_mmap_grouped = collections.namedtuple(
'mmap', ' '.join(_mmap_base_fields)
)
nt_mmap_ext = collections.namedtuple(
'mmap', 'addr perms ' + ' '.join(_mmap_base_fields)
)
[docs] def get_memory_maps(self):
"""
get memory map (from /proc smaps file)
"""
# Return process's mapped memory regions as a list of nameduples.
# Fields are explained in 'man proc'; here is an updated (Apr 2012)
# version: http://goo.gl/fmebo
if not os.path.exists('/proc/%s/smaps' % os.getpid()):
msg = """
couldn't find /proc/%s/smaps; kernel < 2.6.14 or CONFIG_MMU
ernel configuration option is not enabled""" % self.pid
raise err.NotSupportedError(msg)
fhandle = None
try:
fhandle = open("/proc/%s/smaps" % self.pid)
first_line = fhandle.readline()
current_block = [first_line]
def get_blocks():
"""
internal get blocks for get_memory_maps
"""
data = {}
for line in fhandle:
fields = line.split(None, 5)
if not fields[0].endswith(':'):
# new block section
yield (current_block.pop(), data)
current_block.append(line)
else:
try:
data[fields[0]] = int(fields[1]) * 1024
except ValueError as exc:
if fields[0].startswith('VmFlags:'):
# see issue # 369
continue
else:
raise ValueError("don't know how to interpret"
" line %r" % line) from exc
yield (current_block.pop(), data)
if first_line: # smaps file can be empty
for header, data in get_blocks():
hfields = header.split(None, 5)
try:
addr, perms, offset, dev, inode, path = hfields
except ValueError:
addr, perms, offset, dev, inode, path = hfields + ['']
if not path:
path = '[anon]'
else:
path = path.strip()
yield (addr, perms, path,
data['Rss:'],
data.get('Size:', 0),
data.get('Pss:', 0),
data.get('Shared_Clean:', 0),
data.get('Shared_Dirty:', 0),
data.get('Private_Clean:', 0),
data.get('Private_Dirty:', 0),
data.get('Referenced:', 0),
data.get('Anonymous:', 0),
data.get('Swap:', 0))
fhandle.close()
except EnvironmentError as error:
# XXX - Can't use wrap_exceptions decorator as we're
# returning a generator; this probably needs some
# refactoring in order to avoid this code duplication.
if fhandle is not None:
fhandle.close()
errinfo = sys.exc_info()[1]
if errinfo.errno in (errno.ENOENT, errno.ESRCH):
raise err.NoSuchProcess(self.pid, self._process_name)
if errinfo.errno in (errno.EPERM, errno.EACCES):
raise err.AccessDenied(self._process_name)
raise error
except Exception as error:
if fhandle is not None:
fhandle.close()
raise error
fhandle.close()
[docs] @wrap_exceptions
def get_process_cwd(self):
"""
get process current working direcotry
"""
path = os.readlink("/proc/%s/cwd" % self.pid)
return path.replace('\x00', '')
_nt_ctxsw = collections.namedtuple(
'voluntary_ctxt_switches',
[
'vol',
'unvol'
]
)
[docs] @wrap_exceptions
def get_num_ctx_switches(self):
"""
get process context switch info (from /proc status file), return
with a namedtuple (vol, unvol)
"""
vol = unvol = None
f = open("/proc/%s/status" % self.pid)
try:
for line in f:
if line.startswith("voluntary_ctxt_switches"):
vol = int(line.split()[1])
elif line.startswith("nonvoluntary_ctxt_switches"):
unvol = int(line.split()[1])
if vol is not None and unvol is not None:
return self._nt_ctxsw(vol, unvol)
raise err.NotSupportedError(
"'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'"
"fields were not found in /proc/%s/status; the kernel is "
"probably older than 2.6.23" % self.pid)
finally:
f.close()
[docs] @wrap_exceptions
def get_process_num_threads(self):
"""
get threads num of this process
"""
f = open("/proc/%s/status" % self.pid)
try:
for line in f:
if line.startswith("Threads:"):
return int(line.split()[1])
raise err.NotSupportedError("line not found")
finally:
f.close()
_thread_tuple = collections.namedtuple(
'ntuple',
[
'thread_id',
'utime',
'stime'
]
)
[docs] @wrap_exceptions
def get_process_threads(self):
"""
get threads that is current using, return with a namedtuple (
thread_id, utime, stime)
"""
thread_ids = os.listdir("/proc/%s/task" % self.pid)
thread_ids.sort()
retlist = []
hit_enoent = False
for thread_id in thread_ids:
try:
f = open("/proc/%s/task/%s/stat" % (self.pid, thread_id))
except EnvironmentError as error:
err = sys.exc_info()[1]
if err.errno == errno.ENOENT:
# no such file or directory; it means thread
# disappeared on us
hit_enoent = True
continue
raise error
try:
st = f.read().strip()
finally:
f.close()
# ignore the first two values ("pid (exe)")
st = st[st.find(')') + 2:]
values = st.split(' ')
utime = float(values[11]) / _CLOCK_TICKS
stime = float(values[12]) / _CLOCK_TICKS
ntuple = self._thread_tuple(int(thread_id), utime, stime)
retlist.append(ntuple)
if hit_enoent:
# raise NSP if the process disappeared on us
os.stat('/proc/%s' % self.pid)
return retlist
[docs] @wrap_exceptions
def get_process_nice(self):
"""
get process nice
"""
f = open('/proc/%s/stat' % self.pid, 'r')
try:
data = f.read()
return int(data.split()[18])
finally:
f.close()
[docs] @wrap_exceptions
def get_process_status(self):
"""
get status of the current process (info from /proc/xxx/status)
"""
f = open("/proc/%s/status" % self.pid)
try:
for line in f:
if line.startswith("State:"):
letter = line.split()[1]
# FIXME: xxx is '?' legit? (we're not supposed to return
# it anyway)
return _PROC_STATUSES.get(letter, '?')
finally:
f.close()
_nt_openfile = collections.namedtuple(
'nt_openfile',
[
'file',
'fd'
]
)
[docs] @wrap_exceptions
def get_open_files(self):
"""
get opened file info
"""
retlist = []
files = os.listdir("/proc/%s/fd" % self.pid)
hit_enoent = False
for fhandle in files:
file = "/proc/%s/fd/%s" % (self.pid, fhandle)
if os.path.islink(file):
try:
file = os.readlink(file)
except OSError as error:
# ENOENT == file which is gone in the meantime
errinfo = sys.exc_info()[1]
if errinfo.errno == errno.ENOENT:
hit_enoent = True
continue
raise error
else:
# If file is not an absolute path there's no way
# to tell whether it's a regular file or not,
# so we skip it. A regular file is always supposed
# to be absolutized though.
if file.startswith('/') and os.path.isfile(file):
ntuple = self._nt_openfile(file, int(fhandle))
retlist.append(ntuple)
if hit_enoent:
# raise NSP if the process disappeared on us
os.stat('/proc/%s' % self.pid)
return retlist
_nt_connection = collections.namedtuple(
'nt_connection',
[
'fd',
'family',
'type',
'laddr',
'raddr',
'status'
]
)
def _process_for_connections(self, inodes, file, family, type_):
retlist = []
try:
fpointer = open(file, 'r')
except IOError as error:
# IPv6 not supported on this platform
errinfo = sys.exc_info()[1]
if errinfo.errno == errno.ENOENT and file.endswith('6'):
return []
else:
raise error
try:
fpointer.readline() # skip the first line
for line in fpointer:
# IPv4 / IPv6
if family in (socket.AF_INET, socket.AF_INET6):
_, laddr, raddr, status, _, _, _, _, _, inode = \
line.split()[:10]
if inode in inodes:
laddr = self._decode_address(laddr, family)
raddr = self._decode_address(raddr, family)
if type_ == socket.SOCK_STREAM:
status = _TCP_STATUSES[status]
else:
status = _CONN_NONE
filed = int(inodes[inode])
conn = self._nt_connection(
filed, family, type_, laddr,
raddr, status
)
retlist.append(conn)
elif family == socket.AF_UNIX:
tokens = line.split()
_, _, _, _, type_, _, inode = tokens[0:7]
if inode in inodes:
if len(tokens) == 8:
path = tokens[-1]
else:
path = ""
filed = int(inodes[inode])
type_ = int(type_)
conn = self._nt_connection(
filed, family, type_, path,
None, _CONN_NONE
)
retlist.append(conn)
else:
raise ValueError(family)
return retlist
finally:
fpointer.close()
[docs] @wrap_exceptions
def get_connections(self, kind='inet'):
"""
get network connection info, each item contains a namedtuple
(fd family type laddr raddr status)
:param kind:
kind='inet' by default
:return:
a list of network connection info
"""
# Return connections opened by process as a list of namedtuples.
# The kind parameter filters for connections that fit the following
# criteria:
# Kind Value Number of connections using
# inet IPv4 and IPv6
# inet4 IPv4
# inet6 IPv6
# tcp TCP
# tcp4 TCP over IPv4
# tcp6 TCP over IPv6
# udp UDP
# udp4 UDP over IPv4
# udp6 UDP over IPv6
# all the sum of all the possible families and protocols
# Note: in case of UNIX sockets we're only able to determine the
# local bound path while the remote endpoint is not retrievable:
# http://goo.gl/R3GHM
inodes = {}
# os.listdir() is gonna raise a lot of access denied
# exceptions in case of unprivileged user; that's fine:
# lsof does the same so it's unlikely that we can to better.
for fdhandle in os.listdir("/proc/%s/fd" % self.pid):
try:
inode = os.readlink("/proc/%s/fd/%s" % (self.pid, fdhandle))
except OSError:
continue
if inode.startswith('socket:['):
# the process is using a socket
inode = inode[8:][:-1]
inodes[inode] = fdhandle
if not inodes:
# no connections for this process
return []
tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM)
tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM)
udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM)
udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM)
unix = ("unix", socket.AF_UNIX, None)
tmap = {
"all": (tcp4, tcp6, udp4, udp6, unix),
"tcp": (tcp4, tcp6),
"tcp4": (tcp4, ),
"tcp6": (tcp6, ),
"udp": (udp4, udp6),
"udp4": (udp4, ),
"udp6": (udp6, ),
"unix": (unix, ),
"inet": (tcp4, tcp6, udp4, udp6),
"inet4": (tcp4, udp4),
"inet6": (tcp6, udp6),
}
if kind not in tmap:
raise ValueError("invalid %r kind argument; choose between %s"
% (kind, ', '.join([repr(x) for x in tmap])))
ret = []
for filed, family, type_ in tmap[kind]:
ret += self._process_for_connections(
inodes, "/proc/net/{}".format(filed), family, type_)
# raise NSP if the process disappeared on us
os.stat('/proc/%s' % self.pid)
return ret
[docs] @wrap_exceptions
def get_num_fds(self):
"""
get opened file descriptor num
"""
return len(os.listdir("/proc/%s/fd" % self.pid))
[docs] @wrap_exceptions
def get_process_ppid(self):
"""
get parent process id
"""
f = open("/proc/%s/status" % self.pid)
try:
for line in f:
if line.startswith("PPid:"):
# PPid: nnnn
return int(line.split()[1])
raise err.NotSupportedError('line not found')
finally:
f.close()
_nt_uids = collections.namedtuple(
'nt_uids',
[
'real',
'effective',
'saved'
]
)
[docs] @wrap_exceptions
def get_process_uids(self):
"""
get uid info of the process, will return a namedtuple
"""
fdhandle = open("/proc/%s/status" % self.pid)
try:
for line in fdhandle:
if line.startswith('Uid:'):
_, real, effective, saved, fs = line.split()
return self._nt_uids(int(real), int(effective), int(saved))
raise err.NotSupportedError("line not found")
finally:
fdhandle.close()
_nt_gids = collections.namedtuple(
'nt_gids',
[
'real',
'effective',
'saved'
]
)
[docs] @wrap_exceptions
def get_process_gids(self):
"""
get process gid, namedtuple will be returned
(with attrs .real .effective .saved)
"""
f = open("/proc/%s/status" % self.pid)
try:
for line in f:
if line.startswith('Gid:'):
_, real, effective, saved, fs = line.split()
return self._nt_gids(int(real), int(effective), int(saved))
raise err.NotSupportedError("line not found")
finally:
f.close()
@staticmethod
def _decode_address(addr, family):
"""Accept an "ip:port" address as displayed in /proc/net/*
and convert it into a human readable form, like:
"0500000A:0016" -> ("10.0.0.5", 22)
"0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521)
The IP address portion is a little or big endian four-byte
hexadecimal number; that is, the least significant byte is listed
first, so we need to reverse the order of the bytes to convert it
to an IP address.
The port is represented as a two-byte hexadecimal number.
Reference:
http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
"""
ipadr, port = addr.split(':')
port = int(port, 16)
if sys.version_info >= (3, 0):
ipadr = ipadr.encode('ascii')
# this usually refers to a local socket in listen mode with
# no end-points connected
if not port:
return ()
if family == socket.AF_INET:
# see: http://code.google.com/p/psutil/issues/detail?id=201
if sys.byteorder == 'little':
ipadr = socket.inet_ntop(family, base64.b16decode(ipadr)[::-1])
else:
ipadr = socket.inet_ntop(family, base64.b16decode(ipadr))
else: # IPv6
# old version - let's keep it, just in case...
# ip = ip.decode('hex')
# return socket.inet_ntop(socket.AF_INET6,
# ''.join(ip[i:i+4][::-1] for i in range(0, 16, 4)))
ipadr = base64.b16decode(ipadr)
# see: http://code.google.com/p/psutil/issues/detail?id=201
if sys.byteorder == 'little':
ipadr = socket.inet_ntop(
socket.AF_INET6,
struct.pack('>4I', *struct.unpack('<4I', ipadr)))
else:
ipadr = socket.inet_ntop(
socket.AF_INET6,
struct.pack('<4I', *struct.unpack('<4I', ipadr)))
return (ipadr, port)
[docs] def getpgid(self):
"""
return process group id (not pid, not gid either)
"""
return os.getpgid(self.pid)
if '__main__' == __name__:
# system info
print(get_boottime_since_epoch())
print(get_cpu_nums())
print(get_kernel_version())
print(get_disk_usage_all())
print(get_disk_info())
# resouce info
print(get_cpu_usage(2))
print(get_cpu_core_usage(0, 0.5))
print(get_meminfo())
print(get_swapinfo())
print(get_net_through('xgbe0'))
print(get_net_transmit_speed('xgbe0', 5))
print(get_net_recv_speed('xgbe0', 5))
print(net_io_counters())
# vi:set tw=0 ts=4 sw=4 nowrap fdm=indent