Use Python spacing
- remove tabs and replace with 4 spaces
- specify python3
- Remove spaces on empty lines
Signed-off-by: John Kacur <jkacur(a)redhat.com>
---
procfs/procfs.py | 1882 ++++++++++++++++++++++-----------------------
procfs/procfs.pyc | Bin 0 -> 40780 bytes
2 files changed, 941 insertions(+), 941 deletions(-)
create mode 100644 procfs/procfs.pyc
diff --git a/procfs/procfs.py b/procfs/procfs.py
index b0ce2514063d..83d142f4f27f 100755
--- a/procfs/procfs.py
+++ b/procfs/procfs.py
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#! /usr/bin/python3
# -*- python -*-
# -*- coding: utf-8 -*-
#
@@ -28,188 +28,188 @@ from six.moves import range
VERSION="0.5"
def process_cmdline(pid_info):
- """
- Returns the process command line, if available in the given `process' class, if
- not available, falls back to using the comm (short process name) in its pidstat key.
- """
- if pid_info["cmdline"]:
- return reduce(lambda a, b: a + " %s" % b,
pid_info["cmdline"]).strip()
+ """
+ Returns the process command line, if available in the given `process' class, if
+ not available, falls back to using the comm (short process name) in its pidstat key.
+ """
+ if pid_info["cmdline"]:
+ return reduce(lambda a, b: a + " %s" % b,
pid_info["cmdline"]).strip()
- return pid_info["stat"]["comm"]
+ return pid_info["stat"]["comm"]
class pidstat:
- """Provides a dictionary to access the fields in the per process
/proc/PID/stat
- files.
+ """Provides a dictionary to access the fields in the per process
/proc/PID/stat
+ files.
- One can obtain the available fields asking for the keys of the dictionary, e.g.:
+ One can obtain the available fields asking for the keys of the dictionary, e.g.:
- >>> p = procfs.pidstat(1)
- >>> print p.keys()
- ['majflt', 'rss', 'cnswap', 'cstime', 'pid',
'session', 'startstack', 'startcode', 'cmajflt',
'blocked', 'exit_signal', 'minflt', 'nswap',
'environ', 'priority', 'state', 'delayacct_blkio_ticks',
'policy', 'rt_priority', 'ppid', 'nice', 'cutime',
'endcode', 'wchan', 'num_threads', 'sigcatch',
'comm', 'stime', 'sigignore', 'tty_nr', 'kstkeip',
'utime', 'tpgid', 'itrealvalue', 'kstkesp',
'rlim', 'signal', 'pgrp', 'flags', 'starttime',
'cminflt', 'vsize', 'processor']
+ >>> p = procfs.pidstat(1)
+ >>> print p.keys()
+ ['majflt', 'rss', 'cnswap', 'cstime',
'pid', 'session', 'startstack', 'startcode',
'cmajflt', 'blocked', 'exit_signal', 'minflt',
'nswap', 'environ', 'priority', 'state',
'delayacct_blkio_ticks', 'policy', 'rt_priority', 'ppid',
'nice', 'cutime', 'endcode', 'wchan',
'num_threads', 'sigcatch', 'comm', 'stime',
'sigignore', 'tty_nr', 'kstkeip', 'utime',
'tpgid', 'itrealvalue', 'kstkesp', 'rlim',
'signal', 'pgrp', 'flags', 'starttime', 'cminflt',
'vsize', 'processor']
- And then access the various process properties using it as a dictionary:
+ And then access the various process properties using it as a dictionary:
- >>> print p['comm']
- systemd
- >>> print p['priority']
- 20
- >>> print p['state']
- S
+ >>> print p['comm']
+ systemd
+ >>> print p['priority']
+ 20
+ >>> print p['state']
+ S
- Please refer to the 'procfs(5)' man page, by using:
+ Please refer to the 'procfs(5)' man page, by using:
- $ man 5 procfs
+ $ man 5 procfs
- To see information for each of the above fields, it is part of the
+ To see information for each of the above fields, it is part of the
'man-pages' RPM package.
- """
-
- # Entries with the same value, the one with a comment after it is the
- # more recent, having replaced the other name in v4.1-rc kernel times.
-
- PF_ALIGNWARN = 0x00000001
- PF_STARTING = 0x00000002
- PF_EXITING = 0x00000004
- PF_EXITPIDONE = 0x00000008
- PF_VCPU = 0x00000010
- PF_WQ_WORKER = 0x00000020 # /* I'm a workqueue worker */
- PF_FORKNOEXEC = 0x00000040
- PF_MCE_PROCESS = 0x00000080 # /* process policy on mce errors */
- PF_SUPERPRIV = 0x00000100
- PF_DUMPCORE = 0x00000200
- PF_SIGNALED = 0x00000400
- PF_MEMALLOC = 0x00000800
- PF_NPROC_EXCEEDED= 0x00001000 # /* set_user noticed that RLIMIT_NPROC was exceeded */
- PF_FLUSHER = 0x00001000
- PF_USED_MATH = 0x00002000
- PF_USED_ASYNC = 0x00004000 # /* used async_schedule*(), used by module init */
- PF_NOFREEZE = 0x00008000
- PF_FROZEN = 0x00010000
- PF_FSTRANS = 0x00020000
- PF_KSWAPD = 0x00040000
- PF_MEMALLOC_NOIO = 0x00080000 # /* Allocating memory without IO involved */
- PF_SWAPOFF = 0x00080000
- PF_LESS_THROTTLE = 0x00100000
- PF_KTHREAD = 0x00200000
- PF_RANDOMIZE = 0x00400000
- PF_SWAPWRITE = 0x00800000
- PF_SPREAD_PAGE = 0x01000000
- PF_SPREAD_SLAB = 0x02000000
- PF_THREAD_BOUND = 0x04000000
- PF_NO_SETAFFINITY = 0x04000000 # /* Userland is not allowed to meddle with cpus_allowed
*/
- PF_MCE_EARLY = 0x08000000 # /* Early kill for mce process policy */
- PF_MEMPOLICY = 0x10000000
- PF_MUTEX_TESTER = 0x20000000
- PF_FREEZER_SKIP = 0x40000000
- PF_FREEZER_NOSIG = 0x80000000
- PF_SUSPEND_TASK = 0x80000000 # /* this thread called freeze_processes and should not be
frozen */
-
- proc_stat_fields = [ "pid", "comm", "state",
"ppid", "pgrp", "session",
- "tty_nr", "tpgid", "flags", "minflt",
"cminflt",
- "majflt", "cmajflt", "utime", "stime",
"cutime",
- "cstime", "priority", "nice",
"num_threads",
- "itrealvalue", "starttime", "vsize",
"rss",
- "rlim", "startcode", "endcode",
"startstack",
- "kstkesp", "kstkeip", "signal",
"blocked",
- "sigignore", "sigcatch", "wchan",
"nswap",
- "cnswap", "exit_signal", "processor",
- "rt_priority", "policy",
- "delayacct_blkio_ticks", "environ" ]
-
- def __init__(self, pid, basedir = "/proc"):
- self.pid = pid
- self.load(basedir)
-
- def __getitem__(self, fieldname):
- return self.fields[fieldname]
-
- def keys(self):
- return list(self.fields.keys())
-
- def values(self):
- return list(self.fields.values())
-
- def has_key(self, fieldname):
- return fieldname in self.fields
-
- def items(self):
- return self.fields
-
- def __contains__(self, fieldname):
- return fieldname in self.fields
-
- def load(self, basedir = "/proc"):
- f = open("%s/%d/stat" % (basedir, self.pid))
- fields = f.readline().strip().split(') ')
- f.close()
- fields = fields[0].split(' (') + fields[1].split()
- self.fields = {}
- nr_fields = min(len(fields), len(self.proc_stat_fields))
- for i in range(nr_fields):
- attrname = self.proc_stat_fields[i]
- value = fields[i]
- if attrname == "comm":
- self.fields["comm"] = value.strip('()')
- else:
- try:
- self.fields[attrname] = int(value)
- except:
- self.fields[attrname] = value
-
- def is_bound_to_cpu(self):
- """
- Returns true if this process has a fixed smp affinity mask,
+ """
+
+ # Entries with the same value, the one with a comment after it is the
+ # more recent, having replaced the other name in v4.1-rc kernel times.
+
+ PF_ALIGNWARN = 0x00000001
+ PF_STARTING = 0x00000002
+ PF_EXITING = 0x00000004
+ PF_EXITPIDONE = 0x00000008
+ PF_VCPU = 0x00000010
+ PF_WQ_WORKER = 0x00000020 # /* I'm a workqueue worker */
+ PF_FORKNOEXEC = 0x00000040
+ PF_MCE_PROCESS = 0x00000080 # /* process policy on mce errors */
+ PF_SUPERPRIV = 0x00000100
+ PF_DUMPCORE = 0x00000200
+ PF_SIGNALED = 0x00000400
+ PF_MEMALLOC = 0x00000800
+ PF_NPROC_EXCEEDED= 0x00001000 # /* set_user noticed that RLIMIT_NPROC was exceeded
*/
+ PF_FLUSHER = 0x00001000
+ PF_USED_MATH = 0x00002000
+ PF_USED_ASYNC = 0x00004000 # /* used async_schedule*(), used by module init */
+ PF_NOFREEZE = 0x00008000
+ PF_FROZEN = 0x00010000
+ PF_FSTRANS = 0x00020000
+ PF_KSWAPD = 0x00040000
+ PF_MEMALLOC_NOIO = 0x00080000 # /* Allocating memory without IO involved */
+ PF_SWAPOFF = 0x00080000
+ PF_LESS_THROTTLE = 0x00100000
+ PF_KTHREAD = 0x00200000
+ PF_RANDOMIZE = 0x00400000
+ PF_SWAPWRITE = 0x00800000
+ PF_SPREAD_PAGE = 0x01000000
+ PF_SPREAD_SLAB = 0x02000000
+ PF_THREAD_BOUND = 0x04000000
+ PF_NO_SETAFFINITY = 0x04000000 # /* Userland is not allowed to meddle with
cpus_allowed */
+ PF_MCE_EARLY = 0x08000000 # /* Early kill for mce process policy */
+ PF_MEMPOLICY = 0x10000000
+ PF_MUTEX_TESTER = 0x20000000
+ PF_FREEZER_SKIP = 0x40000000
+ PF_FREEZER_NOSIG = 0x80000000
+ PF_SUSPEND_TASK = 0x80000000 # /* this thread called freeze_processes and should
not be frozen */
+
+ proc_stat_fields = [ "pid", "comm", "state",
"ppid", "pgrp", "session",
+ "tty_nr", "tpgid", "flags",
"minflt", "cminflt",
+ "majflt", "cmajflt", "utime",
"stime", "cutime",
+ "cstime", "priority", "nice",
"num_threads",
+ "itrealvalue", "starttime", "vsize",
"rss",
+ "rlim", "startcode", "endcode",
"startstack",
+ "kstkesp", "kstkeip", "signal",
"blocked",
+ "sigignore", "sigcatch", "wchan",
"nswap",
+ "cnswap", "exit_signal", "processor",
+ "rt_priority", "policy",
+ "delayacct_blkio_ticks", "environ" ]
+
+ def __init__(self, pid, basedir = "/proc"):
+ self.pid = pid
+ self.load(basedir)
+
+ def __getitem__(self, fieldname):
+ return self.fields[fieldname]
+
+ def keys(self):
+ return list(self.fields.keys())
+
+ def values(self):
+ return list(self.fields.values())
+
+ def has_key(self, fieldname):
+ return fieldname in self.fields
+
+ def items(self):
+ return self.fields
+
+ def __contains__(self, fieldname):
+ return fieldname in self.fields
+
+ def load(self, basedir = "/proc"):
+ f = open("%s/%d/stat" % (basedir, self.pid))
+ fields = f.readline().strip().split(') ')
+ f.close()
+ fields = fields[0].split(' (') + fields[1].split()
+ self.fields = {}
+ nr_fields = min(len(fields), len(self.proc_stat_fields))
+ for i in range(nr_fields):
+ attrname = self.proc_stat_fields[i]
+ value = fields[i]
+ if attrname == "comm":
+ self.fields["comm"] = value.strip('()')
+ else:
+ try:
+ self.fields[attrname] = int(value)
+ except:
+ self.fields[attrname] = value
+
+ def is_bound_to_cpu(self):
+ """
+ Returns true if this process has a fixed smp affinity mask,
not allowing it to be moved to a different set of CPUs.
- """
- return self.fields["flags"] & self.PF_THREAD_BOUND and \
- True or False
-
- def process_flags(self):
- """
- Returns a list with all the process flags known, details depend
- on kernel version, declared in the file include/linux/sched.h in
- the kernel sources.
-
- As of v4.2-rc7 these include (from include/linux/sched.h comments):
-
- PF_EXITING Getting shut down
- PF_EXITPIDONE Pi exit done on shut down
- PF_VCPU I'm a virtual CPU
- PF_WQ_WORKER I'm a workqueue worker
- PF_FORKNOEXEC Forked but didn't exec
- PF_MCE_PROCESS Process policy on mce errors
- PF_SUPERPRIV Used super-user privileges
- PF_DUMPCORE Dumped core
- PF_SIGNALED Killed by a signal
- PF_MEMALLOC Allocating memory
- PF_NPROC_EXCEEDED Set_user noticed that RLIMIT_NPROC was exceeded
- PF_USED_MATH If unset the fpu must be initialized before use
- PF_USED_ASYNC Used async_schedule*(), used by module init
- PF_NOFREEZE This thread should not be frozen
- PF_FROZEN Frozen for system suspend
- PF_FSTRANS Inside a filesystem transaction
- PF_KSWAPD I am kswapd
- PF_MEMALLOC_NOIO Allocating memory without IO involved
- PF_LESS_THROTTLE Throttle me less: I clean memory
- PF_KTHREAD I am a kernel thread
- PF_RANDOMIZE Randomize virtual address space
- PF_SWAPWRITE Allowed to write to swap
- PF_NO_SETAFFINITY Userland is not allowed to meddle with cpus_allowed
- PF_MCE_EARLY Early kill for mce process policy
- PF_MUTEX_TESTER Thread belongs to the rt mutex tester
- PF_FREEZER_SKIP Freezer should not count it as freezable
- PF_SUSPEND_TASK This thread called freeze_processes and should not be frozen
-
- """
- sflags = []
- for attr in dir(self):
- if attr[:3] != "PF_":
- continue
- value = getattr(self, attr)
- if value & self.fields["flags"]:
- sflags.append(attr)
-
- return sflags
+ """
+ return self.fields["flags"] & self.PF_THREAD_BOUND and \
+ True or False
+
+ def process_flags(self):
+ """
+ Returns a list with all the process flags known, details depend
+ on kernel version, declared in the file include/linux/sched.h in
+ the kernel sources.
+
+ As of v4.2-rc7 these include (from include/linux/sched.h comments):
+
+ PF_EXITING Getting shut down
+ PF_EXITPIDONE Pi exit done on shut down
+ PF_VCPU I'm a virtual CPU
+ PF_WQ_WORKER I'm a workqueue worker
+ PF_FORKNOEXEC Forked but didn't exec
+ PF_MCE_PROCESS Process policy on mce errors
+ PF_SUPERPRIV Used super-user privileges
+ PF_DUMPCORE Dumped core
+ PF_SIGNALED Killed by a signal
+ PF_MEMALLOC Allocating memory
+ PF_NPROC_EXCEEDED Set_user noticed that RLIMIT_NPROC was exceeded
+ PF_USED_MATH If unset the fpu must be initialized before use
+ PF_USED_ASYNC Used async_schedule*(), used by module init
+ PF_NOFREEZE This thread should not be frozen
+ PF_FROZEN Frozen for system suspend
+ PF_FSTRANS Inside a filesystem transaction
+ PF_KSWAPD I am kswapd
+ PF_MEMALLOC_NOIO Allocating memory without IO involved
+ PF_LESS_THROTTLE Throttle me less: I clean memory
+ PF_KTHREAD I am a kernel thread
+ PF_RANDOMIZE Randomize virtual address space
+ PF_SWAPWRITE Allowed to write to swap
+ PF_NO_SETAFFINITY Userland is not allowed to meddle with cpus_allowed
+ PF_MCE_EARLY Early kill for mce process policy
+ PF_MUTEX_TESTER Thread belongs to the rt mutex tester
+ PF_FREEZER_SKIP Freezer should not count it as freezable
+ PF_SUSPEND_TASK This thread called freeze_processes and should not be
frozen
+
+ """
+ sflags = []
+ for attr in dir(self):
+ if attr[:3] != "PF_":
+ continue
+ value = getattr(self, attr)
+ if value & self.fields["flags"]:
+ sflags.append(attr)
+
+ return sflags
def cannot_set_affinity(self, pid):
PF_NO_SETAFFINITY = 0x04000000
@@ -228,651 +228,651 @@ def cannot_set_thread_affinity(self, pid, tid):
return True
class pidstatus:
- """
- Provides a dictionary to access the fields in the per process /proc/PID/status
- files. This provides additional information about processes and threads to what
- can be obtained with the procfs.pidstat() class.
-
- One can obtain the available fields asking for the keys of the dictionary, e.g.:
-
- >>> import procfs
- >>> p = procfs.pidstatus(1)
- >>> print p.keys()
- ['VmExe', 'CapBnd', 'NSpgid', 'Tgid', 'NSpid',
'VmSize', 'VmPMD', 'ShdPnd', 'State', 'Gid',
'nonvoluntary_ctxt_switches', 'SigIgn', 'VmStk', 'VmData',
'SigCgt', 'CapEff', 'VmPTE', 'Groups', 'NStgid',
'Threads', 'PPid', 'VmHWM', 'NSsid', 'VmSwap',
'Name', 'SigBlk', 'Mems_allowed_list', 'VmPeak',
'Ngid', 'VmLck', 'SigQ', 'VmPin', 'Mems_allowed',
'CapPrm', 'Seccomp', 'VmLib', 'Cpus_allowed',
'Uid', 'SigPnd', 'Pid', 'Cpus_allowed_list',
'TracerPid', 'CapInh', 'voluntary_ctxt_switches', 'VmRSS',
'FDSize']
- >>> print p["Pid"]
- 1
- >>> print p["Threads"]
- 1
- >>> print p["VmExe"]
- 1248 kB
- >>> print p["Cpus_allowed"]
- f
- >>> print p["SigQ"]
- 0/30698
- >>> print p["VmPeak"]
- 320300 kB
- >>>
-
- Please refer to the 'procfs(5)' man page, by using:
-
- $ man 5 procfs
-
- To see information for each of the above fields, it is part of the
- 'man-pages' RPM package.
-
- In the man page there will be references to further documentation, like
+ """
+ Provides a dictionary to access the fields in the per process /proc/PID/status
+ files. This provides additional information about processes and threads to what
+ can be obtained with the procfs.pidstat() class.
+
+ One can obtain the available fields asking for the keys of the dictionary, e.g.:
+
+ >>> import procfs
+ >>> p = procfs.pidstatus(1)
+ >>> print p.keys()
+ ['VmExe', 'CapBnd', 'NSpgid', 'Tgid',
'NSpid', 'VmSize', 'VmPMD', 'ShdPnd', 'State',
'Gid', 'nonvoluntary_ctxt_switches', 'SigIgn', 'VmStk',
'VmData', 'SigCgt', 'CapEff', 'VmPTE', 'Groups',
'NStgid', 'Threads', 'PPid', 'VmHWM', 'NSsid',
'VmSwap', 'Name', 'SigBlk', 'Mems_allowed_list',
'VmPeak', 'Ngid', 'VmLck', 'SigQ', 'VmPin',
'Mems_allowed', 'CapPrm', 'Seccomp', 'VmLib',
'Cpus_allowed', 'Uid', 'SigPnd', 'Pid',
'Cpus_allowed_list', 'TracerPid', 'CapInh',
'voluntary_ctxt_switches', 'VmRSS', 'FDSize']
+ >>> print p["Pid"]
+ 1
+ >>> print p["Threads"]
+ 1
+ >>> print p["VmExe"]
+ 1248 kB
+ >>> print p["Cpus_allowed"]
+ f
+ >>> print p["SigQ"]
+ 0/30698
+ >>> print p["VmPeak"]
+ 320300 kB
+ >>>
+
+ Please refer to the 'procfs(5)' man page, by using:
+
+ $ man 5 procfs
+
+ To see information for each of the above fields, it is part of the
+ 'man-pages' RPM package.
+
+ In the man page there will be references to further documentation, like
referring to the "getrlimit(2)" man page when explaining the
"SigQ"
line/field.
- """
+ """
- def __init__(self, pid, basedir = "/proc"):
- self.pid = pid
- self.load(basedir)
+ def __init__(self, pid, basedir = "/proc"):
+ self.pid = pid
+ self.load(basedir)
- def __getitem__(self, fieldname):
- return self.fields[fieldname]
+ def __getitem__(self, fieldname):
+ return self.fields[fieldname]
- def keys(self):
- return list(self.fields.keys())
+ def keys(self):
+ return list(self.fields.keys())
- def values(self):
- return list(self.fields.values())
+ def values(self):
+ return list(self.fields.values())
- def has_key(self, fieldname):
- return fieldname in self.fields
+ def has_key(self, fieldname):
+ return fieldname in self.fields
- def items(self):
- return self.fields
+ def items(self):
+ return self.fields
- def __contains__(self, fieldname):
- return fieldname in self.fields
+ def __contains__(self, fieldname):
+ return fieldname in self.fields
- def load(self, basedir = "/proc"):
- f = open("%s/%d/status" % (basedir, self.pid))
- self.fields = {}
- for line in f.readlines():
- fields = line.split(":")
- if len(fields) != 2:
- continue
- name = fields[0]
- value = fields[1].strip()
- try:
- self.fields[name] = int(value)
- except:
- self.fields[name] = value
- f.close()
+ def load(self, basedir = "/proc"):
+ f = open("%s/%d/status" % (basedir, self.pid))
+ self.fields = {}
+ for line in f.readlines():
+ fields = line.split(":")
+ if len(fields) != 2:
+ continue
+ name = fields[0]
+ value = fields[1].strip()
+ try:
+ self.fields[name] = int(value)
+ except:
+ self.fields[name] = value
+ f.close()
class process:
- """
- Information about a process with a given pid, provides a dictionary with
- two entries, instances of different wrappers for /proc/ process related
- meta files: "stat" and "status", see the documentation for
procfs.pidstat
- and procfs.pidstatus for further info about those classes.
- """
-
- def __init__(self, pid, basedir = "/proc"):
- self.pid = pid
- self.basedir = basedir
-
- def __getitem__(self, attr):
- if not hasattr(self, attr):
- if attr in ("stat", "status"):
- if attr == "stat":
- sclass = pidstat
- else:
- sclass = pidstatus
-
- setattr(self, attr, sclass(self.pid, self.basedir))
- elif attr == "cmdline":
- self.load_cmdline()
- elif attr == "threads":
- self.load_threads()
- elif attr == "cgroups":
- self.load_cgroups()
- elif attr == "environ":
- self.load_environ()
-
- return getattr(self, attr)
-
- def has_key(self, attr):
- return hasattr(self, attr)
-
- def __contains__(self, attr):
- return hasattr(self, attr)
-
- def load_cmdline(self):
- f = open("/proc/%d/cmdline" % self.pid)
- self.cmdline = f.readline().strip().split('\0')[:-1]
- f.close()
-
- def load_threads(self):
- self.threads = pidstats("/proc/%d/task/" % self.pid)
- # remove thread leader
- del self.threads[self.pid]
-
- def load_cgroups(self):
- f = open("/proc/%d/cgroup" % self.pid)
- self.cgroups = ""
- for line in reversed(f.readlines()):
- if len(self.cgroups):
- self.cgroups = self.cgroups + "," + line[:-1]
- else:
- self.cgroups = line[:-1]
- f.close()
-
- def load_environ(self):
- """
- Loads the environment variables for this process. The entries then
- become available via the 'environ' member, or via the 'environ'
- dict key when accessing as p["environ"].
-
- E.g.:
-
-
- >>> all_processes = procfs.pidstats()
- >>> firefox_pid = all_processes.find_by_name("firefox")
- >>> firefox_process = all_processes[firefox_pid[0]]
- >>> print firefox_process["environ"]["PWD"]
- /home/acme
- >>> print len(firefox_process.environ.keys())
- 66
- >>> print firefox_process["environ"]["SHELL"]
- /bin/bash
- >>> print firefox_process["environ"]["USERNAME"]
- acme
- >>> print firefox_process["environ"]["HOME"]
- /home/acme
- >>> print firefox_process["environ"]["MAIL"]
- /var/spool/mail/acme
- >>>
- """
- self.environ = {}
- f = open("/proc/%d/environ" % self.pid)
- for x in f.readline().split('\0'):
- if len(x) > 0:
- y = x.split('=')
- self.environ[y[0]] = y[1]
- f.close()
+ """
+ Information about a process with a given pid, provides a dictionary with
+ two entries, instances of different wrappers for /proc/ process related
+ meta files: "stat" and "status", see the documentation for
procfs.pidstat
+ and procfs.pidstatus for further info about those classes.
+ """
+
+ def __init__(self, pid, basedir = "/proc"):
+ self.pid = pid
+ self.basedir = basedir
+
+ def __getitem__(self, attr):
+ if not hasattr(self, attr):
+ if attr in ("stat", "status"):
+ if attr == "stat":
+ sclass = pidstat
+ else:
+ sclass = pidstatus
+
+ setattr(self, attr, sclass(self.pid, self.basedir))
+ elif attr == "cmdline":
+ self.load_cmdline()
+ elif attr == "threads":
+ self.load_threads()
+ elif attr == "cgroups":
+ self.load_cgroups()
+ elif attr == "environ":
+ self.load_environ()
+
+ return getattr(self, attr)
+
+ def has_key(self, attr):
+ return hasattr(self, attr)
+
+ def __contains__(self, attr):
+ return hasattr(self, attr)
+
+ def load_cmdline(self):
+ f = open("/proc/%d/cmdline" % self.pid)
+ self.cmdline = f.readline().strip().split('\0')[:-1]
+ f.close()
+
+ def load_threads(self):
+ self.threads = pidstats("/proc/%d/task/" % self.pid)
+ # remove thread leader
+ del self.threads[self.pid]
+
+ def load_cgroups(self):
+ f = open("/proc/%d/cgroup" % self.pid)
+ self.cgroups = ""
+ for line in reversed(f.readlines()):
+ if len(self.cgroups):
+ self.cgroups = self.cgroups + "," + line[:-1]
+ else:
+ self.cgroups = line[:-1]
+ f.close()
+
+ def load_environ(self):
+ """
+ Loads the environment variables for this process. The entries then
+ become available via the 'environ' member, or via the 'environ'
+ dict key when accessing as p["environ"].
+
+ E.g.:
+
+
+ >>> all_processes = procfs.pidstats()
+ >>> firefox_pid = all_processes.find_by_name("firefox")
+ >>> firefox_process = all_processes[firefox_pid[0]]
+ >>> print firefox_process["environ"]["PWD"]
+ /home/acme
+ >>> print len(firefox_process.environ.keys())
+ 66
+ >>> print firefox_process["environ"]["SHELL"]
+ /bin/bash
+ >>> print firefox_process["environ"]["USERNAME"]
+ acme
+ >>> print firefox_process["environ"]["HOME"]
+ /home/acme
+ >>> print firefox_process["environ"]["MAIL"]
+ /var/spool/mail/acme
+ >>>
+ """
+ self.environ = {}
+ f = open("/proc/%d/environ" % self.pid)
+ for x in f.readline().split('\0'):
+ if len(x) > 0:
+ y = x.split('=')
+ self.environ[y[0]] = y[1]
+ f.close()
class pidstats:
- """
- Provides access to all the processes in the system, to get a picture of
- how many processes there are at any given moment.
-
- The entries can be accessed as a dictionary, keyed by pid. Also there are
- methods to find processes that match a given COMM or regular expression.
- """
- def __init__(self, basedir = "/proc"):
- self.basedir = basedir
- self.processes = {}
- self.reload()
-
- def __getitem__(self, key):
- return self.processes[key]
-
- def __delitem__(self, key):
- # not clear on why this can fail, but it can
- try:
- del self.processes[key]
- except:
- pass
-
- def keys(self):
- return list(self.processes.keys())
-
- def values(self):
- return list(self.processes.values())
-
- def has_key(self, key):
- return key in self.processes
-
- def items(self):
- return self.processes
-
- def __contains__(self, key):
- return key in self.processes
-
- def reload(self):
- """
- This operation will throw away the current dictionary contents, if any, and
- read all the pid files from /proc/, instantiating a 'process' instance for
- each of them.
-
- This is a high overhead operation, and should be avoided if the perf python
- binding can be used to detect when new threads appear and existing ones
- terminate.
-
- In RHEL it is found in the python-perf rpm package.
-
- More information about the perf facilities can be found in the
'perf_event_open'
- man page.
- """
- del self.processes
- self.processes = {}
- pids = os.listdir(self.basedir)
- for spid in pids:
- try:
- pid = int(spid)
- except:
- continue
-
- self.processes[pid] = process(pid, self.basedir)
-
- def reload_threads(self):
- to_remove = []
- for pid in self.processes.keys():
- try:
- self.processes[pid].load_threads()
- except OSError:
- # process vanished, remove it
- to_remove.append(pid)
- for pid in to_remove:
- del self.processes[pid]
-
- def find_by_name(self, name):
- name = name[:15]
- pids = []
- for pid in self.processes.keys():
- try:
- if name == self.processes[pid]["stat"]["comm"]:
- pids.append(pid)
- except IOError:
- # We're doing lazy loading of /proc files
- # So if we get this exception is because the
- # process vanished, remove it
- del self.processes[pid]
-
- return pids
-
- def find_by_regex(self, regex):
- pids = []
- for pid in self.processes.keys():
- try:
- if regex.match(self.processes[pid]["stat"]["comm"]):
- pids.append(pid)
- except IOError:
- # We're doing lazy loading of /proc files
- # So if we get this exception is because the
- # process vanished, remove it
- del self.processes[pid]
- return pids
-
- def find_by_cmdline_regex(self, regex):
- pids = []
- for pid in self.processes.keys():
- try:
- if regex.match(process_cmdline(self.processes[pid])):
- pids.append(pid)
- except IOError:
- # We're doing lazy loading of /proc files
- # So if we get this exception is because the
- # process vanished, remove it
- del self.processes[pid]
- return pids
-
- def get_per_cpu_rtprios(self, basename):
- cpu = 0
- priorities=""
- processed_pids = []
- while True:
- name = "%s/%d" % (basename, cpu)
- pids = self.find_by_name(name)
- if not pids or len([n for n in pids if n not in processed_pids]) == 0:
- break
- for pid in pids:
- try:
- priorities += "%s," %
self.processes[pid]["stat"]["rt_priority"]
- except IOError:
- # We're doing lazy loading of /proc files
- # So if we get this exception is because the
- # process vanished, remove it
- del self.processes[pid]
- processed_pids += pids
- cpu += 1
-
- priorities = priorities.strip(',')
- return priorities
-
- def get_rtprios(self, name):
- cpu = 0
- priorities=""
- processed_pids = []
- while True:
- pids = self.find_by_name(name)
- if not pids or len([n for n in pids if n not in processed_pids]) == 0:
- break
- for pid in pids:
- try:
- priorities += "%s," %
self.processes[pid]["stat"]["rt_priority"]
- except IOError:
- # We're doing lazy loading of /proc files
- # So if we get this exception is because the
- # process vanished, remove it
- del self.processes[pid]
- processed_pids += pids
- cpu += 1
-
- priorities = priorities.strip(',')
- return priorities
-
- def is_bound_to_cpu(self, pid):
- """
- Checks if a given pid can't have its SMP affinity mask changed.
- """
- return self.processes[pid]["stat"].is_bound_to_cpu()
+ """
+ Provides access to all the processes in the system, to get a picture of
+ how many processes there are at any given moment.
+
+ The entries can be accessed as a dictionary, keyed by pid. Also there are
+ methods to find processes that match a given COMM or regular expression.
+ """
+ def __init__(self, basedir = "/proc"):
+ self.basedir = basedir
+ self.processes = {}
+ self.reload()
+
+ def __getitem__(self, key):
+ return self.processes[key]
+
+ def __delitem__(self, key):
+ # not clear on why this can fail, but it can
+ try:
+ del self.processes[key]
+ except:
+ pass
+
+ def keys(self):
+ return list(self.processes.keys())
+
+ def values(self):
+ return list(self.processes.values())
+
+ def has_key(self, key):
+ return key in self.processes
+
+ def items(self):
+ return self.processes
+
+ def __contains__(self, key):
+ return key in self.processes
+
+ def reload(self):
+ """
+ This operation will throw away the current dictionary contents, if any, and
+ read all the pid files from /proc/, instantiating a 'process' instance
for
+ each of them.
+
+ This is a high overhead operation, and should be avoided if the perf python
+ binding can be used to detect when new threads appear and existing ones
+ terminate.
+
+ In RHEL it is found in the python-perf rpm package.
+
+ More information about the perf facilities can be found in the
'perf_event_open'
+ man page.
+ """
+ del self.processes
+ self.processes = {}
+ pids = os.listdir(self.basedir)
+ for spid in pids:
+ try:
+ pid = int(spid)
+ except:
+ continue
+
+ self.processes[pid] = process(pid, self.basedir)
+
+ def reload_threads(self):
+ to_remove = []
+ for pid in self.processes.keys():
+ try:
+ self.processes[pid].load_threads()
+ except OSError:
+ # process vanished, remove it
+ to_remove.append(pid)
+ for pid in to_remove:
+ del self.processes[pid]
+
+ def find_by_name(self, name):
+ name = name[:15]
+ pids = []
+ for pid in self.processes.keys():
+ try:
+ if name == self.processes[pid]["stat"]["comm"]:
+ pids.append(pid)
+ except IOError:
+ # We're doing lazy loading of /proc files
+ # So if we get this exception is because the
+ # process vanished, remove it
+ del self.processes[pid]
+
+ return pids
+
+ def find_by_regex(self, regex):
+ pids = []
+ for pid in self.processes.keys():
+ try:
+ if regex.match(self.processes[pid]["stat"]["comm"]):
+ pids.append(pid)
+ except IOError:
+ # We're doing lazy loading of /proc files
+ # So if we get this exception is because the
+ # process vanished, remove it
+ del self.processes[pid]
+ return pids
+
+ def find_by_cmdline_regex(self, regex):
+ pids = []
+ for pid in self.processes.keys():
+ try:
+ if regex.match(process_cmdline(self.processes[pid])):
+ pids.append(pid)
+ except IOError:
+ # We're doing lazy loading of /proc files
+ # So if we get this exception is because the
+ # process vanished, remove it
+ del self.processes[pid]
+ return pids
+
+ def get_per_cpu_rtprios(self, basename):
+ cpu = 0
+ priorities=""
+ processed_pids = []
+ while True:
+ name = "%s/%d" % (basename, cpu)
+ pids = self.find_by_name(name)
+ if not pids or len([n for n in pids if n not in processed_pids]) == 0:
+ break
+ for pid in pids:
+ try:
+ priorities += "%s," %
self.processes[pid]["stat"]["rt_priority"]
+ except IOError:
+ # We're doing lazy loading of /proc files
+ # So if we get this exception is because the
+ # process vanished, remove it
+ del self.processes[pid]
+ processed_pids += pids
+ cpu += 1
+
+ priorities = priorities.strip(',')
+ return priorities
+
+ def get_rtprios(self, name):
+ cpu = 0
+ priorities=""
+ processed_pids = []
+ while True:
+ pids = self.find_by_name(name)
+ if not pids or len([n for n in pids if n not in processed_pids]) == 0:
+ break
+ for pid in pids:
+ try:
+ priorities += "%s," %
self.processes[pid]["stat"]["rt_priority"]
+ except IOError:
+ # We're doing lazy loading of /proc files
+ # So if we get this exception is because the
+ # process vanished, remove it
+ del self.processes[pid]
+ processed_pids += pids
+ cpu += 1
+
+ priorities = priorities.strip(',')
+ return priorities
+
+ def is_bound_to_cpu(self, pid):
+ """
+ Checks if a given pid can't have its SMP affinity mask changed.
+ """
+ return self.processes[pid]["stat"].is_bound_to_cpu()
class interrupts:
- """
- Information about IRQs in the system. A dictionary keyed by IRQ number
- will have as its value another dictionary with "cpu", "type" and
"users"
- keys, with the SMP affinity mask, type of IRQ and the drivers associated
- with each interrupt.
-
- The information comes from the /proc/interrupts file, documented in
- 'man procfs(5)', for instance, the 'cpu' dict is an array with one
entry
- per CPU present in the sistem, each value being the number of interrupts
- that took place per CPU.
-
- E.g.:
-
- >>> import procfs
- >>> interrupts = procfs.interrupts()
- >>> thunderbolt_irq = interrupts.find_by_user("thunderbolt")
- >>> print thunderbolt_irq
- 34
- >>> thunderbolt = interrupts[thunderbolt_irq]
- >>> print thunderbolt
- {'affinity': [0, 1, 2, 3], 'type': 'PCI-MSI', 'cpu':
[3495, 0, 81, 0], 'users': ['thunderbolt']}
- >>>
- """
- def __init__(self):
- self.interrupts = {}
- self.reload()
-
- def __getitem__(self, key):
- return self.interrupts[str(key)]
-
- def keys(self):
- return list(self.interrupts.keys())
-
- def values(self):
- return list(self.interrupts.values())
-
- def has_key(self, key):
- return str(key) in self.interrupts
-
- def items(self):
- return self.interrupts
-
- def __contains__(self, key):
- return str(key) in self.interrupts
-
- def reload(self):
- del self.interrupts
- self.interrupts = {}
- f = open("/proc/interrupts")
-
- for line in f.readlines():
- line = line.strip()
- fields = line.split()
- if fields[0][:3] == "CPU":
- self.nr_cpus = len(fields)
- continue
- irq = fields[0].strip(":")
- self.interrupts[irq] = {}
- self.interrupts[irq] = self.parse_entry(fields[1:], line)
- try:
- nirq = int(irq)
- except:
- continue
- self.interrupts[irq]["affinity"] = self.parse_affinity(nirq)
-
- f.close()
-
- def parse_entry(self, fields, line):
- dict = {}
- dict["cpu"] = []
- dict["cpu"].append(int(fields[0]))
- nr_fields = len(fields)
- if nr_fields >= self.nr_cpus:
- dict["cpu"] += [int(i) for i in fields[1:self.nr_cpus]]
- if nr_fields > self.nr_cpus:
- dict["type"] = fields[self.nr_cpus]
- # look if there are users (interrupts 3 and 4 haven't)
- if nr_fields > self.nr_cpus + 1:
- dict["users"] = [a.strip() for a in fields[nr_fields -
1].split(',')]
- else:
- dict["users"] = []
- return dict
-
- def parse_affinity(self, irq):
- try:
- f = open("/proc/irq/%s/smp_affinity" % irq)
- line = f.readline()
- f.close()
- return bitmasklist(line, self.nr_cpus)
- except IOError:
- return [ 0, ]
-
- def find_by_user(self, user):
- """
- Looks up a interrupt number by the name of one of its users"
-
- E.g.:
-
- >>> import procfs
- >>> interrupts = procfs.interrupts()
- >>> thunderbolt_irq = interrupts.find_by_user("thunderbolt")
- >>> print thunderbolt_irq
- 34
- >>> thunderbolt = interrupts[thunderbolt_irq]
- >>> print thunderbolt
- {'affinity': [0, 1, 2, 3], 'type': 'PCI-MSI', 'cpu':
[3495, 0, 81, 0], 'users': ['thunderbolt']}
- >>>
- """
- for i in self.interrupts.keys():
- if "users" in self.interrupts[i] and \
- user in self.interrupts[i]["users"]:
- return i
- return None
-
- def find_by_user_regex(self, regex):
- """
- Looks up a interrupt number by a regex that matches names of its users"
-
- E.g.:
-
- >>> import procfs
- >>> import re
- >>> interrupts = procfs.interrupts()
- >>> usb_controllers =
interrupts.find_by_user_regex(re.compile(".*hcd"))
- >>> print usb_controllers
- ['22', '23', '31']
- >>> print [ interrupts[irq]["users"] for irq in usb_controllers ]
- [['ehci_hcd:usb4'], ['ehci_hcd:usb3'], ['xhci_hcd']]
- >>>
- """
- irqs = []
- for i in self.interrupts.keys():
- if "users" not in self.interrupts[i]:
- continue
- for user in self.interrupts[i]["users"]:
- if regex.match(user):
- irqs.append(i)
- break
- return irqs
+ """
+ Information about IRQs in the system. A dictionary keyed by IRQ number
+ will have as its value another dictionary with "cpu", "type" and
"users"
+ keys, with the SMP affinity mask, type of IRQ and the drivers associated
+ with each interrupt.
+
+ The information comes from the /proc/interrupts file, documented in
+ 'man procfs(5)', for instance, the 'cpu' dict is an array with one
entry
+ per CPU present in the sistem, each value being the number of interrupts
+ that took place per CPU.
+
+ E.g.:
+
+ >>> import procfs
+ >>> interrupts = procfs.interrupts()
+ >>> thunderbolt_irq = interrupts.find_by_user("thunderbolt")
+ >>> print thunderbolt_irq
+ 34
+ >>> thunderbolt = interrupts[thunderbolt_irq]
+ >>> print thunderbolt
+ {'affinity': [0, 1, 2, 3], 'type': 'PCI-MSI', 'cpu':
[3495, 0, 81, 0], 'users': ['thunderbolt']}
+ >>>
+ """
+ def __init__(self):
+ self.interrupts = {}
+ self.reload()
+
+ def __getitem__(self, key):
+ return self.interrupts[str(key)]
+
+ def keys(self):
+ return list(self.interrupts.keys())
+
+ def values(self):
+ return list(self.interrupts.values())
+
+ def has_key(self, key):
+ return str(key) in self.interrupts
+
+ def items(self):
+ return self.interrupts
+
+ def __contains__(self, key):
+ return str(key) in self.interrupts
+
+ def reload(self):
+ del self.interrupts
+ self.interrupts = {}
+ f = open("/proc/interrupts")
+
+ for line in f.readlines():
+ line = line.strip()
+ fields = line.split()
+ if fields[0][:3] == "CPU":
+ self.nr_cpus = len(fields)
+ continue
+ irq = fields[0].strip(":")
+ self.interrupts[irq] = {}
+ self.interrupts[irq] = self.parse_entry(fields[1:], line)
+ try:
+ nirq = int(irq)
+ except:
+ continue
+ self.interrupts[irq]["affinity"] = self.parse_affinity(nirq)
+
+ f.close()
+
+ def parse_entry(self, fields, line):
+ dict = {}
+ dict["cpu"] = []
+ dict["cpu"].append(int(fields[0]))
+ nr_fields = len(fields)
+ if nr_fields >= self.nr_cpus:
+ dict["cpu"] += [int(i) for i in fields[1:self.nr_cpus]]
+ if nr_fields > self.nr_cpus:
+ dict["type"] = fields[self.nr_cpus]
+ # look if there are users (interrupts 3 and 4 haven't)
+ if nr_fields > self.nr_cpus + 1:
+ dict["users"] = [a.strip() for a in fields[nr_fields -
1].split(',')]
+ else:
+ dict["users"] = []
+ return dict
+
+ def parse_affinity(self, irq):
+ try:
+ f = open("/proc/irq/%s/smp_affinity" % irq)
+ line = f.readline()
+ f.close()
+ return bitmasklist(line, self.nr_cpus)
+ except IOError:
+ return [ 0, ]
+
+ def find_by_user(self, user):
+ """
+ Looks up a interrupt number by the name of one of its users"
+
+ E.g.:
+
+ >>> import procfs
+ >>> interrupts = procfs.interrupts()
+ >>> thunderbolt_irq = interrupts.find_by_user("thunderbolt")
+ >>> print thunderbolt_irq
+ 34
+ >>> thunderbolt = interrupts[thunderbolt_irq]
+ >>> print thunderbolt
+ {'affinity': [0, 1, 2, 3], 'type': 'PCI-MSI',
'cpu': [3495, 0, 81, 0], 'users': ['thunderbolt']}
+ >>>
+ """
+ for i in self.interrupts.keys():
+ if "users" in self.interrupts[i] and \
+ user in self.interrupts[i]["users"]:
+ return i
+ return None
+
+ def find_by_user_regex(self, regex):
+ """
+ Looks up a interrupt number by a regex that matches names of its users"
+
+ E.g.:
+
+ >>> import procfs
+ >>> import re
+ >>> interrupts = procfs.interrupts()
+ >>> usb_controllers =
interrupts.find_by_user_regex(re.compile(".*hcd"))
+ >>> print usb_controllers
+ ['22', '23', '31']
+ >>> print [ interrupts[irq]["users"] for irq in
usb_controllers ]
+ [['ehci_hcd:usb4'], ['ehci_hcd:usb3'], ['xhci_hcd']]
+ >>>
+ """
+ irqs = []
+ for i in self.interrupts.keys():
+ if "users" not in self.interrupts[i]:
+ continue
+ for user in self.interrupts[i]["users"]:
+ if regex.match(user):
+ irqs.append(i)
+ break
+ return irqs
class cmdline:
- """
- Parses the kernel command line (/proc/cmdline), turning it into a dictionary."
+ """
+ Parses the kernel command line (/proc/cmdline), turning it into a dictionary."
- Useful to figure out if some kernel boolean knob has been turned on, as well
- as to find the value associated to other kernel knobs.
+ Useful to figure out if some kernel boolean knob has been turned on, as well
+ as to find the value associated to other kernel knobs.
- It can also be used to find out about parameters passed to the init process,
- such as 'BOOT_IMAGE', etc.
+ It can also be used to find out about parameters passed to the init process,
+ such as 'BOOT_IMAGE', etc.
- E.g.:
- >>> import procfs
- >>> kcmd = procfs.cmdline()
- >>> print kcmd.keys()
- ['LANG', 'BOOT_IMAGE', 'quiet', 'rhgb',
'rd.lvm.lv', 'ro', 'root']
- >>> print kcmd["BOOT_IMAGE"]
- /vmlinuz-4.3.0-rc1+
- >>>
- """
+ E.g.:
+ >>> import procfs
+ >>> kcmd = procfs.cmdline()
+ >>> print kcmd.keys()
+ ['LANG', 'BOOT_IMAGE', 'quiet', 'rhgb',
'rd.lvm.lv', 'ro', 'root']
+ >>> print kcmd["BOOT_IMAGE"]
+ /vmlinuz-4.3.0-rc1+
+ >>>
+ """
- def __init__(self):
- self.options = {}
- self.parse()
+ def __init__(self):
+ self.options = {}
+ self.parse()
- def parse(self):
- f = open("/proc/cmdline")
- for option in f.readline().strip().split():
- fields = option.split("=")
- if len(fields) == 1:
- self.options[fields[0]] = True
- else:
- self.options[fields[0]] = fields[1]
+ def parse(self):
+ f = open("/proc/cmdline")
+ for option in f.readline().strip().split():
+ fields = option.split("=")
+ if len(fields) == 1:
+ self.options[fields[0]] = True
+ else:
+ self.options[fields[0]] = fields[1]
- f.close()
+ f.close()
- def __getitem__(self, key):
- return self.options[key]
+ def __getitem__(self, key):
+ return self.options[key]
- def keys(self):
- return list(self.options.keys())
+ def keys(self):
+ return list(self.options.keys())
- def values(self):
- return list(self.options.values())
+ def values(self):
+ return list(self.options.values())
- def items(self):
- return self.options
+ def items(self):
+ return self.options
class cpuinfo:
- """
- Dictionary with information about CPUs in the system.
+ """
+ Dictionary with information about CPUs in the system.
- Please refer to 'man procfs(5)' for further information about the
+ Please refer to 'man procfs(5)' for further information about the
'/proc/cpuinfo' file, that is the source of the information provided by
this
class. The 'man lscpu(1)' also has information about a program that uses
- the '/proc/cpuinfo' file.
+ the '/proc/cpuinfo' file.
- Using this class one can obtain the number of CPUs in a system:
+ Using this class one can obtain the number of CPUs in a system:
- >>> cpus = procfs.cpuinfo()
+ >>> cpus = procfs.cpuinfo()
>> print cpus.nr_cpus
4
- It is also possible to figure out aspects of the CPU topology, such as
- how many CPU physical sockets exists, i.e. groups of CPUs sharing components
- such as CPU memory caches:
+ It is also possible to figure out aspects of the CPU topology, such as
+ how many CPU physical sockets exists, i.e. groups of CPUs sharing components
+ such as CPU memory caches:
- >>> print len(cpus.sockets)
- 1
+ >>> print len(cpus.sockets)
+ 1
- Additionally dictionary with information common to all CPUs in the system is
- available:
+ Additionally dictionary with information common to all CPUs in the system is
+ available:
- >>> print cpus["model name"]
+ >>> print cpus["model name"]
Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
>> print cpus["cache size"]
4096 KB
>>
- """
- def __init__(self, filename="/proc/cpuinfo"):
- self.tags = {}
- self.nr_cpus = 0
- self.sockets = []
- self.parse(filename)
-
- def __getitem__(self, key):
- return self.tags[key.lower()]
-
- def keys(self):
- return list(self.tags.keys())
-
- def values(self):
- return list(self.tags.values())
-
- def items(self):
- return self.tags
-
- def parse(self, filename):
- f = open(filename)
- for line in f.readlines():
- line = line.strip()
- if len(line) == 0:
- continue
- fields = line.split(":")
- tagname = fields[0].strip().lower()
- if tagname == "processor":
- self.nr_cpus += 1
- continue
- elif tagname == "core id":
- continue
- self.tags[tagname] = fields[1].strip()
- if tagname == "physical id":
- socket_id = self.tags[tagname]
- if socket_id not in self.sockets:
- self.sockets.append(socket_id)
-
- f.close()
- self.nr_sockets = self.sockets and len(self.sockets) or \
- (self.nr_cpus / ("siblings" in self.tags and
int(self.tags["siblings"]) or 1))
- self.nr_cores = ("cpu cores" in self.tags and int(self.tags["cpu
cores"]) or 1) * self.nr_sockets
+ """
+ def __init__(self, filename="/proc/cpuinfo"):
+ self.tags = {}
+ self.nr_cpus = 0
+ self.sockets = []
+ self.parse(filename)
+
+ def __getitem__(self, key):
+ return self.tags[key.lower()]
+
+ def keys(self):
+ return list(self.tags.keys())
+
+ def values(self):
+ return list(self.tags.values())
+
+ def items(self):
+ return self.tags
+
+ def parse(self, filename):
+ f = open(filename)
+ for line in f.readlines():
+ line = line.strip()
+ if len(line) == 0:
+ continue
+ fields = line.split(":")
+ tagname = fields[0].strip().lower()
+ if tagname == "processor":
+ self.nr_cpus += 1
+ continue
+ elif tagname == "core id":
+ continue
+ self.tags[tagname] = fields[1].strip()
+ if tagname == "physical id":
+ socket_id = self.tags[tagname]
+ if socket_id not in self.sockets:
+ self.sockets.append(socket_id)
+
+ f.close()
+ self.nr_sockets = self.sockets and len(self.sockets) or \
+ (self.nr_cpus / ("siblings" in self.tags and
int(self.tags["siblings"]) or 1))
+ self.nr_cores = ("cpu cores" in self.tags and int(self.tags["cpu
cores"]) or 1) * self.nr_sockets
class smaps_lib:
- """
- Representation of an mmap in place for a process. Can be used to figure
- out which processes have an library mapped, etc.
-
- The 'perm' member can be used to figure out executable mmaps, i.e. libraries.
-
- The 'vm_start' and 'vm_end' in turn can be used when trying to resolve
- processor instruction pointer addresses to a symbol name in a library.
- """
- def __init__(self, lines):
- fields = lines[0].split()
- self.vm_start, self.vm_end = [int(a, 16) for a in fields[0].split("-")]
- self.perms = fields[1]
- self.offset = int(fields[2], 16)
- self.major, self.minor = fields[3].split(":")
- self.inode = int(fields[4])
- if len(fields) > 5:
- self.name = fields[5]
- else:
- self.name = None
- self.tags = {}
- for line in lines[1:]:
- fields = line.split()
- tag = fields[0][:-1].lower()
- try:
- self.tags[tag] = int(fields[1])
- except:
- # VmFlags are strings
- self.tags[tag] = fields
-
- def __getitem__(self, key):
- return self.tags[key.lower()]
-
- def keys(self):
- return list(self.tags.keys())
-
- def values(self):
- return list(self.tags.values())
-
- def items(self):
- return self.tags
+ """
+ Representation of an mmap in place for a process. Can be used to figure
+ out which processes have an library mapped, etc.
+
+ The 'perm' member can be used to figure out executable mmaps, i.e.
libraries.
+
+ The 'vm_start' and 'vm_end' in turn can be used when trying to
resolve
+ processor instruction pointer addresses to a symbol name in a library.
+ """
+ def __init__(self, lines):
+ fields = lines[0].split()
+ self.vm_start, self.vm_end = [int(a, 16) for a in
fields[0].split("-")]
+ self.perms = fields[1]
+ self.offset = int(fields[2], 16)
+ self.major, self.minor = fields[3].split(":")
+ self.inode = int(fields[4])
+ if len(fields) > 5:
+ self.name = fields[5]
+ else:
+ self.name = None
+ self.tags = {}
+ for line in lines[1:]:
+ fields = line.split()
+ tag = fields[0][:-1].lower()
+ try:
+ self.tags[tag] = int(fields[1])
+ except:
+ # VmFlags are strings
+ self.tags[tag] = fields
+
+ def __getitem__(self, key):
+ return self.tags[key.lower()]
+
+ def keys(self):
+ return list(self.tags.keys())
+
+ def values(self):
+ return list(self.tags.values())
+
+ def items(self):
+ return self.tags
class smaps:
- """
- List of libraries mapped by a process. Parses the lines in
- the /proc/PID/smaps file, that is further documented in the
- procfs(5) man page.
+ """
+ List of libraries mapped by a process. Parses the lines in
+ the /proc/PID/smaps file, that is further documented in the
+ procfs(5) man page.
- Example: Listing the executable maps for the 'sshd' process:
+ Example: Listing the executable maps for the 'sshd' process:
>> import procfs
>> processes = procfs.pidstats()
@@ -889,185 +889,185 @@ class smaps:
/usr/lib64/libkrb5support.so.0.1: r-xp
/usr/lib64/libfreebl3.so: r-xp
/usr/lib64/libpthread-2.20.so: r-xp
- ...
- """
- def __init__(self, pid):
- self.pid = pid
- self.entries = []
- self.reload()
-
- def parse_entry(self, f, line):
- lines = []
- if not line:
- line = f.readline().strip()
- if not line:
- return
- lines.append(line)
- while True:
- line = f.readline()
- if not line:
- break
- line = line.strip()
- if line.split()[0][-1] == ':':
- lines.append(line)
- else:
- break
- self.entries.append(smaps_lib(lines))
- return line
-
- def __len__(self):
- return len(self.entries)
-
- def __getitem__(self, index):
- return self.entries[index]
-
- def reload(self):
- f = open("/proc/%d/smaps" % self.pid)
- line = None
- while True:
- line = self.parse_entry(f, line)
- if not line:
- break
- f.close()
- self.nr_entries = len(self.entries)
-
- def find_by_name_fragment(self, fragment):
- result = []
- for i in range(self.nr_entries):
- if self.entries[i].name and \
- self.entries[i].name.find(fragment) >= 0:
- result.append(self.entries[i])
-
- return result
+ ...
+ """
+ def __init__(self, pid):
+ self.pid = pid
+ self.entries = []
+ self.reload()
+
+ def parse_entry(self, f, line):
+ lines = []
+ if not line:
+ line = f.readline().strip()
+ if not line:
+ return
+ lines.append(line)
+ while True:
+ line = f.readline()
+ if not line:
+ break
+ line = line.strip()
+ if line.split()[0][-1] == ':':
+ lines.append(line)
+ else:
+ break
+ self.entries.append(smaps_lib(lines))
+ return line
+
+ def __len__(self):
+ return len(self.entries)
+
+ def __getitem__(self, index):
+ return self.entries[index]
+
+ def reload(self):
+ f = open("/proc/%d/smaps" % self.pid)
+ line = None
+ while True:
+ line = self.parse_entry(f, line)
+ if not line:
+ break
+ f.close()
+ self.nr_entries = len(self.entries)
+
+ def find_by_name_fragment(self, fragment):
+ result = []
+ for i in range(self.nr_entries):
+ if self.entries[i].name and \
+ self.entries[i].name.find(fragment) >= 0:
+ result.append(self.entries[i])
+
+ return result
class cpustat:
- """
- CPU statistics, obtained from a line in the '/proc/stat' file, Please
- refer to 'man procfs(5)' for further information about the '/proc/stat'
- file, that is the source of the information provided by this class.
- """
-
- def __init__(self, fields):
- self.name = fields[0]
- (self.user,
- self.nice,
- self.system,
- self.idle,
- self.iowait,
- self.irq,
- self.softirq) = [int(i) for i in fields[1:8]]
- if len(fields) > 7:
- self.steal = int(fields[7])
- if len(fields) > 8:
- self.guest = int(fields[8])
-
- def __repr__(self):
- s = "< user: %s, nice: %s, system: %s, idle: %s, iowait: %s, irq: %s, softirq:
%s" % \
- (self.user, self.nice, self.system, self.idle, self.iowait, self.irq, self.softirq)
- if hasattr(self, 'steal'):
- s += ", steal: %d" % self.steal
- if hasattr(self, 'guest'):
- s += ", guest: %d" % self.guest
- return s + ">"
+ """
+ CPU statistics, obtained from a line in the '/proc/stat' file, Please
+ refer to 'man procfs(5)' for further information about the
'/proc/stat'
+ file, that is the source of the information provided by this class.
+ """
+
+ def __init__(self, fields):
+ self.name = fields[0]
+ (self.user,
+ self.nice,
+ self.system,
+ self.idle,
+ self.iowait,
+ self.irq,
+ self.softirq) = [int(i) for i in fields[1:8]]
+ if len(fields) > 7:
+ self.steal = int(fields[7])
+ if len(fields) > 8:
+ self.guest = int(fields[8])
+
+ def __repr__(self):
+ s = "< user: %s, nice: %s, system: %s, idle: %s, iowait: %s, irq: %s,
softirq: %s" % \
+ (self.user, self.nice, self.system, self.idle, self.iowait, self.irq,
self.softirq)
+ if hasattr(self, 'steal'):
+ s += ", steal: %d" % self.steal
+ if hasattr(self, 'guest'):
+ s += ", guest: %d" % self.guest
+ return s + ">"
class cpusstats:
- """
- Dictionary with information about CPUs in the system. First entry in the
- dictionary gives an aggregate view of all CPUs, each other entry is about
- separate CPUs. Please refer to 'man procfs(5)' for further information
- about the '/proc/stat' file, that is the source of the information provided
- by this class.
- """
- def __init__(self, filename = "/proc/stat"):
- self.entries = {}
- self.time = None
- self.hertz = os.sysconf(2)
- self.filename = filename
- self.reload()
-
- def __iter__(self):
- return iter(self.entries)
-
- def __getitem__(self, key):
- return self.entries[key]
-
- def __len__(self):
- return len(list(self.entries.keys()))
-
- def keys(self):
- return list(self.entries.keys())
-
- def values(self):
- return list(self.entries.values())
-
- def items(self):
- return self.entries
-
- def reload(self):
- last_entries = self.entries
- self.entries = {}
- f = open(self.filename)
- for line in f.readlines():
- fields = line.strip().split()
- if fields[0][:3].lower() != "cpu":
- continue
- c = cpustat(fields)
- if c.name == "cpu":
- idx = 0
- else:
- idx = int(c.name[3:]) + 1
- self.entries[idx] = c
- f.close()
- last_time = self.time
- self.time = time.time()
- if last_entries:
- delta_sec = self.time - last_time
- interval_hz = delta_sec * self.hertz
- for cpu in self.entries.keys():
- if cpu not in last_entries:
- curr.usage = 0
- continue
- curr = self.entries[cpu]
- prev = last_entries[cpu]
- delta = (curr.user - prev.user) + \
- (curr.nice - prev.nice) + \
- (curr.system - prev.system)
- curr.usage = (delta / interval_hz) * 100
- if curr.usage > 100:
- curr.usage = 100
+ """
+ Dictionary with information about CPUs in the system. First entry in the
+ dictionary gives an aggregate view of all CPUs, each other entry is about
+ separate CPUs. Please refer to 'man procfs(5)' for further information
+ about the '/proc/stat' file, that is the source of the information provided
+ by this class.
+ """
+ def __init__(self, filename = "/proc/stat"):
+ self.entries = {}
+ self.time = None
+ self.hertz = os.sysconf(2)
+ self.filename = filename
+ self.reload()
+
+ def __iter__(self):
+ return iter(self.entries)
+
+ def __getitem__(self, key):
+ return self.entries[key]
+
+ def __len__(self):
+ return len(list(self.entries.keys()))
+
+ def keys(self):
+ return list(self.entries.keys())
+
+ def values(self):
+ return list(self.entries.values())
+
+ def items(self):
+ return self.entries
+
+ def reload(self):
+ last_entries = self.entries
+ self.entries = {}
+ f = open(self.filename)
+ for line in f.readlines():
+ fields = line.strip().split()
+ if fields[0][:3].lower() != "cpu":
+ continue
+ c = cpustat(fields)
+ if c.name == "cpu":
+ idx = 0
+ else:
+ idx = int(c.name[3:]) + 1
+ self.entries[idx] = c
+ f.close()
+ last_time = self.time
+ self.time = time.time()
+ if last_entries:
+ delta_sec = self.time - last_time
+ interval_hz = delta_sec * self.hertz
+ for cpu in self.entries.keys():
+ if cpu not in last_entries:
+ curr.usage = 0
+ continue
+ curr = self.entries[cpu]
+ prev = last_entries[cpu]
+ delta = (curr.user - prev.user) + \
+ (curr.nice - prev.nice) + \
+ (curr.system - prev.system)
+ curr.usage = (delta / interval_hz) * 100
+ if curr.usage > 100:
+ curr.usage = 100
if __name__ == '__main__':
- import sys
-
- ints = interrupts()
-
- for i in ints.interrupts.keys():
- print("%s: %s" % (i, ints.interrupts[i]))
-
- options = cmdline()
- for o in options.options.keys():
- print("%s: %s" % (o, options.options[o]))
-
- cpu = cpuinfo()
- print("\ncpuinfo data: %d processors" % cpu.nr_cpus)
- for tag in cpu.keys():
- print("%s=%s" % (tag, cpu[tag]))
-
- print("smaps:\n" + ("-" * 40))
- s = smaps(int(sys.argv[1]))
- for i in range(s.nr_entries):
- print("%#x %s" % (s.entries[i].vm_start, s.entries[i].name))
- print("-" * 40)
- for a in s.find_by_name_fragment(sys.argv[2]):
- print(a["Size"])
-
- ps = pidstats()
- print(ps[1])
-
- cs = cpusstats()
- while True:
- time.sleep(1)
- cs.reload()
- for cpu in cs:
- print("%s: %d" % (cpu.name, cpu.usage))
- print("-" * 10)
+ import sys
+
+ ints = interrupts()
+
+ for i in ints.interrupts.keys():
+ print("%s: %s" % (i, ints.interrupts[i]))
+
+ options = cmdline()
+ for o in options.options.keys():
+ print("%s: %s" % (o, options.options[o]))
+
+ cpu = cpuinfo()
+ print("\ncpuinfo data: %d processors" % cpu.nr_cpus)
+ for tag in cpu.keys():
+ print("%s=%s" % (tag, cpu[tag]))
+
+ print("smaps:\n" + ("-" * 40))
+ s = smaps(int(sys.argv[1]))
+ for i in range(s.nr_entries):
+ print("%#x %s" % (s.entries[i].vm_start, s.entries[i].name))
+ print("-" * 40)
+ for a in s.find_by_name_fragment(sys.argv[2]):
+ print(a["Size"])
+
+ ps = pidstats()
+ print(ps[1])
+
+ cs = cpusstats()
+ while True:
+ time.sleep(1)
+ cs.reload()
+ for cpu in cs:
+ print("%s: %d" % (cpu.name, cpu.usage))
+ print("-" * 10)
diff --git a/procfs/procfs.pyc b/procfs/procfs.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e3aca1595ac1a158bbc08e14a629e0caddcf6867
GIT binary patch
literal 40780
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
literal 0
HcmV?d00001
--
2.21.3