NAME
Proc::ProcessTable::piddler - Display all process table, open files, and network connections for a PID.
VERSION
Version 0.3.0
SYNOPSIS
use Proc::ProcessTable::piddler;
# skip over the less useful stuff for less spammy output
my $args={
txt=>0,
unix=>0,
pipe=>0,
fifo=>0,
vregroot=>0,
dont_dedup=>0,
dont_resolv=>0,
};
my $piddler = Proc::ProcessTable::piddler->new( $args );
print $piddler->run( [ 0, 1432 ] );
METHODS
new
Initiates the object.
One argument is taken and that is a option hash reference of options.
my $args={
txt=>0,
unix=>0,
pipe=>0,
fifo=>0,
vregroot=>0,
dont_dedup=>0,
dont_resolv=>0,
};
my $piddler = Proc::ProcessTable::piddler->new( $args );
args hash
a_inode
Print a_inode types.
Defaults to 0, false.
dont_dedup
Don't dedup the file descriptor list.
When deduping a list it checks if a file is open in rw, r, or w, only showing it once for any of those modes. Any file with more than one open FD of that mode will have + appended to the value in the FD column.
The modes below are all also RW and considered that.
u
ur
uw
Shared memory objects are rolled up as well, as per "SHARED MEMORY". A process may hold a great many that print the same, anonymous ones having nothing to tell them apart but their size and what else holds them, so only the first of them is printed, with the number left off tacked onto the FD.
40u+43 SHM 262.144k
Defaults to 0, false.
dont_resolv
Don't resolve PTR addresses.
Defaults to 0, false.
fifo
Print named pipes, which is to say the FIFOs sitting on the file system rather than the anonymous pipes pipe covers.
Defaults to 1, true.
human_size
Print the size of a open file as a readable value rather than the raw number of bytes, in the same manner as the process VSZ and RSS.
SIZE/OFF
823.640k
Only the SIZE/OFF values that are a size are touched, a offset being a position rather than a amount.
Defaults to 1, true.
jail_info
Print what the process has been shut away in under its own section, that being every parameter of the jail it is in on FreeBSD, as per "JAILS", and the cgroups and namespaces it is in on Linux, as per "CONTAINERS".
JAIL ARG VALUE
host.hostname test.example.org
jid 1
name test
path /jails/test
persist true
CONTAINER ARG VALUE
cgroup /system.slice/lldpd.service
ns:ipc 4026531839
ns:mnt 4026532643 private
ns:net 4026531833
Nothing is printed for a process that is not in a jail, the cgroups and namespaces being there to print either way.
Defaults to 0, false.
memreglib
Print memory mapped libraries that are of the type REG.
The following are used to match libraries.
/\.so$/
/\.so\.[0-9]+$/
/\.so\.[0-9]+\.[0-9]+$/
/\.so\.[0-9]+\.[0-9]+\.[0-9]+$/
/\.jar$/
Defaults to 0, false.
peer_max
How many commands to print for the far end of a pipe, FIFO, unix socket, or shared memory object and how many PIDs to print for any one of those commands.
A endpoint may be held by any number of other processes, such as a shared memory object inherited across a pile of forks, so this is what keeps that from being more than is worth reading through. Whatever is left off is noted as a count.
17u SHM 288876 0 (firefox -contentproc...(7375, 24844, + 26 more))
Zero or less prints all of them.
Defaults to 0, printing all of them.
peers
For each pipe, FIFO, unix socket, and shared memory object printed, show the command holding the far end of it.
FD TYPE DEVICE SIZE/OFF NODE NAME
7u unix 0xfffff80022630400 0 ->0xfffff80022635000 (dbus-daemon --session(51092))
14u unix 0xfffff80052dee800 0 /tmp/dbus-nWRW4XDDoD (xfce4-panel(63471))
3r FIFO 0xe6 0t0 299839014 /tmp/testfifo (cat(12593))
17u SHM 288876 0 (firefox -contentproc...(7375, 24844, 32640, + 26 more))
The far end is found either by way of the endpoint this one points at, by way of whatever points at this one, or by way of what else has the same one open. The second is what covers the unix sockets lsof names after the path they are bound to, such as the accepted end of a connection, and the third the FIFOs, shared memory objects, and pipes that both ends hold the same object for.
Commands longer than 40 characters are truncated. A endpoint whose far end can not be looked up, such as one held by another user's process when not running as root, is shown as a ?. Nothing is shown for one that has no far end to speak of, such as a unix socket that is only bound and listening or a FIFO no one else has open.
The process itself is never named as the far end of its own entry. It is always one of the holders on systems that tie the two ends together via the node, as Linux does, and on the ones that hand each end a object of its own it comes up for the pipe or socket pair a process made for itself, where the two rows pointing at each other are the whole of what there is to say.
63u PIPE 0xfffffe016dcb5cc0 16384 ->0xfffffe016dcb5e18
64u PIPE 0xfffffe016dcb5e18 4096 ->0xfffffe016dcb5cc0
A endpoint may be held by any number of other processes, such as a shared memory object inherited across a pile of forks, so the PIDs are gathered up under the command they are running, with peer_max capping how many of each are printed.
Tying the two ends together requires a system wide lsof, so it is only run when a pipe, FIFO, unix socket, or shared memory object is actually going to be printed, and only once per call to run.
Unix sockets are tied together either by way of a lsof that points at the far end of one, as FreeBSD does, or by way of lsof +E, which is what is used on Linux. A build of lsof with no +E to it just leaves them without a far end to speak of, everything else carrying on as it was.
Defaults to 1, true.
pipe
Print anonymous pipes, the sort a shell makes when it strings two commands together.
Linux hands these the FIFO type rather than one of their own, naming them pipe instead of after a path the way a named one is, which is what tells the two apart there.
Defaults to 1, true.
pipe_chain_command_length
How long a command may be in a pipe chain before it is truncated, with ... tacked onto the end of it to show that it was.
ps auxw(4821) | grep -i --line-buffered --color=auto...(4822)
Zero or less does not truncate at all.
Only the pipe chains are touched, the commands shown for the far end of a endpoint having a length of their own.
Defaults to 0, not truncating at all.
pipe_chains
Print the pipelines the process is a part of, showing the commands in the order the data flows through them, oldest to newest.
PIPE CHAINS
ps auxw(4821) | grep foo(4822) | wc -l(4823)
Commands are truncated as per pipe_chain_command_length. Any process that can not be looked up, such as one belonging to another user when not running as root, is shown as a ?. A process may sit on more than one pipe, so at most 16 lines are shown for any one of them.
A pipe is a link in a pipeline where a process that was handed it is writing at the one end and another is reading at the other, that being what a shell makes when it strings two commands together. More than one of them on a end, such as a log a whole pack of workers writes to, is a link apiece rather than the one. Two processes each running a pipe at the other are talking both ways, which is the one channel between them rather than two pipelines pointed opposite ways.
firefox(7375) <-> firefox -contentproc...(24844)
Anything else is left as the one pipe it is, printed as whatever is writing to it against whatever is reading, rather than being strung into a pipeline it is not. A worker pool sharing a queue out among its workers is the usual sort.
python process.py(8007) | python process.py(8007), inherited(94645, 94646, + 5 more)
A process forked off of one already holding a pipe is handed a copy of it whether it has any use for it or not, and a pool that forks a worker at a time ends up with every worker holding a end of every pipe made before it. Those are printed as inherited so they are not taken for something that was given the pipe to use. A end sitting on stdin or stdout counts as given either way, a shell wiring the last command of a pipeline onto the very descriptor it is holding the pipe on itself, so leaving those to the parent would break the chain a hop early. Stderr is left out of that, as it is the one every child carries off of its parent whether it has any use for it or not.
Tying the two ends of a pipe together requires a system wide lsof, so it is only run for processes that actually have a pipe open, and only once per call to run.
The direction of a pipe is taken from the r and w access characters when lsof reports them. Systems such as FreeBSD report pipes as being open read/write instead, in which case the descriptor number is used, 0 being the input and 1 and 2 being the output.
Those systems also hand each end of a pipe a object of its own and point the two at each other, so a end told by a descriptor in the one process is that same end everywhere else it is held, and the far end of it goes the other way. That is what puts a direction on the ends only ever held on a descriptor there is nothing to be read off of, such as the stdin, stdout, and stderr a supervisor keeps of everything it started.
galla --name mail(96264) <-> baphomet start(95432)
A pipe a process made for itself never lands on a standard descriptor, so on FreeBSD the buffer sitting behind either end is what is left to tell the two apart. The read end is handed the one everything written to the pipe lands in, which starts out at 16k and is grown to 64k for one that is being pushed, against the 4k the write end is handed and never uses, so the larger of the two is the end being read from. Both are made at the smaller size when the kernel is low on room for them, in which case there is nothing to go on.
A pipe with nothing on either side of it to go on is left out of the chains, as are both ends of one with descriptors going both ways. So is one with nothing on either end of it but the process itself, that being the same command printed twice and no more than the open files already say, which something like a browser holds a pile of.
Defaults to 1, true.
txt
Print the linked libraries used by the binary.
Defaults to 0, false.
unix
Print unix sockets.
Defaults to 1, true.
vregroot
Show VREG entries for /.
Defaults to 0, false.
run
This runs it and returns a string.
One option is taken and that is a array ref of PIDs to do.
print $piddler->run( [ 0, 1432 ] );
timeString
Turns the raw run string into something usable.
memString
Turns the raw run string into something usable.
startString
Generates a short time string based on the supplied unix time.
SHARED MEMORY
The total size of the shared memory a process holds is shown with the rest of the memory bits. A object held on more than one FD is counted the once, the object being what takes up the memory rather than the handle on it.
Total SHM 1.076M
FreeBSD and the like hand these a SHM type of their own. Linux has no such type, giving them the same REG every other file gets, so there they are picked out by name instead.
/dev/shm/* POSIX objects, which live on a tmpfs
/SYSV* SysV segments, which are named after their key
/memfd:* the anonymous ones made by memfd_create
The TYPE column is whatever lsof called it either way, so these read as REG on Linux rather than SHM.
Every one of them is deduped and has its peers looked up in the same manner as any other, so what else holds a object is shown for it as per "peers" and the ones that print the same are rolled up as per "dont_dedup".
JAILS
On FreeBSD, any process with a JID other than 0 has the jail it is in looked up, showing the name of it with the number after it in the same manner as the UID and GID.
jid test(1) test.example.org /jails/test
The hostname and path are only tacked on when they have anything to add, the first being more often than not just the name over again and the second nothing worth mentioning for a jail sharing the file system it was started from. Just the number is shown for a jail that can not be looked up, such as one that has gone away since the process table was read.
jail_info adds a section with every parameter of the jail in it, which is the lot of what jls reports for one.
JAIL ARG VALUE
host.hostname test.example.org
jid 1
name test
path /jails/test
persist true
The lookup takes a run of jls, which is only done for a process that is in a jail, and only once per jail no matter how many PIDs are given.
CONTAINERS
Linux has no jails, shutting a process away with cgroups and namespaces instead, so that is what is shown in their place there.
The cgroup a process is in is shown with the rest of its info, that being what names the systemd unit or the container it belongs to. Nothing is shown for one left sitting in the root cgroup it started in, such as a kernel thread.
Cgroup /system.slice/postgresql@17-main.service
Every namespace it does not share with PID 1 is shown as well, those being what it has been shut away from the rest of the system in. A process sharing the lot of them has nothing to say here.
Namespaces mnt, net, pid, uts
When the cgroup names a container, the runtime that started it is shown with the ID after it, in the same manner as the UID and GID. The long hash a runtime hands a container is cut down to the first dozen characters, which is how everything else prints them.
Container docker(3f2a1b8c9d4e)
The runtimes below are the ones picked out. Anything else still has its cgroup and namespaces shown, just with no name put to it.
docker
podman
containerd
crio
lxc
machine systemd-nspawn and the like
CGROUP LIMITS
What the cgroup is using of what it is allowed is shown along with it, each line being left off when it has nothing to say, be it a limit that was never set or a controller that was never turned on for it.
Cgroup mem 98.304k / 33.554M peak 33.554M
Cgroup cpu quota 0.1, used 26, throttled 2604 for 4:05
Cgroup pids 1 / 50
Cgroup events oom_kill 1, oom 1, max 879
Cgroup pressure cpu 74.00/77.31, memory 0.15/2.73
All of it covers the whole cgroup rather than the one process, so a unit that started a dozen of them reports what the lot are using between them. Cgroup mem takes in the page cache on top of the anonymous memory as well, so it is not the RSS over again and will read higher than it for anything that has been through a pile of files.
The limit is only shown when there is one, memory.max falling back to the softer memory.high with high after it. Cgroup cpu is only shown for a cgroup that is either capped or being held back, the time it has used saying no more than the process table already does otherwise, and its quota is the share of a single core it works out to. Cgroup events and Cgroup pressure only turn up when something actually happened, everything sitting at zero having nothing to report.
None of it is read for a process in the root cgroup, which has nothing of its own to report, nor for one whose cgroup can not be found under the mount point, that being what a process in a cgroup namespace of its own looks like from outside of it. Reading the root for one of those would hand back the whole system's numbers as though they belonged to the process.
Only the v2 hierarchy is read. A system still running v1 alone has its cgroup paths shown as ever, just with none of this alongside them.
jail_info
jail_info adds a section with every cgroup and namespace in it, the ones not shared with PID 1 being called out, followed by the knobs of the cgroup itself and what else is sitting in it.
CONTAINER ARG VALUE
cgroup /piddler-test
ns:mnt 4026532643 private
ns:net 4026531833
cgroup.controllers cpuset cpu io memory pids
cgroup.procs 1 sh -c while :; do :; done(369709)
memory.current 90.112k
memory.max 33.554M
pids.max 50
cpu.max 10000 100000
cpu.stat:nr_throttled 2829
memory.events:oom_kill 1
cpu.pressure:some avg10=85.84 avg60=79.75 avg300=47.38
cgroup.procs is everything sitting in the cgroup, gathered up under the command each of them is running and capped in the same manner as the peers of a endpoint, as per "peer_max". cgroup.controllers is what says why any of the rest are missing, a knob only being there for a controller that was turned on.
The memory.stat lines break memory.current down into where it actually went, which is what says how much of a cgroup is its own anonymous memory and how much is page cache it has read through, the two often being nothing alike.
memory.current 40.747M
memory.stat:anon 3.871M
memory.stat:file 29.131M
memory.stat:shmem 12.263M
There are a great many of these, so only the handful worth reading through are printed, and only the ones with something in them at that. memory.stat:shmem is all the tmpfs and shared memory charged to the cgroup, which is not the Total SHM shown with the process info, that being the shared memory objects the one process has open as per "SHARED MEMORY".
All of it is read from /proc and the cgroup mount, so there is nothing to run for it the way there is for a jail. The namespaces of a process belonging to another user are kept from everyone but root, so those go unshown when it is not root doing the asking.
AUTHOR
Zane C. Bowers-Hadley, <vvelox at vvelox.net>
BUGS
Please report any bugs or feature requests to bug-proc-processtable-piddler at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Proc-ProcessTable-piddler. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Proc::ProcessTable::piddler
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
https://rt.cpan.org/NoAuth/Bugs.html?Dist=Proc-ProcessTable-piddler
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
Repository
LICENSE AND COPYRIGHT
This software is Copyright (c) 2019 by Zane C. Bowers-Hadley.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)