Skip to content

Instantly share code, notes, and snippets.

@d3dave
Last active April 6, 2023 21:57
Show Gist options
  • Save d3dave/8f3f18a1c3bacb8ce94809abaeccd29d to your computer and use it in GitHub Desktop.
Save d3dave/8f3f18a1c3bacb8ce94809abaeccd29d to your computer and use it in GitHub Desktop.
Track process exits using acct(2)
#!/usr/bin/env python3
import os
import time
import signal
st = time.time()
N = 1000000
n = 0
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
while n < N:
n += 1
pid = os.fork()
if pid == 0:
break
et = time.time()
if n % 1000 == 0:
print(n / (et - st), 'procs / s')
#!/usr/bin/env python3
import enum
import os
import asyncio
import ctypes
from termcolor import cprint
ACCT_COMM = 16
comp_t = ctypes.c_uint16
'''
comp_t is a 16-bit "floating" point number with a 3-bit base 8
exponent and a 13-bit fraction. See linux/kernel/acct.c for the
specific encoding system used.
'''
class acct_v3(ctypes.Structure):
_fields_ = [
('ac_flag', ctypes.c_char),
('ac_version', ctypes.c_char),
('ac_tty', ctypes.c_uint16),
('ac_exitcode', ctypes.c_int32),
('ac_uid', ctypes.c_int32),
('ac_gid', ctypes.c_int32),
('ac_pid', ctypes.c_int32),
('ac_ppid', ctypes.c_int32),
('ac_btime', ctypes.c_int32),
('ac_etime', ctypes.c_float),
('ac_utime', comp_t),
('ac_stime', comp_t),
('ac_mem', comp_t),
('ac_io', comp_t),
('ac_rw', comp_t),
('ac_minflt', comp_t),
('ac_majflt', comp_t),
('ac_swaps', comp_t),
('ac_comm', ctypes.c_char * ACCT_COMM),
]
class acct_flag(enum.IntFlag):
AFORK = 0x01
ASU = 0x02
ACORE = 0x08
AXSIG = 0x10
def check_errno(result, func, args):
if result < 0:
errno = ctypes.get_errno()
raise OSError(f'{func.__name__}: [Errno {errno}] {os.strerror(errno)}')
libc = ctypes.CDLL('libc.so.6', use_errno=True)
libc.acct.errcheck = check_errno
libc.acct.restype = ctypes.c_int
libc.acct.argtypes = [ctypes.c_char_p]
def acct(filename):
if filename is None:
libc.acct(None)
return
# touch the file
os.close(os.open(filename, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o400))
libc.acct(filename.encode())
def snapshot(filename):
snapshot_filename = f'{filename}.snapshot'
os.rename(filename, snapshot_filename)
acct(filename)
data = open(snapshot_filename, 'rb').read()
os.unlink(snapshot_filename)
return data
def parse(data):
sz = ctypes.sizeof(acct_v3)
assert len(data) % sz == 0
count = len(data) // sz
entries_ptr = ctypes.cast(data, ctypes.POINTER(acct_v3 * count))
return list(entries_ptr.contents)
def dump_entry(e):
flags = acct_flag(ord(e.ac_flag))
if flags & acct_flag.AFORK:
return
assert ord(e.ac_version) == 3, f'expected version 3, got {ord(e.ac_version)}'
comm = e.ac_comm.decode()
print(f'{e.ac_pid:-7} {comm:16} uid:{e.ac_uid} gid:{e.ac_gid}')
print(f'{" ":7}', end=' ')
exit_code = e.ac_exitcode
if os.WIFEXITED(exit_code):
print(f'exited with status {os.WEXITSTATUS(exit_code)}')
elif os.WIFSTOPPED(exit_code):
print(f'stopped by signal {os.WSTOPSIG(exit_code)}')
elif os.WIFSIGNALED(exit_code):
print(f'terminated by signal {os.WTERMSIG(exit_code)}')
elif os.WIFCONTINUED(exit_code):
print('continued')
strs = []
if flags & acct_flag.AXSIG:
strs.append('killed by signal')
if flags & acct_flag.ACORE:
strs.append('core dumped')
if flags & acct_flag.ASU:
strs.append('used superuser privileges')
print(', '.join(strs))
def dump(entries):
for e in entries:
dump_entry(e)
async def loop(filename):
try:
while True:
await asyncio.sleep(2)
data = snapshot(filename)
entries = parse(data)
dump(entries)
except Exception as e:
cprint(str(e), 'red')
def main():
filename = 'psacct'
acct(filename)
try:
asyncio.run(loop(filename))
except KeyboardInterrupt:
acct(None)
os.unlink(filename)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment