Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions tools/filetop.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@
import argparse
import os
import stat
import re
from subprocess import call

MAX_DENTRY_DEPTH_VAL = 32

# arguments
examples = """examples:
./filetop # file I/O top, 1 second refresh
./filetop -C # don't clear the screen
./filetop -p 181 # PID 181 only
./filetop -d /home/user # trace files in /home/user directory only
./filetop 5 # 5 second summaries
./filetop 5 10 # 5 second summaries, 10 times only
./filetop 5 --read-only # 5 second summaries, only read operations traced
./filetop 5 --write-only # 5 second summaries, only write operations traced
examples = f"""examples:
./filetop # file I/O top, 1 second refresh
./filetop -C # don't clear the screen
./filetop -p 181 # PID 181 only
./filetop -d /home/user # trace files in /home/user directory only
./filetop -d /home/user -R # trace files in /home/user and subdirectories (max depth {MAX_DENTRY_DEPTH_VAL})
./filetop 5 # 5 second summaries
./filetop 5 10 # 5 second summaries, 10 times only
./filetop 5 --read-only # 5 second summaries, only read operations traced
./filetop 5 --write-only # 5 second summaries, only write operations traced
"""
parser = argparse.ArgumentParser(
description="File reads and writes by process",
Expand Down Expand Up @@ -60,6 +64,8 @@
help=argparse.SUPPRESS)
parser.add_argument("-d", "--directory", type=str,
help="trace this directory only")
parser.add_argument("-R", "--recursive", action="store_true",
help=f"when used with -d, also trace files in subdirectories (max depth {MAX_DENTRY_DEPTH_VAL})")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to make it clearer that this is only available when using -d. The current wording makes it seem like there could be cases where it's possible to use without -d.
For example, "This feature is only supported when used with the -d option, tracing files inside subdirectories."


args = parser.parse_args()
interval = int(args.interval)
Expand All @@ -80,6 +86,28 @@
#undef DNAME_INLINE_LEN
#define DNAME_INLINE_LEN __BCC_DNAME_INLINE_LEN
// Limit dentry parent walk depth to satisfy eBPF verifier loop
#ifndef MAX_DENTRY_DEPTH
#define MAX_DENTRY_DEPTH MAX_DENTRY_DEPTH_VALUE
#endif
static __always_inline int not_under_inode(struct dentry *de, unsigned long target_ino)
{
struct dentry *pde = de;
int found = 0;
#pragma unroll
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a blank line after the variable declaration section.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is #pragma unroll (and __always_inline) intended?
please refer to:
https://lwn.net/Articles/1017116/

for (int i = 0; i < MAX_DENTRY_DEPTH; i++) {
if (!pde->d_parent)
break;
pde = pde->d_parent;
if (pde->d_inode && pde->d_inode->i_ino == target_ino) {
found = 1;
break;
}
}
return !found;
}
// the key for the output summary
struct info_t {
unsigned long inode;
Expand Down Expand Up @@ -169,6 +197,9 @@
}
"""
bpf_text = bpf_text.replace('MAX_DENTRY_DEPTH_VALUE', str(MAX_DENTRY_DEPTH_VAL))
m = re.search(r'#define\s+MAX_DENTRY_DEPTH\s+(\d+)', bpf_text)
MAX_DENTRY_DEPTH = int(m.group(1)) if m else 32
if args.tgid:
bpf_text = bpf_text.replace('TGID_FILTER', 'tgid != %d' % args.tgid)
else:
Expand All @@ -180,12 +211,24 @@
if args.directory:
try:
directory_inode = os.lstat(args.directory)[stat.ST_INO]
print(f'Tracing directory: {args.directory} (Inode: {directory_inode})')
bpf_text = bpf_text.replace('DIRECTORY_FILTER', 'file->f_path.dentry->d_parent->d_inode->i_ino != %d' % directory_inode)
if args.recursive:
print(
f'Tracing directory recursively: {args.directory} '
f'(Inode: {directory_inode}, max depth: {MAX_DENTRY_DEPTH_VAL})'
)
directory_filter = "not_under_inode(de, %d)" % directory_inode
else:
print(f'Tracing directory: {args.directory} (Inode: {directory_inode})')
directory_filter = "de->d_parent->d_inode->i_ino != %d" % directory_inode

bpf_text = bpf_text.replace('DIRECTORY_FILTER', directory_filter)
except (FileNotFoundError, PermissionError) as e:
print(f'Error accessing directory {args.directory}: {e}')
exit(1)
else:
if args.recursive:
print("Error: --recursive can only be used with -d/--directory option")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about -R/--recursive?

exit(1)
bpf_text = bpf_text.replace('DIRECTORY_FILTER', '0')

if debug or args.ebpf:
Expand Down