From c263b96cbbfd5634bff997b36a13c5bc1ab3f8b5 Mon Sep 17 00:00:00 2001 From: xzl Date: Fri, 27 Dec 2024 10:14:18 +0800 Subject: [PATCH] feat: update python-linux-procfs to 0.7.3 Linux /proc abstraction classes in Python Issue: https://github.com/deepin-community/sig-deepin-sysdev-team/issues/558 Log: update repo --- .../.pydistutils.cfg | 10 + .../build/procfs/__init__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 498 bytes .../procfs/__pycache__/procfs.cpython-312.pyc | Bin 0 -> 51544 bytes .../__pycache__/utilist.cpython-312.pyc | Bin 0 -> 1299 bytes .../build/procfs/procfs.py | 1110 ++++++++++++++++ .../build/procfs/utilist.py | 40 + COPYING | 5 + Makefile | 14 + README.md | 1 - bitmasklist_test.py | 75 ++ build/scripts-3.12/pflags | 86 ++ .../dh_installchangelogs.dch.trimmed | 77 ++ .../installed-by-dh_installdocs | 0 .../installed-by-dh_installman | 1 + debian/.gitlab-ci.yml | 11 + debian/changelog | 97 +- debian/clean | 2 + debian/compat | 1 - debian/control | 44 +- debian/copyright | 23 +- debian/debhelper-build-stamp | 1 + debian/files | 2 + debian/patches/remove-six.patch | 38 + debian/patches/series | 1 + debian/pflags.8 | 53 + debian/pflags.8.asciidoc | 35 + debian/python3-linux-procfs.debhelper.log | 1 + debian/python3-linux-procfs.manpages | 1 + .../python3-linux-procfs.postinst.debhelper | 10 + debian/python3-linux-procfs.prerm.debhelper | 10 + debian/python3-linux-procfs.substvars | 3 + debian/python3-linux-procfs/DEBIAN/control | 17 + debian/python3-linux-procfs/DEBIAN/md5sums | 10 + debian/python3-linux-procfs/DEBIAN/postinst | 12 + debian/python3-linux-procfs/DEBIAN/prerm | 12 + debian/python3-linux-procfs/usr/bin/pflags | 86 ++ .../python3/dist-packages/procfs/__init__.py | 17 + .../python3/dist-packages/procfs/procfs.py | 1110 ++++++++++++++++ .../python3/dist-packages/procfs/utilist.py | 40 + .../PKG-INFO | 11 + .../dependency_links.txt | 1 + .../top_level.txt | 1 + .../python3-linux-procfs/changelog.Debian.gz | Bin 0 -> 1039 bytes .../share/doc/python3-linux-procfs/copyright | 33 + .../usr/share/man/man8/pflags.8.gz | Bin 0 -> 686 bytes debian/rules | 10 +- debian/watch | 2 + pflags | 87 ++ procfs/__init__.py | 17 + procfs/procfs.py | 1111 +++++++++++++++++ procfs/utilist.py | 41 + python_linux_procfs.egg-info/PKG-INFO | 11 + python_linux_procfs.egg-info/SOURCES.txt | 10 + .../dependency_links.txt | 1 + python_linux_procfs.egg-info/top_level.txt | 1 + setup.py | 33 + 57 files changed, 4418 insertions(+), 25 deletions(-) create mode 100644 .pybuild/cpython3_3.12_linux-procfs/.pydistutils.cfg create mode 100644 .pybuild/cpython3_3.12_linux-procfs/build/procfs/__init__.py create mode 100644 .pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/__init__.cpython-312.pyc create mode 100644 .pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/procfs.cpython-312.pyc create mode 100644 .pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/utilist.cpython-312.pyc create mode 100644 .pybuild/cpython3_3.12_linux-procfs/build/procfs/procfs.py create mode 100644 .pybuild/cpython3_3.12_linux-procfs/build/procfs/utilist.py create mode 100644 COPYING create mode 100644 Makefile delete mode 100644 README.md create mode 100755 bitmasklist_test.py create mode 100755 build/scripts-3.12/pflags create mode 100644 debian/.debhelper/generated/python3-linux-procfs/dh_installchangelogs.dch.trimmed create mode 100644 debian/.debhelper/generated/python3-linux-procfs/installed-by-dh_installdocs create mode 100644 debian/.debhelper/generated/python3-linux-procfs/installed-by-dh_installman create mode 100644 debian/.gitlab-ci.yml create mode 100644 debian/clean delete mode 100644 debian/compat create mode 100644 debian/debhelper-build-stamp create mode 100644 debian/files create mode 100644 debian/patches/remove-six.patch create mode 100644 debian/patches/series create mode 100644 debian/pflags.8 create mode 100644 debian/pflags.8.asciidoc create mode 100644 debian/python3-linux-procfs.debhelper.log create mode 100644 debian/python3-linux-procfs.manpages create mode 100644 debian/python3-linux-procfs.postinst.debhelper create mode 100644 debian/python3-linux-procfs.prerm.debhelper create mode 100644 debian/python3-linux-procfs.substvars create mode 100644 debian/python3-linux-procfs/DEBIAN/control create mode 100644 debian/python3-linux-procfs/DEBIAN/md5sums create mode 100755 debian/python3-linux-procfs/DEBIAN/postinst create mode 100755 debian/python3-linux-procfs/DEBIAN/prerm create mode 100755 debian/python3-linux-procfs/usr/bin/pflags create mode 100644 debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/__init__.py create mode 100644 debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/procfs.py create mode 100644 debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/utilist.py create mode 100644 debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/PKG-INFO create mode 100644 debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/dependency_links.txt create mode 100644 debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/top_level.txt create mode 100644 debian/python3-linux-procfs/usr/share/doc/python3-linux-procfs/changelog.Debian.gz create mode 100644 debian/python3-linux-procfs/usr/share/doc/python3-linux-procfs/copyright create mode 100644 debian/python3-linux-procfs/usr/share/man/man8/pflags.8.gz create mode 100644 debian/watch create mode 100755 pflags create mode 100644 procfs/__init__.py create mode 100755 procfs/procfs.py create mode 100755 procfs/utilist.py create mode 100644 python_linux_procfs.egg-info/PKG-INFO create mode 100644 python_linux_procfs.egg-info/SOURCES.txt create mode 100644 python_linux_procfs.egg-info/dependency_links.txt create mode 100644 python_linux_procfs.egg-info/top_level.txt create mode 100755 setup.py diff --git a/.pybuild/cpython3_3.12_linux-procfs/.pydistutils.cfg b/.pybuild/cpython3_3.12_linux-procfs/.pydistutils.cfg new file mode 100644 index 0000000..a223dbe --- /dev/null +++ b/.pybuild/cpython3_3.12_linux-procfs/.pydistutils.cfg @@ -0,0 +1,10 @@ +[clean] +all=1 +[build] +build_lib=/home/xzl/work/python-linux-procfs/python-linux-procfs/.pybuild/cpython3_3.12_linux-procfs/build +[install] +force=1 +install_layout=deb +install_scripts=$base/bin +install_lib=/usr/lib/python3.12/dist-packages +prefix=/usr diff --git a/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__init__.py b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__init__.py new file mode 100644 index 0000000..6deedf4 --- /dev/null +++ b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__init__.py @@ -0,0 +1,17 @@ +#! /usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2008, 2009 Red Hat, Inc. +# +""" +Copyright (c) 2008, 2009 Red Hat Inc. + +Abstractions to extract information from the Linux kernel /proc files. +""" +__author__ = "Arnaldo Carvalho de Melo " +__license__ = "GPLv2 License" + +from .procfs import * +from .utilist import * diff --git a/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/__init__.cpython-312.pyc b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2abaa4435843f73723b59b5c87345b1c6fe0999 GIT binary patch literal 498 zcmZuuKTjJm6t|NQ(9uL)+Nnry=s-|&mxiTO2!R+V5+KBw#d2(?XT@iqd_G9hg^$3- zXJF_j=?4Iv#8j4UAT}m!LZuFU!_)8g^nUN}uU0EU-mZTfiVKX;??C=q?G-$J^xzV4 zG(a2=Fs}`2yv`e{{VT2dk_D{a)|Or{FgLP6UT<)WN=H zaTM(g3uh^F(&z%50Wo=@Aoa-DjD|RjtjWNo0uH1uComRP3k6AT4TF(XqKKo}+K$y! zaRa;5o>G+>-~tYXGO$TmCbq5MDRnV3+19MJcYJW#_tluth4|waMjc$?wWH2LwMYmz zjF2iKgqAKf*5hSD)I8afX6QRAs>RYtB@1_j9>ay6iX=6eNG3CtoEbY#@~Kzrt_m}E z=bHb<;(R(RrQ!*jE7r++{JHNN{-c{GycQuuYUv2^!l$Lp%SrRRnyCN) literal 0 HcmV?d00001 diff --git a/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/procfs.cpython-312.pyc b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/procfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10180a7c6ae07a9933401214ab69d50ade696074 GIT binary patch literal 51544 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 F{~yNQ3>g3b literal 0 HcmV?d00001 diff --git a/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/utilist.cpython-312.pyc b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/__pycache__/utilist.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..637998747c8b86e6bb3d5f4eec88184add706638 GIT binary patch literal 1299 zcma)*&uiOe7{{MiKWxpC)!0cK8c0Z-wbX2h=>jb+qY&azmW{xc9?HfDPNX?oL!l?%OaeXRwC9zru$CV7fF6DIJn!@9(ffY% zPdpw2wBonr!jC+_TlNqd8Hvj=6o;U}FYp6s1U=d3GW<3dF2uWt7uLh&-{=mZ01Zwf z4IVGS4*P&Zjz&5MKV<`%%q#TX;VYD{Me4Zw`JJnytV7|D10H1BFpbv5o|IS*cHw0RLXjG zuU^W&sLH5pn2RODtnDpUX=VFo>!Y!y>i$;EC~4X4Va4+1^3vTeHs7loE*Leb z*+$8*Y^A#c=B~6Kddisl zU1#C9rzgs6E9Xga>secB@t&-@>4OJea=f+bDXBAM=0ut4rO>#9`n)p4eUmJiSA42o4mlDPn7f zb~L(woCu8ziD+=hBXWc+GEM`tqoHy*?V6pCPfgTd8u_$8xn3oPIb?SU$&CD`LnO>; zkvh&v6AoWwyZnJ6qoc1NfJ8sRD+A1K?0KKlpzjBqNT zL-a%onXCFys#i<-ZJqIE`n+MrBBTM9@(5K1 z2*sZbQz5*>u-N_#X`Q1gYC01-CaeQ=$d8)*wb(8ml+M)jiJI;%|H<{#&zg_Dv|~dU*Lw1-rzAW%bu6R?ajYCDpiJQdy@B(rXSgBdY{I`Fhy}A> jNne<CJo%qVt$yhhVXS0Z$0INR5%y<)<>43JU%ORB$R; literal 0 HcmV?d00001 diff --git a/.pybuild/cpython3_3.12_linux-procfs/build/procfs/procfs.py b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/procfs.py new file mode 100644 index 0000000..3fcc45c --- /dev/null +++ b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/procfs.py @@ -0,0 +1,1110 @@ +#!/usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2007-2015 Red Hat, Inc. +# + +import os +import platform +import re +import time +from functools import reduce +from procfs.utilist import bitmasklist + +VERSION = "0.7.3" + + +def is_s390(): + """ Return True if running on s390 or s390x """ + machine = platform.machine() + return bool(re.search('s390', machine)) + + +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() + + try: + """ If a pid disappears before we query it, return None """ + return pid_info["stat"]["comm"] + except: + return None + + +class pidstat: + """ + Provides a dictionary to access the fields in the + per process /proc/PID/stat files. + + One can obtain the available fields by 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'] + + And then access the various process properties using it as a dictionary: + + >>> print p['comm'] + systemd + >>> print p['priority'] + 20 + >>> print p['state'] + S + + 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. + """ + + # 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 + # /* set_user noticed that RLIMIT_NPROC was exceeded */ + PF_NPROC_EXCEEDED = 0x00001000 + 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 + # /* Userland is not allowed to meddle with cpus_allowed */ + PF_NO_SETAFFINITY = 0x04000000 + 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 + # /* this thread called freeze_processes and should not be frozen */ + PF_SUSPEND_TASK = 0x80000000 + + 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 + try: + self.load(basedir) + except FileNotFoundError: + # The file representing the pid has disappeared + # propagate the error to the user to handle + raise + + 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"): + try: + f = open(f"{basedir}/{self.pid}/stat") + except FileNotFoundError: + # The pid has disappeared, propagate the error + raise + 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 bool(self.fields["flags"] & self.PF_THREAD_BOUND) + + 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 + try: + return bool(int(self.processes[pid]["stat"]["flags"]) & + PF_NO_SETAFFINITY) + except: + return True + + +def cannot_set_thread_affinity(self, pid, tid): + PF_NO_SETAFFINITY = 0x04000000 + try: + return bool(int(self.processes[pid].threads[tid]["stat"]["flags"]) & + PF_NO_SETAFFINITY) + except: + 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 by 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 __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"): + self.fields = {} + with open(f"{basedir}/{self.pid}/status") as f: + 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 + + +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 + + try: + setattr(self, attr, sclass(self.pid, self.basedir)) + except FileNotFoundError: + # The pid has disappeared, progate the error + raise + 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): + try: + with open(f"/proc/{self.pid}/cmdline") as f: + self.cmdline = f.readline().strip().split('\0')[:-1] + except FileNotFoundError: + """ This can happen when a pid disappears """ + self.cmdline = None + except UnicodeDecodeError: + """ TODO - this shouldn't happen, needs to be investigated """ + self.cmdline = None + + def load_threads(self): + self.threads = pidstats(f"/proc/{self.pid}/task/") + # remove thread leader + del self.threads[self.pid] + + def load_cgroups(self): + self.cgroups = "" + with open(f"/proc/{self.pid}/cgroup") as f: + for line in reversed(f.readlines()): + if len(self.cgroups) != 0: + self.cgroups = self.cgroups + "," + line[:-1] + else: + self.cgroups = line[:-1] + + 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 = {} + with open(f"/proc/{self.pid}/environ") as f: + for x in f.readline().split('\0'): + if len(x) > 0: + y = x.split('=') + self.environ[y[0]] = y[1] + + +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 list(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 list(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 list(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 list(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 = f"{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 += f'{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 += f'{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 = {} + with open("/proc/interrupts") as f: + 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) + + 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: + with open(f"/proc/irq/{irq}/smp_affinity") as f: + line = f.readline() + 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 list(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 list(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." + + 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. + + 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 parse(self): + with open("/proc/cmdline") as f: + 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 __getitem__(self, key): + return self.options[key] + + def keys(self): + return list(self.options.keys()) + + def values(self): + return list(self.options.values()) + + def items(self): + return self.options + + +class cpuinfo: + """ + Dictionary with information about CPUs in the system. + + 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. + + Using this class one can obtain the number of CPUs in a system: + + >>> 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: + + >>> print len(cpus.sockets) + 1 + + Additionally dictionary with information common to all CPUs in the system + is available: + + >>> 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): + with open(filename) as f: + for line in f.readlines(): + line = line.strip() + if not line: + continue + fields = line.split(":") + tagname = fields[0].strip().lower() + if tagname == "processor": + self.nr_cpus += 1 + continue + if is_s390() and tagname == "cpu number": + self.nr_cpus += 1 + continue + if 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) + 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 + + +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. + + Example: Listing the executable maps for the 'sshd' process: + + >>> import procfs + >>> processes = procfs.pidstats() + >>> sshd = processes.find_by_name("sshd") + >>> sshd_maps = procfs.smaps(sshd[0]) + >>> for i in range(len(sshd_maps)): + ... if 'x' in sshd_maps[i].perms: + ... print "%s: %s" % (sshd_maps[i].name, sshd_maps[i].perms) + ... + /usr/sbin/sshd: r-xp + /usr/lib64/libnss_files-2.20.so: r-xp + /usr/lib64/librt-2.20.so: r-xp + /usr/lib64/libkeyutils.so.1.5: r-xp + /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): + line = None + with open(f"/proc/{self.pid}/smaps") as f: + while True: + line = self.parse_entry(f, line) + if not line: + break + 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 = f"< user: {self.user}, nice: {self.nice}, system: {self.system}, idle: {self.idle}, iowait: {self.iowait}, irq: {self.irq}, softirq: {self.softirq}" + if hasattr(self, 'steal'): + s += f", steal: {self.steal}" + if hasattr(self, 'guest'): + s += f", guest: {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 = {} + with open(self.filename) as f: + 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 + 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 list(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 + curr.usage = min(curr.usage, 100) + + +if __name__ == '__main__': + import sys + + ints = interrupts() + + for i in list(ints.interrupts.keys()): + print(f"{i}: {ints.interrupts[i]}") + + options = cmdline() + for o in list(options.options.keys()): + print(f"{o}: {options.options[o]}") + + cpu = cpuinfo() + print(f"\ncpuinfo data: {cpu.nr_cpus} processors") + for tag in list(cpu.keys()): + print(f"{tag}={cpu[tag]}") + + print("smaps:\n" + ("-" * 40)) + s = smaps(int(sys.argv[1])) + for i in range(s.nr_entries): + print(f"{s.entries[i].vm_start:#x} {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(f"{cpu}: {cs[cpu]}") + print("-" * 10) diff --git a/.pybuild/cpython3_3.12_linux-procfs/build/procfs/utilist.py b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/utilist.py new file mode 100644 index 0000000..2e260b0 --- /dev/null +++ b/.pybuild/cpython3_3.12_linux-procfs/build/procfs/utilist.py @@ -0,0 +1,40 @@ +#! /usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2007 Red Hat, Inc. +# + + + +def hexbitmask(l, nr_entries): + hexbitmask = [] + bit = 0 + mask = 0 + for entry in range(nr_entries): + if entry in l: + mask |= (1 << bit) + bit += 1 + if bit == 32: + bit = 0 + hexbitmask.insert(0, mask) + mask = 0 + + if bit < 32 and mask != 0: + hexbitmask.insert(0, mask) + + return hexbitmask + +def bitmasklist(line, nr_entries): + hexmask = line.strip().replace(",", "") + bitmasklist = [] + entry = 0 + bitmask = bin(int(hexmask, 16))[2::] + for i in reversed(bitmask): + if int(i) & 1: + bitmasklist.append(entry) + entry += 1 + if entry == nr_entries: + break + return bitmasklist diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..1699f18 --- /dev/null +++ b/COPYING @@ -0,0 +1,5 @@ +python-linux-procfs is provided under + + SPDX-License-Identifier: GPL-2.0-only + +All contributions to python-linux-procfs are subject to this COPYING file. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6819214 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CTAGS_EXTRA := $(shell ctags --version 2>&1 | grep -iq universal && echo extras || echo extra) +.PHONY: tags +tags: + ctags -R --$(CTAGS_EXTRA)=+fq --python-kinds=+cfmvi + +.PHONY: cleantags +cleantags: + rm -f tags + +.PHONY: pyclean +pyclean: + @find . -type f \( -name \*~ -o -name \*.pyc \) -delete + +clean: pyclean cleantags diff --git a/README.md b/README.md deleted file mode 100644 index 9ebb840..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# template-repository \ No newline at end of file diff --git a/bitmasklist_test.py b/bitmasklist_test.py new file mode 100755 index 0000000..af26884 --- /dev/null +++ b/bitmasklist_test.py @@ -0,0 +1,75 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-only + +""" Module to test bitmasklist functionality """ + +import sys +from procfs import bitmasklist + +class BitmasklistTest: + """ class to run the bitmasklist test """ + # Assume true (passed) until proven false + # Many tests can be run, but just one failure is recorded overall here + unit_test_result = 0 # Assume true (passed) until proven false + + def __init__(self, line, nr_entries, expected_result): + self.result = 0 # Assume pass + self.line = line + self.nr_entries = nr_entries # Corresponds to the number of cpus + self.expected_result = expected_result + + # A failure in any single test is recorded as an overall failure + def set_unit_test_result(self): + """ set unit_test_result to fail if any test fails """ + if BitmasklistTest.unit_test_result == 1: + return + if self.result == 1: + BitmasklistTest.unit_test_result = 1 + return + + # This is the function that actually runs the test + def bitmasklist_test(self): + """ Run the test """ + print("\n##################\n") + cpu = bitmasklist(self.line, self.nr_entries) + print("Converted : ", self.line, "\nto ", cpu) + if cpu == self.expected_result: + self.result = 0 + print("PASS") + else: + self.result = 1 + print("expected : ", self.expected_result) + print("FAIL") + self.set_unit_test_result() + +# CPU 2 +t = \ + BitmasklistTest("00000000,00000000,00000000,00000000,00000000,00000004", 44, [2]) +t.bitmasklist_test() + +# CPU 34 +t = \ + BitmasklistTest("00000000,00000000,00000000,00000000,00000004,00000000", 44, [34]) +t.bitmasklist_test() + +# CPU 30 +t = \ + BitmasklistTest("00000000,00000000,00000000,00000000,00000000,40000000", 44, [30]) +t.bitmasklist_test() + +# CPU 0, 32 +t = \ + BitmasklistTest("00000000,00000000,00000000,00000000,00000001,00000001", 44, [0, 32]) +t.bitmasklist_test() + +# cpu 0-15 +t = \ + BitmasklistTest("ffff", 44, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) +t.bitmasklist_test() + +#cpu 0-71 +t = \ + BitmasklistTest("ff,ffffffff,ffffffff", 96, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71]) +t.bitmasklist_test() + +sys.exit(BitmasklistTest.unit_test_result) diff --git a/build/scripts-3.12/pflags b/build/scripts-3.12/pflags new file mode 100755 index 0000000..35fa661 --- /dev/null +++ b/build/scripts-3.12/pflags @@ -0,0 +1,86 @@ +#!/usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# print process flags +# Copyright (C) 2015 Red Hat Inc. +# Arnaldo Carvalho de Melo +# +# This application is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2. +# +# This application is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + + +import procfs, re, fnmatch, sys +import argparse +from functools import reduce + +ps = None + +def thread_mapper(s): + global ps + + try: + return [int(s), ] + except: + pass + try: + return ps.find_by_regex(re.compile(fnmatch.translate(s))) + except: + return ps.find_by_name(s) + +def main(argv): + + global ps + ps = procfs.pidstats() + + parser = argparse.ArgumentParser(description='Print process flags') + parser.add_argument('pid', nargs='*', help='a list of pids or names') + args = parser.parse_args() + + if len(argv) > 1: + pids = args.pid + pids = reduce(lambda i, j: i + j, list(map(thread_mapper, pids))) + else: + pids = list(ps.processes.keys()) + + pids.sort() + len_comms = [] + for pid in pids: + if pid in ps: + try: + len(ps[pid]["stat"]["comm"]) + except (TypeError, FileNotFoundError): + continue + len_comms.append(len(ps[pid]["stat"]["comm"])) + + max_comm_len = max(len_comms, default=0) + del len_comms + + for pid in pids: + if pid not in ps: + continue + try: + flags = ps[pid].stat.process_flags() + except AttributeError: + continue + # Remove flags that were superseeded + if "PF_THREAD_BOUND" in flags and "PF_NO_SETAFFINITY" in flags: + flags.remove("PF_THREAD_BOUND") + if "PF_FLUSHER" in flags and "PF_NPROC_EXCEEDED" in flags: + flags.remove("PF_FLUSHER") + if "PF_SWAPOFF" in flags and "PF_MEMALLOC_NOIO" in flags: + flags.remove("PF_SWAPOFF") + if "PF_FREEZER_NOSIG" in flags and "PF_SUSPEND_TASK" in flags: + flags.remove("PF_FREEZER_NOSIG") + comm = ps[pid].stat["comm"] + flags.sort() + sflags = reduce(lambda i, j: "%s|%s" % (i, j), [a[3:] for a in flags]) + print("%6d %*s %s" %(pid, max_comm_len, comm, sflags)) + +if __name__ == '__main__': + main(sys.argv) diff --git a/debian/.debhelper/generated/python3-linux-procfs/dh_installchangelogs.dch.trimmed b/debian/.debhelper/generated/python3-linux-procfs/dh_installchangelogs.dch.trimmed new file mode 100644 index 0000000..3b08628 --- /dev/null +++ b/debian/.debhelper/generated/python3-linux-procfs/dh_installchangelogs.dch.trimmed @@ -0,0 +1,77 @@ +python-linux-procfs (0.7.3-3) unstable; urgency=medium + + * Team upload. + * Patch-out usage of python3-six. No clear way to contact upstream. + + -- Alexandre Detiste Mon, 11 Nov 2024 12:26:35 +0100 + +python-linux-procfs (0.7.3-2) unstable; urgency=medium + + * Team Upload + * Set DPT as Maintainer per new Team Policy + * Update Homepage + + -- Alexandre Detiste Thu, 22 Aug 2024 10:15:36 +0200 + +python-linux-procfs (0.7.3-1) unstable; urgency=medium + + * Team Upload + * New upstream version 0.7.3 + + [ Debian Janitor ] + * Update standards version to 4.6.1, no changes needed. + * Remove constraints unnecessary since buster (oldstable): + + python3-linux-procfs: Drop versioned constraint + on python-linux-procfs in Replaces & Breaks. + + -- Alexandre Detiste Sun, 14 Apr 2024 21:15:20 +0200 + +python-linux-procfs (0.6.3-1) unstable; urgency=medium + + [ Ondřej Nový ] + * d/control: Update Vcs-* fields with new Debian Python Team Salsa + layout. + + [ Sandro Tosi ] + * Use the new Debian Python Team contact name and address + + [ Stewart Ferguson ] + * Imported Upstream version 0.6.3 + * Removing upstream patch + * Bumping Standards-Version: 4.5.1 + + -- Stewart Ferguson Wed, 20 Jan 2021 14:38:46 +0100 + +python-linux-procfs (0.6.2-1) unstable; urgency=medium + + * Imported Upstream version 0.6.2 + * Adding upstream patch to fix failed utilist import + * Bump Standards-Version to 4.5.0 + * Removing unused install override + * Moving man page from patch to d/ + * Removing clean target from rules. Using debian/clean instead + * Bumping compat 13 + * Adding Rules-Requires-Root + * Bumping copyright years + * Now using dh-sequence-python3 instead of --with python3 + + -- Stewart Ferguson Sat, 11 Jul 2020 11:59:58 +0200 + +python-linux-procfs (0.6.1-2) unstable; urgency=medium + + [ Ondřej Nový ] + * Bump Standards-Version to 4.4.0. + + [ Stewart Ferguson ] + * Removing python2 binary package + * Replacing pflags3 with pflags and removing upodate-alternatives + + Breaks python-linux-procfs + * Removing unused .gitignore ignore rule + * Removing superfluous copyright block for COPYING file + * Improving package description + * Removing redundant debhelper build-dep + + -- Stewart Ferguson Mon, 29 Jul 2019 21:44:23 +0200 + +# Older entries have been removed from this changelog. +# To read the complete changelog use `apt changelog python3-linux-procfs`. diff --git a/debian/.debhelper/generated/python3-linux-procfs/installed-by-dh_installdocs b/debian/.debhelper/generated/python3-linux-procfs/installed-by-dh_installdocs new file mode 100644 index 0000000..e69de29 diff --git a/debian/.debhelper/generated/python3-linux-procfs/installed-by-dh_installman b/debian/.debhelper/generated/python3-linux-procfs/installed-by-dh_installman new file mode 100644 index 0000000..248adaa --- /dev/null +++ b/debian/.debhelper/generated/python3-linux-procfs/installed-by-dh_installman @@ -0,0 +1 @@ +./debian/pflags.8 diff --git a/debian/.gitlab-ci.yml b/debian/.gitlab-ci.yml new file mode 100644 index 0000000..79d6bf5 --- /dev/null +++ b/debian/.gitlab-ci.yml @@ -0,0 +1,11 @@ +image: registry.salsa.debian.org/salsa-ci-team/ci-image-git-buildpackage:latest + +pages: + stage: deploy + artifacts: + paths: + - public + script: + - gitlab-ci-git-buildpackage + - gitlab-ci-lintian + - gitlab-ci-aptly diff --git a/debian/changelog b/debian/changelog index bad88e2..297be81 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,96 @@ -template-repository (1.0-1) unstable; urgency=medium +python-linux-procfs (0.7.3-3) unstable; urgency=medium - * Initial release + * Team upload. + * Patch-out usage of python3-six. No clear way to contact upstream. - -- Tsic404 Sat, 28 Jan 2023 13:46:49 +0800 + -- Alexandre Detiste Mon, 11 Nov 2024 12:26:35 +0100 + +python-linux-procfs (0.7.3-2) unstable; urgency=medium + + * Team Upload + * Set DPT as Maintainer per new Team Policy + * Update Homepage + + -- Alexandre Detiste Thu, 22 Aug 2024 10:15:36 +0200 + +python-linux-procfs (0.7.3-1) unstable; urgency=medium + + * Team Upload + * New upstream version 0.7.3 + + [ Debian Janitor ] + * Update standards version to 4.6.1, no changes needed. + * Remove constraints unnecessary since buster (oldstable): + + python3-linux-procfs: Drop versioned constraint + on python-linux-procfs in Replaces & Breaks. + + -- Alexandre Detiste Sun, 14 Apr 2024 21:15:20 +0200 + +python-linux-procfs (0.6.3-1) unstable; urgency=medium + + [ Ondřej Nový ] + * d/control: Update Vcs-* fields with new Debian Python Team Salsa + layout. + + [ Sandro Tosi ] + * Use the new Debian Python Team contact name and address + + [ Stewart Ferguson ] + * Imported Upstream version 0.6.3 + * Removing upstream patch + * Bumping Standards-Version: 4.5.1 + + -- Stewart Ferguson Wed, 20 Jan 2021 14:38:46 +0100 + +python-linux-procfs (0.6.2-1) unstable; urgency=medium + + * Imported Upstream version 0.6.2 + * Adding upstream patch to fix failed utilist import + * Bump Standards-Version to 4.5.0 + * Removing unused install override + * Moving man page from patch to d/ + * Removing clean target from rules. Using debian/clean instead + * Bumping compat 13 + * Adding Rules-Requires-Root + * Bumping copyright years + * Now using dh-sequence-python3 instead of --with python3 + + -- Stewart Ferguson Sat, 11 Jul 2020 11:59:58 +0200 + +python-linux-procfs (0.6.1-2) unstable; urgency=medium + + [ Ondřej Nový ] + * Bump Standards-Version to 4.4.0. + + [ Stewart Ferguson ] + * Removing python2 binary package + * Replacing pflags3 with pflags and removing upodate-alternatives + + Breaks python-linux-procfs + * Removing unused .gitignore ignore rule + * Removing superfluous copyright block for COPYING file + * Improving package description + * Removing redundant debhelper build-dep + + -- Stewart Ferguson Mon, 29 Jul 2019 21:44:23 +0200 + +python-linux-procfs (0.6.1-1) unstable; urgency=medium + + * Upstream release 0.6 -> 0.6.1 + * debhelper compat 10 -> 12 + + Build system python_distutils -> pybuild + + Removed old d/compat file + * Standards-Version 4.2.1 -> 4.3.0 + + No changes required + * Removing .gitignore from debian source + * Correcting license from GPL-2+ to GPL-2 + * Adding dh-python to Build-Depends + + -- Stewart Ferguson Sun, 10 Feb 2019 21:32:12 +0100 + +python-linux-procfs (0.6-1) unstable; urgency=medium + + * Initial release (Closes: #912771) + * Python 2 version supplied to satisfy current state of tuna. Tuna's GUI-mode + * still relies on python2. + + -- Stewart Ferguson Sat, 03 Nov 2018 17:32:03 +0100 diff --git a/debian/clean b/debian/clean new file mode 100644 index 0000000..79acad2 --- /dev/null +++ b/debian/clean @@ -0,0 +1,2 @@ +debian/pflags.8 +python_linux_procfs.egg-info/ diff --git a/debian/compat b/debian/compat deleted file mode 100644 index b4de394..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -11 diff --git a/debian/control b/debian/control index cb7c4a0..856a042 100644 --- a/debian/control +++ b/debian/control @@ -1,15 +1,33 @@ -Source: template-repository -Section: unknown +Source: python-linux-procfs +Section: python Priority: optional -Maintainer: Tsic404 -Build-Depends: debhelper (>= 11) -Standards-Version: 4.1.3 -Homepage: https://github.com/deepin-community/template-repository -#Vcs-Browser: https://salsa.debian.org/debian/deepin-community-template-repository -#Vcs-Git: https://salsa.debian.org/debian/deepin-community-template-repository.git +Maintainer: Debian Python Team +Uploaders: Stewart Ferguson , +Build-Depends: + debhelper-compat (= 13), + dh-sequence-python3, + python3-setuptools, + python3-all-dev, + asciidoc-base, + libxml2-utils, + docbook-xml, + docbook-xsl, + xsltproc +Standards-Version: 4.6.1 +Homepage: https://git.kernel.org/pub/scm/libs/python/python-linux-procfs/python-linux-procfs.git/ +Vcs-Browser: https://salsa.debian.org/python-team/packages/python-linux-procfs +Vcs-Git: https://salsa.debian.org/python-team/packages/python-linux-procfs.git +Rules-Requires-Root: no -Package: template-repository -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: - +Package: python3-linux-procfs +Architecture: linux-any +Depends: + ${misc:Depends}, + ${python3:Depends}, +Description: Linux /proc abstraction classes in Python + Python abstractions to extract information from the Linux kernel /proc + files. + . + The proc filesystem is a pseudo-filesystem which provides an interface to + kernel data structures. This package provides a means to query that system + from a Python module. diff --git a/debian/copyright b/debian/copyright index f5c805e..a33a9bc 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,14 +1,25 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: template-repository -Source: https://github.com/deepin-community/template-repository +Upstream-Name: python-linux-procfs +Upstream-Contact: Jiri Kastner +Source: https://git.kernel.org/pub/scm/libs/python/python-linux-procfs/python-linux-procfs.git/ Files: * -Copyright: 2023 Tsic404 -License: GPL-2+ +Copyright: 2007-2015 Red Hat Inc. +License: GPL-2 +Comment: Content was authored by Arnaldo Carvalho de Melo in + 2007. Maintainership transferred to Jiri Kastner in + 2015. Jiri Kasterner (as of 2018) is the point of contact for all upstream + matters related to this library. The dates of this copyright were deduced from + procfs/procfs.py, a primary file in this library. + +Files: debian/* +Copyright: 2018-2020 Stewart Ferguson +License: GPL-2 + +License: GPL-2 This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. + the Free Software Foundation; version 2 of the License . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/debian/debhelper-build-stamp b/debian/debhelper-build-stamp new file mode 100644 index 0000000..7421372 --- /dev/null +++ b/debian/debhelper-build-stamp @@ -0,0 +1 @@ +python3-linux-procfs diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..56e2150 --- /dev/null +++ b/debian/files @@ -0,0 +1,2 @@ +python-linux-procfs_0.7.3-3_amd64.buildinfo python optional +python3-linux-procfs_0.7.3-3_amd64.deb python optional diff --git a/debian/patches/remove-six.patch b/debian/patches/remove-six.patch new file mode 100644 index 0000000..3c5e057 --- /dev/null +++ b/debian/patches/remove-six.patch @@ -0,0 +1,38 @@ +--- a/pflags ++++ b/pflags +@@ -18,7 +18,6 @@ + import procfs, re, fnmatch, sys + import argparse + from functools import reduce +-from six.moves import map + + ps = None + +--- a/procfs/procfs.py ++++ b/procfs/procfs.py +@@ -11,7 +11,6 @@ + import re + import time + from functools import reduce +-from six.moves import range + from procfs.utilist import bitmasklist + + VERSION = "0.7.3" +--- a/procfs/utilist.py ++++ b/procfs/utilist.py +@@ -6,7 +6,6 @@ + # Copyright (C) 2007 Red Hat, Inc. + # + +-from six.moves import range + + + def hexbitmask(l, nr_entries): +--- a/setup.py ++++ b/setup.py +@@ -29,5 +29,4 @@ + """, + packages = ["procfs"], + scripts = ['pflags'], +- install_requires = ['six'], + ) diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..6a9f04d --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +remove-six.patch diff --git a/debian/pflags.8 b/debian/pflags.8 new file mode 100644 index 0000000..9f34edc --- /dev/null +++ b/debian/pflags.8 @@ -0,0 +1,53 @@ +'\" t +.\" Title: pflags +.\" Author: [see the "AUTHORS" section] +.\" Generator: DocBook XSL Stylesheets vsnapshot +.\" Date: 11/11/2024 +.\" Manual: \ \& +.\" Source: \ \& +.\" Language: English +.\" +.TH "PFLAGS" "8" "11/11/2024" "\ \&" "\ \&" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- +.\" * set default formatting +.\" ----------------------------------------------------------------- +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.\" ----------------------------------------------------------------- +.\" * MAIN CONTENT STARTS HERE * +.\" ----------------------------------------------------------------- +.SH "NAME" +pflags \- Print process flags +.SH "SYNOPSIS" +.sp +pflags [PID] [NAME] +.SH "DESCRIPTION" +.sp +This script prints process flags and is written purely in python using the python\-linux\-procfs package +.SH "OPTIONS" +.PP +PID +.RS 4 +Is the process ID to be queried +.RE +.PP +NAME +.RS 4 +Is the name of the process to be queried +.RE +.SH "AUTHORS" +.sp +Arnaldo Carvalho de Melo +.sp +Man page written by Stewart Ferguson diff --git a/debian/pflags.8.asciidoc b/debian/pflags.8.asciidoc new file mode 100644 index 0000000..37be999 --- /dev/null +++ b/debian/pflags.8.asciidoc @@ -0,0 +1,35 @@ +pflags(8) +=========== + +NAME +---- +pflags - Print process flags + + +SYNOPSIS +-------- +pflags [PID] [NAME] + + +DESCRIPTION +----------- + +This script prints process flags and is written purely in python +using the python-linux-procfs package + + +OPTIONS +------- + +PID:: +Is the process ID to be queried + +NAME:: +Is the name of the process to be queried + + +AUTHORS +------- +Arnaldo Carvalho de Melo + +Man page written by Stewart Ferguson diff --git a/debian/python3-linux-procfs.debhelper.log b/debian/python3-linux-procfs.debhelper.log new file mode 100644 index 0000000..1108d89 --- /dev/null +++ b/debian/python3-linux-procfs.debhelper.log @@ -0,0 +1 @@ +dh_auto_build diff --git a/debian/python3-linux-procfs.manpages b/debian/python3-linux-procfs.manpages new file mode 100644 index 0000000..d25733c --- /dev/null +++ b/debian/python3-linux-procfs.manpages @@ -0,0 +1 @@ +debian/pflags.8 diff --git a/debian/python3-linux-procfs.postinst.debhelper b/debian/python3-linux-procfs.postinst.debhelper new file mode 100644 index 0000000..57cbc15 --- /dev/null +++ b/debian/python3-linux-procfs.postinst.debhelper @@ -0,0 +1,10 @@ + +# Automatically added by dh_python3 +if command -v py3compile >/dev/null 2>&1; then + py3compile -p python3-linux-procfs:amd64 +fi +if command -v pypy3compile >/dev/null 2>&1; then + pypy3compile -p python3-linux-procfs:amd64 || true +fi + +# End automatically added section diff --git a/debian/python3-linux-procfs.prerm.debhelper b/debian/python3-linux-procfs.prerm.debhelper new file mode 100644 index 0000000..d324ec3 --- /dev/null +++ b/debian/python3-linux-procfs.prerm.debhelper @@ -0,0 +1,10 @@ + +# Automatically added by dh_python3 +if command -v py3clean >/dev/null 2>&1; then + py3clean -p python3-linux-procfs:amd64 +else + dpkg -L python3-linux-procfs:amd64 | sed -En -e '/^(.*)\/(.+)\.py$/s,,rm "\1/__pycache__/\2".*,e' + find /usr/lib/python3/dist-packages/ -type d -name __pycache__ -empty -print0 | xargs --null --no-run-if-empty rmdir +fi + +# End automatically added section diff --git a/debian/python3-linux-procfs.substvars b/debian/python3-linux-procfs.substvars new file mode 100644 index 0000000..13de292 --- /dev/null +++ b/debian/python3-linux-procfs.substvars @@ -0,0 +1,3 @@ +python3:Depends=python3:any +misc:Depends= +misc:Pre-Depends= diff --git a/debian/python3-linux-procfs/DEBIAN/control b/debian/python3-linux-procfs/DEBIAN/control new file mode 100644 index 0000000..d138d46 --- /dev/null +++ b/debian/python3-linux-procfs/DEBIAN/control @@ -0,0 +1,17 @@ +Package: python3-linux-procfs +Source: python-linux-procfs +Version: 0.7.3-3 +Architecture: amd64 +Maintainer: Debian Python Team +Installed-Size: 65 +Depends: python3:any +Section: python +Priority: optional +Homepage: https://git.kernel.org/pub/scm/libs/python/python-linux-procfs/python-linux-procfs.git/ +Description: Linux /proc abstraction classes in Python + Python abstractions to extract information from the Linux kernel /proc + files. + . + The proc filesystem is a pseudo-filesystem which provides an interface to + kernel data structures. This package provides a means to query that system + from a Python module. diff --git a/debian/python3-linux-procfs/DEBIAN/md5sums b/debian/python3-linux-procfs/DEBIAN/md5sums new file mode 100644 index 0000000..b046c6d --- /dev/null +++ b/debian/python3-linux-procfs/DEBIAN/md5sums @@ -0,0 +1,10 @@ +4c7610655610a41cea153e050ed18702 usr/bin/pflags +7a3fd3f40d661912de53b121c7856ffa usr/lib/python3/dist-packages/procfs/__init__.py +0da5faf1bb10b3ed7bd3fc5d1c1aaff8 usr/lib/python3/dist-packages/procfs/procfs.py +45d274f39bbab8fef5b116afe84446fb usr/lib/python3/dist-packages/procfs/utilist.py +e040cb73f88d60fafe9abd2c56c54723 usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/PKG-INFO +68b329da9893e34099c7d8ad5cb9c940 usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/dependency_links.txt +64bba31d8b839dd7a83ad8657965aeef usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/top_level.txt +646972ec72638c2557194e0eb8b1a587 usr/share/doc/python3-linux-procfs/changelog.Debian.gz +d12fc6b3ebffc15b27b7295134930090 usr/share/doc/python3-linux-procfs/copyright +cffda690327637b5a2da63835e4bd271 usr/share/man/man8/pflags.8.gz diff --git a/debian/python3-linux-procfs/DEBIAN/postinst b/debian/python3-linux-procfs/DEBIAN/postinst new file mode 100755 index 0000000..fcf42e6 --- /dev/null +++ b/debian/python3-linux-procfs/DEBIAN/postinst @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +# Automatically added by dh_python3 +if command -v py3compile >/dev/null 2>&1; then + py3compile -p python3-linux-procfs:amd64 +fi +if command -v pypy3compile >/dev/null 2>&1; then + pypy3compile -p python3-linux-procfs:amd64 || true +fi + +# End automatically added section diff --git a/debian/python3-linux-procfs/DEBIAN/prerm b/debian/python3-linux-procfs/DEBIAN/prerm new file mode 100755 index 0000000..6120461 --- /dev/null +++ b/debian/python3-linux-procfs/DEBIAN/prerm @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +# Automatically added by dh_python3 +if command -v py3clean >/dev/null 2>&1; then + py3clean -p python3-linux-procfs:amd64 +else + dpkg -L python3-linux-procfs:amd64 | sed -En -e '/^(.*)\/(.+)\.py$/s,,rm "\1/__pycache__/\2".*,e' + find /usr/lib/python3/dist-packages/ -type d -name __pycache__ -empty -print0 | xargs --null --no-run-if-empty rmdir +fi + +# End automatically added section diff --git a/debian/python3-linux-procfs/usr/bin/pflags b/debian/python3-linux-procfs/usr/bin/pflags new file mode 100755 index 0000000..35fa661 --- /dev/null +++ b/debian/python3-linux-procfs/usr/bin/pflags @@ -0,0 +1,86 @@ +#!/usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# print process flags +# Copyright (C) 2015 Red Hat Inc. +# Arnaldo Carvalho de Melo +# +# This application is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2. +# +# This application is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + + +import procfs, re, fnmatch, sys +import argparse +from functools import reduce + +ps = None + +def thread_mapper(s): + global ps + + try: + return [int(s), ] + except: + pass + try: + return ps.find_by_regex(re.compile(fnmatch.translate(s))) + except: + return ps.find_by_name(s) + +def main(argv): + + global ps + ps = procfs.pidstats() + + parser = argparse.ArgumentParser(description='Print process flags') + parser.add_argument('pid', nargs='*', help='a list of pids or names') + args = parser.parse_args() + + if len(argv) > 1: + pids = args.pid + pids = reduce(lambda i, j: i + j, list(map(thread_mapper, pids))) + else: + pids = list(ps.processes.keys()) + + pids.sort() + len_comms = [] + for pid in pids: + if pid in ps: + try: + len(ps[pid]["stat"]["comm"]) + except (TypeError, FileNotFoundError): + continue + len_comms.append(len(ps[pid]["stat"]["comm"])) + + max_comm_len = max(len_comms, default=0) + del len_comms + + for pid in pids: + if pid not in ps: + continue + try: + flags = ps[pid].stat.process_flags() + except AttributeError: + continue + # Remove flags that were superseeded + if "PF_THREAD_BOUND" in flags and "PF_NO_SETAFFINITY" in flags: + flags.remove("PF_THREAD_BOUND") + if "PF_FLUSHER" in flags and "PF_NPROC_EXCEEDED" in flags: + flags.remove("PF_FLUSHER") + if "PF_SWAPOFF" in flags and "PF_MEMALLOC_NOIO" in flags: + flags.remove("PF_SWAPOFF") + if "PF_FREEZER_NOSIG" in flags and "PF_SUSPEND_TASK" in flags: + flags.remove("PF_FREEZER_NOSIG") + comm = ps[pid].stat["comm"] + flags.sort() + sflags = reduce(lambda i, j: "%s|%s" % (i, j), [a[3:] for a in flags]) + print("%6d %*s %s" %(pid, max_comm_len, comm, sflags)) + +if __name__ == '__main__': + main(sys.argv) diff --git a/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/__init__.py b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/__init__.py new file mode 100644 index 0000000..6deedf4 --- /dev/null +++ b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/__init__.py @@ -0,0 +1,17 @@ +#! /usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2008, 2009 Red Hat, Inc. +# +""" +Copyright (c) 2008, 2009 Red Hat Inc. + +Abstractions to extract information from the Linux kernel /proc files. +""" +__author__ = "Arnaldo Carvalho de Melo " +__license__ = "GPLv2 License" + +from .procfs import * +from .utilist import * diff --git a/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/procfs.py b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/procfs.py new file mode 100644 index 0000000..3fcc45c --- /dev/null +++ b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/procfs.py @@ -0,0 +1,1110 @@ +#!/usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2007-2015 Red Hat, Inc. +# + +import os +import platform +import re +import time +from functools import reduce +from procfs.utilist import bitmasklist + +VERSION = "0.7.3" + + +def is_s390(): + """ Return True if running on s390 or s390x """ + machine = platform.machine() + return bool(re.search('s390', machine)) + + +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() + + try: + """ If a pid disappears before we query it, return None """ + return pid_info["stat"]["comm"] + except: + return None + + +class pidstat: + """ + Provides a dictionary to access the fields in the + per process /proc/PID/stat files. + + One can obtain the available fields by 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'] + + And then access the various process properties using it as a dictionary: + + >>> print p['comm'] + systemd + >>> print p['priority'] + 20 + >>> print p['state'] + S + + 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. + """ + + # 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 + # /* set_user noticed that RLIMIT_NPROC was exceeded */ + PF_NPROC_EXCEEDED = 0x00001000 + 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 + # /* Userland is not allowed to meddle with cpus_allowed */ + PF_NO_SETAFFINITY = 0x04000000 + 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 + # /* this thread called freeze_processes and should not be frozen */ + PF_SUSPEND_TASK = 0x80000000 + + 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 + try: + self.load(basedir) + except FileNotFoundError: + # The file representing the pid has disappeared + # propagate the error to the user to handle + raise + + 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"): + try: + f = open(f"{basedir}/{self.pid}/stat") + except FileNotFoundError: + # The pid has disappeared, propagate the error + raise + 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 bool(self.fields["flags"] & self.PF_THREAD_BOUND) + + 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 + try: + return bool(int(self.processes[pid]["stat"]["flags"]) & + PF_NO_SETAFFINITY) + except: + return True + + +def cannot_set_thread_affinity(self, pid, tid): + PF_NO_SETAFFINITY = 0x04000000 + try: + return bool(int(self.processes[pid].threads[tid]["stat"]["flags"]) & + PF_NO_SETAFFINITY) + except: + 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 by 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 __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"): + self.fields = {} + with open(f"{basedir}/{self.pid}/status") as f: + 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 + + +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 + + try: + setattr(self, attr, sclass(self.pid, self.basedir)) + except FileNotFoundError: + # The pid has disappeared, progate the error + raise + 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): + try: + with open(f"/proc/{self.pid}/cmdline") as f: + self.cmdline = f.readline().strip().split('\0')[:-1] + except FileNotFoundError: + """ This can happen when a pid disappears """ + self.cmdline = None + except UnicodeDecodeError: + """ TODO - this shouldn't happen, needs to be investigated """ + self.cmdline = None + + def load_threads(self): + self.threads = pidstats(f"/proc/{self.pid}/task/") + # remove thread leader + del self.threads[self.pid] + + def load_cgroups(self): + self.cgroups = "" + with open(f"/proc/{self.pid}/cgroup") as f: + for line in reversed(f.readlines()): + if len(self.cgroups) != 0: + self.cgroups = self.cgroups + "," + line[:-1] + else: + self.cgroups = line[:-1] + + 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 = {} + with open(f"/proc/{self.pid}/environ") as f: + for x in f.readline().split('\0'): + if len(x) > 0: + y = x.split('=') + self.environ[y[0]] = y[1] + + +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 list(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 list(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 list(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 list(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 = f"{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 += f'{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 += f'{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 = {} + with open("/proc/interrupts") as f: + 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) + + 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: + with open(f"/proc/irq/{irq}/smp_affinity") as f: + line = f.readline() + 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 list(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 list(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." + + 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. + + 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 parse(self): + with open("/proc/cmdline") as f: + 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 __getitem__(self, key): + return self.options[key] + + def keys(self): + return list(self.options.keys()) + + def values(self): + return list(self.options.values()) + + def items(self): + return self.options + + +class cpuinfo: + """ + Dictionary with information about CPUs in the system. + + 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. + + Using this class one can obtain the number of CPUs in a system: + + >>> 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: + + >>> print len(cpus.sockets) + 1 + + Additionally dictionary with information common to all CPUs in the system + is available: + + >>> 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): + with open(filename) as f: + for line in f.readlines(): + line = line.strip() + if not line: + continue + fields = line.split(":") + tagname = fields[0].strip().lower() + if tagname == "processor": + self.nr_cpus += 1 + continue + if is_s390() and tagname == "cpu number": + self.nr_cpus += 1 + continue + if 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) + 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 + + +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. + + Example: Listing the executable maps for the 'sshd' process: + + >>> import procfs + >>> processes = procfs.pidstats() + >>> sshd = processes.find_by_name("sshd") + >>> sshd_maps = procfs.smaps(sshd[0]) + >>> for i in range(len(sshd_maps)): + ... if 'x' in sshd_maps[i].perms: + ... print "%s: %s" % (sshd_maps[i].name, sshd_maps[i].perms) + ... + /usr/sbin/sshd: r-xp + /usr/lib64/libnss_files-2.20.so: r-xp + /usr/lib64/librt-2.20.so: r-xp + /usr/lib64/libkeyutils.so.1.5: r-xp + /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): + line = None + with open(f"/proc/{self.pid}/smaps") as f: + while True: + line = self.parse_entry(f, line) + if not line: + break + 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 = f"< user: {self.user}, nice: {self.nice}, system: {self.system}, idle: {self.idle}, iowait: {self.iowait}, irq: {self.irq}, softirq: {self.softirq}" + if hasattr(self, 'steal'): + s += f", steal: {self.steal}" + if hasattr(self, 'guest'): + s += f", guest: {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 = {} + with open(self.filename) as f: + 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 + 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 list(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 + curr.usage = min(curr.usage, 100) + + +if __name__ == '__main__': + import sys + + ints = interrupts() + + for i in list(ints.interrupts.keys()): + print(f"{i}: {ints.interrupts[i]}") + + options = cmdline() + for o in list(options.options.keys()): + print(f"{o}: {options.options[o]}") + + cpu = cpuinfo() + print(f"\ncpuinfo data: {cpu.nr_cpus} processors") + for tag in list(cpu.keys()): + print(f"{tag}={cpu[tag]}") + + print("smaps:\n" + ("-" * 40)) + s = smaps(int(sys.argv[1])) + for i in range(s.nr_entries): + print(f"{s.entries[i].vm_start:#x} {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(f"{cpu}: {cs[cpu]}") + print("-" * 10) diff --git a/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/utilist.py b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/utilist.py new file mode 100644 index 0000000..2e260b0 --- /dev/null +++ b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/procfs/utilist.py @@ -0,0 +1,40 @@ +#! /usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2007 Red Hat, Inc. +# + + + +def hexbitmask(l, nr_entries): + hexbitmask = [] + bit = 0 + mask = 0 + for entry in range(nr_entries): + if entry in l: + mask |= (1 << bit) + bit += 1 + if bit == 32: + bit = 0 + hexbitmask.insert(0, mask) + mask = 0 + + if bit < 32 and mask != 0: + hexbitmask.insert(0, mask) + + return hexbitmask + +def bitmasklist(line, nr_entries): + hexmask = line.strip().replace(",", "") + bitmasklist = [] + entry = 0 + bitmask = bin(int(hexmask, 16))[2::] + for i in reversed(bitmask): + if int(i) & 1: + bitmasklist.append(entry) + entry += 1 + if entry == nr_entries: + break + return bitmasklist diff --git a/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/PKG-INFO b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/PKG-INFO new file mode 100644 index 0000000..15f662b --- /dev/null +++ b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 2.1 +Name: python-linux-procfs +Version: 0.7.3 +Summary: Linux /proc abstraction classes +Home-page: http://userweb.kernel.org/python-linux-procfs +Author: Arnaldo Carvalho de Melo +Author-email: acme@redhat.com +License: GPLv2 +License-File: COPYING + +Abstractions to extract information from the Linux kernel /proc files. diff --git a/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/dependency_links.txt b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/top_level.txt b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/top_level.txt new file mode 100644 index 0000000..8b71071 --- /dev/null +++ b/debian/python3-linux-procfs/usr/lib/python3/dist-packages/python_linux_procfs-0.7.3.egg-info/top_level.txt @@ -0,0 +1 @@ +procfs diff --git a/debian/python3-linux-procfs/usr/share/doc/python3-linux-procfs/changelog.Debian.gz b/debian/python3-linux-procfs/usr/share/doc/python3-linux-procfs/changelog.Debian.gz new file mode 100644 index 0000000000000000000000000000000000000000..f72bb01d8e3f6c5476905bbae594805327076272 GIT binary patch literal 1039 zcmV+q1n~PGiwFP!000021C>_WZrer>efL)kP@u@gq)F=1m`#f~HQFYCFR<(sMNl9s z?uguawaf0swmu~vlc%Ep)mbhj%dukF4~9i??&r)IwF}p1l}MrdP0|`&%`802(jU@6 zGI$PNS;wXle*|x8RMq0AIdb9WJ39a`;2hZ;d@D8MY4^n`b5)aQ@4#DDBj_2lUHm~} z#Z3yw8Y+p*z%^TdqX(@VtH`>w&QP$F;wA|kNW5W+8-zo2!a9W46c^naj#I%@s!jbf zz>!wFkmnTi3W}^4LSB@`xEze&WtL~z&QAO7)`~~9cG0!gJx|cV;psUr3r9>))k2|x zmi`o8N9$89MYZUxFIvv1>fiMoTbjdDx;k(CE))eE_`2^ZEAvq~7*kipmag)rc6Cg( zFJ})|Xspl*x>&*fBNZAdgZE4crw#nI-W=sooEdIcjs)m19jEy&D1x?OszyuWL5@8= z&Ty`;FrZE;!xSymtFS_AnOT4pszR808jXQxTJqS+^OBqaUaqLL-g*g#Mz_lnkgo;r zya1@MO(PSE%4sE|1O0|@(3%KTkq4lPYzt81?Ld8m6UG1gjDH7i z|NEt%JKqcIVzexm5PYa?@&aZ8X;g46T+@-U2ko>=A0eMGX<0W9$riMpY0NSSU1~Vj zRxGi#2(H0z#aZsFVsiv?2F7Vj*7o5XUNht1S2VS^Z z6h?$%r3BhHV+6;}=qQ@ZssA8LFf)3s2)=hWQc!Bj!l+pnO^ugmQzA)t9hYG*T7`7j zi^>wOO3x`abGvXlXi35(sjgy9T%qmtHj3gV_1s9gv=&a( zN|SE({{mm`np#gMb0)p^b`9#O)YT=-=)(MRa{A}H z?#4FAJxx@(K+OhaIL=cqBu_ZDk0wL+tD@NN$&l{{SsxC|Vz4B`cW@#(g+|h1LC2$E zbke4XD$bM!(d~+Bgzb-@)OAX3=b9{ts6k)DZb&2vUs({t5k9fjt$nrW;C)JW{s)oe J4neaC008_x1}Fdk literal 0 HcmV?d00001 diff --git a/debian/python3-linux-procfs/usr/share/doc/python3-linux-procfs/copyright b/debian/python3-linux-procfs/usr/share/doc/python3-linux-procfs/copyright new file mode 100644 index 0000000..a33a9bc --- /dev/null +++ b/debian/python3-linux-procfs/usr/share/doc/python3-linux-procfs/copyright @@ -0,0 +1,33 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: python-linux-procfs +Upstream-Contact: Jiri Kastner +Source: https://git.kernel.org/pub/scm/libs/python/python-linux-procfs/python-linux-procfs.git/ + +Files: * +Copyright: 2007-2015 Red Hat Inc. +License: GPL-2 +Comment: Content was authored by Arnaldo Carvalho de Melo in + 2007. Maintainership transferred to Jiri Kastner in + 2015. Jiri Kasterner (as of 2018) is the point of contact for all upstream + matters related to this library. The dates of this copyright were deduced from + procfs/procfs.py, a primary file in this library. + +Files: debian/* +Copyright: 2018-2020 Stewart Ferguson +License: GPL-2 + +License: GPL-2 + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". diff --git a/debian/python3-linux-procfs/usr/share/man/man8/pflags.8.gz b/debian/python3-linux-procfs/usr/share/man/man8/pflags.8.gz new file mode 100644 index 0000000000000000000000000000000000000000..9d7354b4d5c120d0545708cb4c9845bbc723a3ca GIT binary patch literal 686 zcmV;f0#W@RiwFP!000021FclwPunmMe$QWV>H{#OI_1a4h7cm71uD@WYBFgP;lWKV ziHB^wz`<8l*3JrIj+Sl24tsmfLd<(H#O0*TCv+!gUuWh9roK?=% zr(u}uY)(vO3#QP8=WnS%pF2wB;2@;uuy=U8wv4!HxIBdv(w#LH>&9gG9=qkLY=i;g(=s zQxsc_^yj0aRe9Z-c~e@J<6LmXv?;^Sy^}8|NADR_S7%wNn%2%uR*5GJE4PqgX>?Ig zU3=fUy~A)}%U-W{aKyZATbT-kRPC`6SZ*PDgmjNT1`Oru_^i?0t!|`62RRnJkq!!N z7Th_Z$}NV-h2?XJP%Y~U75BF*V5-`%{cfxig~(bf?D5c*g40u{DiQcqOSSla!zC$50BD}`!a(yo^&NR4L?)Pz>yCv9Y( +# +# This application is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2. +# +# This application is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + + +import procfs, re, fnmatch, sys +import argparse +from functools import reduce +from six.moves import map + +ps = None + +def thread_mapper(s): + global ps + + try: + return [int(s), ] + except: + pass + try: + return ps.find_by_regex(re.compile(fnmatch.translate(s))) + except: + return ps.find_by_name(s) + +def main(argv): + + global ps + ps = procfs.pidstats() + + parser = argparse.ArgumentParser(description='Print process flags') + parser.add_argument('pid', nargs='*', help='a list of pids or names') + args = parser.parse_args() + + if len(argv) > 1: + pids = args.pid + pids = reduce(lambda i, j: i + j, list(map(thread_mapper, pids))) + else: + pids = list(ps.processes.keys()) + + pids.sort() + len_comms = [] + for pid in pids: + if pid in ps: + try: + len(ps[pid]["stat"]["comm"]) + except (TypeError, FileNotFoundError): + continue + len_comms.append(len(ps[pid]["stat"]["comm"])) + + max_comm_len = max(len_comms, default=0) + del len_comms + + for pid in pids: + if pid not in ps: + continue + try: + flags = ps[pid].stat.process_flags() + except AttributeError: + continue + # Remove flags that were superseeded + if "PF_THREAD_BOUND" in flags and "PF_NO_SETAFFINITY" in flags: + flags.remove("PF_THREAD_BOUND") + if "PF_FLUSHER" in flags and "PF_NPROC_EXCEEDED" in flags: + flags.remove("PF_FLUSHER") + if "PF_SWAPOFF" in flags and "PF_MEMALLOC_NOIO" in flags: + flags.remove("PF_SWAPOFF") + if "PF_FREEZER_NOSIG" in flags and "PF_SUSPEND_TASK" in flags: + flags.remove("PF_FREEZER_NOSIG") + comm = ps[pid].stat["comm"] + flags.sort() + sflags = reduce(lambda i, j: "%s|%s" % (i, j), [a[3:] for a in flags]) + print("%6d %*s %s" %(pid, max_comm_len, comm, sflags)) + +if __name__ == '__main__': + main(sys.argv) diff --git a/procfs/__init__.py b/procfs/__init__.py new file mode 100644 index 0000000..6deedf4 --- /dev/null +++ b/procfs/__init__.py @@ -0,0 +1,17 @@ +#! /usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2008, 2009 Red Hat, Inc. +# +""" +Copyright (c) 2008, 2009 Red Hat Inc. + +Abstractions to extract information from the Linux kernel /proc files. +""" +__author__ = "Arnaldo Carvalho de Melo " +__license__ = "GPLv2 License" + +from .procfs import * +from .utilist import * diff --git a/procfs/procfs.py b/procfs/procfs.py new file mode 100755 index 0000000..7cc7371 --- /dev/null +++ b/procfs/procfs.py @@ -0,0 +1,1111 @@ +#!/usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2007-2015 Red Hat, Inc. +# + +import os +import platform +import re +import time +from functools import reduce +from six.moves import range +from procfs.utilist import bitmasklist + +VERSION = "0.7.3" + + +def is_s390(): + """ Return True if running on s390 or s390x """ + machine = platform.machine() + return bool(re.search('s390', machine)) + + +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() + + try: + """ If a pid disappears before we query it, return None """ + return pid_info["stat"]["comm"] + except: + return None + + +class pidstat: + """ + Provides a dictionary to access the fields in the + per process /proc/PID/stat files. + + One can obtain the available fields by 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'] + + And then access the various process properties using it as a dictionary: + + >>> print p['comm'] + systemd + >>> print p['priority'] + 20 + >>> print p['state'] + S + + 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. + """ + + # 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 + # /* set_user noticed that RLIMIT_NPROC was exceeded */ + PF_NPROC_EXCEEDED = 0x00001000 + 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 + # /* Userland is not allowed to meddle with cpus_allowed */ + PF_NO_SETAFFINITY = 0x04000000 + 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 + # /* this thread called freeze_processes and should not be frozen */ + PF_SUSPEND_TASK = 0x80000000 + + 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 + try: + self.load(basedir) + except FileNotFoundError: + # The file representing the pid has disappeared + # propagate the error to the user to handle + raise + + 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"): + try: + f = open(f"{basedir}/{self.pid}/stat") + except FileNotFoundError: + # The pid has disappeared, propagate the error + raise + 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 bool(self.fields["flags"] & self.PF_THREAD_BOUND) + + 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 + try: + return bool(int(self.processes[pid]["stat"]["flags"]) & + PF_NO_SETAFFINITY) + except: + return True + + +def cannot_set_thread_affinity(self, pid, tid): + PF_NO_SETAFFINITY = 0x04000000 + try: + return bool(int(self.processes[pid].threads[tid]["stat"]["flags"]) & + PF_NO_SETAFFINITY) + except: + 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 by 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 __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"): + self.fields = {} + with open(f"{basedir}/{self.pid}/status") as f: + 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 + + +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 + + try: + setattr(self, attr, sclass(self.pid, self.basedir)) + except FileNotFoundError: + # The pid has disappeared, progate the error + raise + 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): + try: + with open(f"/proc/{self.pid}/cmdline") as f: + self.cmdline = f.readline().strip().split('\0')[:-1] + except FileNotFoundError: + """ This can happen when a pid disappears """ + self.cmdline = None + except UnicodeDecodeError: + """ TODO - this shouldn't happen, needs to be investigated """ + self.cmdline = None + + def load_threads(self): + self.threads = pidstats(f"/proc/{self.pid}/task/") + # remove thread leader + del self.threads[self.pid] + + def load_cgroups(self): + self.cgroups = "" + with open(f"/proc/{self.pid}/cgroup") as f: + for line in reversed(f.readlines()): + if len(self.cgroups) != 0: + self.cgroups = self.cgroups + "," + line[:-1] + else: + self.cgroups = line[:-1] + + 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 = {} + with open(f"/proc/{self.pid}/environ") as f: + for x in f.readline().split('\0'): + if len(x) > 0: + y = x.split('=') + self.environ[y[0]] = y[1] + + +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 list(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 list(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 list(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 list(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 = f"{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 += f'{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 += f'{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 = {} + with open("/proc/interrupts") as f: + 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) + + 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: + with open(f"/proc/irq/{irq}/smp_affinity") as f: + line = f.readline() + 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 list(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 list(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." + + 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. + + 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 parse(self): + with open("/proc/cmdline") as f: + 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 __getitem__(self, key): + return self.options[key] + + def keys(self): + return list(self.options.keys()) + + def values(self): + return list(self.options.values()) + + def items(self): + return self.options + + +class cpuinfo: + """ + Dictionary with information about CPUs in the system. + + 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. + + Using this class one can obtain the number of CPUs in a system: + + >>> 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: + + >>> print len(cpus.sockets) + 1 + + Additionally dictionary with information common to all CPUs in the system + is available: + + >>> 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): + with open(filename) as f: + for line in f.readlines(): + line = line.strip() + if not line: + continue + fields = line.split(":") + tagname = fields[0].strip().lower() + if tagname == "processor": + self.nr_cpus += 1 + continue + if is_s390() and tagname == "cpu number": + self.nr_cpus += 1 + continue + if 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) + 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 + + +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. + + Example: Listing the executable maps for the 'sshd' process: + + >>> import procfs + >>> processes = procfs.pidstats() + >>> sshd = processes.find_by_name("sshd") + >>> sshd_maps = procfs.smaps(sshd[0]) + >>> for i in range(len(sshd_maps)): + ... if 'x' in sshd_maps[i].perms: + ... print "%s: %s" % (sshd_maps[i].name, sshd_maps[i].perms) + ... + /usr/sbin/sshd: r-xp + /usr/lib64/libnss_files-2.20.so: r-xp + /usr/lib64/librt-2.20.so: r-xp + /usr/lib64/libkeyutils.so.1.5: r-xp + /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): + line = None + with open(f"/proc/{self.pid}/smaps") as f: + while True: + line = self.parse_entry(f, line) + if not line: + break + 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 = f"< user: {self.user}, nice: {self.nice}, system: {self.system}, idle: {self.idle}, iowait: {self.iowait}, irq: {self.irq}, softirq: {self.softirq}" + if hasattr(self, 'steal'): + s += f", steal: {self.steal}" + if hasattr(self, 'guest'): + s += f", guest: {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 = {} + with open(self.filename) as f: + 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 + 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 list(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 + curr.usage = min(curr.usage, 100) + + +if __name__ == '__main__': + import sys + + ints = interrupts() + + for i in list(ints.interrupts.keys()): + print(f"{i}: {ints.interrupts[i]}") + + options = cmdline() + for o in list(options.options.keys()): + print(f"{o}: {options.options[o]}") + + cpu = cpuinfo() + print(f"\ncpuinfo data: {cpu.nr_cpus} processors") + for tag in list(cpu.keys()): + print(f"{tag}={cpu[tag]}") + + print("smaps:\n" + ("-" * 40)) + s = smaps(int(sys.argv[1])) + for i in range(s.nr_entries): + print(f"{s.entries[i].vm_start:#x} {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(f"{cpu}: {cs[cpu]}") + print("-" * 10) diff --git a/procfs/utilist.py b/procfs/utilist.py new file mode 100755 index 0000000..e6314f0 --- /dev/null +++ b/procfs/utilist.py @@ -0,0 +1,41 @@ +#! /usr/bin/python3 +# -*- python -*- +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2007 Red Hat, Inc. +# + +from six.moves import range + + +def hexbitmask(l, nr_entries): + hexbitmask = [] + bit = 0 + mask = 0 + for entry in range(nr_entries): + if entry in l: + mask |= (1 << bit) + bit += 1 + if bit == 32: + bit = 0 + hexbitmask.insert(0, mask) + mask = 0 + + if bit < 32 and mask != 0: + hexbitmask.insert(0, mask) + + return hexbitmask + +def bitmasklist(line, nr_entries): + hexmask = line.strip().replace(",", "") + bitmasklist = [] + entry = 0 + bitmask = bin(int(hexmask, 16))[2::] + for i in reversed(bitmask): + if int(i) & 1: + bitmasklist.append(entry) + entry += 1 + if entry == nr_entries: + break + return bitmasklist diff --git a/python_linux_procfs.egg-info/PKG-INFO b/python_linux_procfs.egg-info/PKG-INFO new file mode 100644 index 0000000..15f662b --- /dev/null +++ b/python_linux_procfs.egg-info/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 2.1 +Name: python-linux-procfs +Version: 0.7.3 +Summary: Linux /proc abstraction classes +Home-page: http://userweb.kernel.org/python-linux-procfs +Author: Arnaldo Carvalho de Melo +Author-email: acme@redhat.com +License: GPLv2 +License-File: COPYING + +Abstractions to extract information from the Linux kernel /proc files. diff --git a/python_linux_procfs.egg-info/SOURCES.txt b/python_linux_procfs.egg-info/SOURCES.txt new file mode 100644 index 0000000..119bebd --- /dev/null +++ b/python_linux_procfs.egg-info/SOURCES.txt @@ -0,0 +1,10 @@ +COPYING +pflags +setup.py +procfs/__init__.py +procfs/procfs.py +procfs/utilist.py +python_linux_procfs.egg-info/PKG-INFO +python_linux_procfs.egg-info/SOURCES.txt +python_linux_procfs.egg-info/dependency_links.txt +python_linux_procfs.egg-info/top_level.txt \ No newline at end of file diff --git a/python_linux_procfs.egg-info/dependency_links.txt b/python_linux_procfs.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/python_linux_procfs.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/python_linux_procfs.egg-info/top_level.txt b/python_linux_procfs.egg-info/top_level.txt new file mode 100644 index 0000000..8b71071 --- /dev/null +++ b/python_linux_procfs.egg-info/top_level.txt @@ -0,0 +1 @@ +procfs diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..144e07e --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-only + +import os +from os.path import isfile, relpath +import sysconfig +from setuptools import setup + +if isfile("MANIFEST"): + os.unlink("MANIFEST") + +SCHEME = 'rpm_prefix' +if not SCHEME in sysconfig.get_scheme_names(): + SCHEME = 'posix_prefix' + +# Get PYTHONLIB with no prefix so --prefix installs work. +PYTHONLIB = relpath(sysconfig.get_path('platlib', SCHEME), '/usr') + +setup(name="python-linux-procfs", + version = "0.7.3", + description = "Linux /proc abstraction classes", + author = "Arnaldo Carvalho de Melo", + author_email = "acme@redhat.com", + url = "http://userweb.kernel.org/python-linux-procfs", + license = "GPLv2", + long_description = +"""\ +Abstractions to extract information from the Linux kernel /proc files. +""", + packages = ["procfs"], + scripts = ['pflags'], + install_requires = ['six'], +)