Skip to content

Commit 7aa459e

Browse files
committed
This commit include the source code of the tool. the usage is shown as below:
Show I/O pattern for every file in ext4 filesystem. Usage: ./ext4file [-h] [-d DIR] [-o FILE] [interval] [count] Options: -h, --help Print this help message -d DIR, --dir=DIR Trace the ext4 filesystem mounted on the specified directory -o FILE, --output=FILE Write output to a file (optional; default: stdout) interval Time interval (in seconds) between reports (default: unlimited) count Number of reports to generate (default: unlimited) Examples: ./ext4file -d /mnt/ext4 # Trace I/O patterns of files on the ext4 filesystem mounted at /mnt/ext4 ./ext4file -d /mnt/ext4 1 10 # Generate 10 reports, one per second ./ext4file -d /mnt/ext4 -o output 1 10 # Generate 10 reports at 1-second intervals, saving output to ./output The output could be: EXT4 Filesystem Info: blocks_count=3750232064 blocks_per_group=32768 bg_cnt=114448 Tracing Ext4 read/write... Hit Ctrl-C to end. 2026-01-14 13:58:21 file_name inode pa_inode hint buffer_read direct_read buffer_write direct_write delete test3 83361794 83361793 0 0 0 0 0 False test2 34 2 2 8 0 1 0 False dir1 83361793 2 0 0 0 0 0 False dir2 440467457 2 0 0 0 0 0 True test3 33 2 3 8 0 1 0 False test1 33 2 5 8 0 1 0 True test3 440467458 440467457 0 0 0 0 0 True Below is the detailed explanation of each field in the ext4file output. This tool traces per-file I/O patterns (buffered vs. direct) on ext4 filesystems, providing fine-grained visibility into application behavior. | Field | Description | |---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | file_name | The name of the file (without full path). Note: multiple files may share the same name. | | inode | The inode number of the file. Inode is the unique identifier for a file within the filesystem, even across renames or hard links. This enables accurate tracking of I/O for specific files. | | pa_inode | The inode number of the file’s parent directory. | | hint | The FDP (Flexible Data Placement) hint value associated with the file. FDP is a new NVMe feature that enables the host to guide data placement on the SSD. | | buffer_read | Number of buffered read operations performed on the file. Buffered I/O goes through the kernel page cache. | | direct_read | Number of direct read operations performed on the file. Direct I/O bypasses the page cache. | | direct_write | Number of direct write operations performed on the file. Like direct read, it skips the page cache and writes data directly from user space to storage. | | buffer_write | Number of buffered write operations performed on the file. Data is first written to the page cache and later flushed to disk asynchronously by the kernel. | | delete | Indicates whether the file has been unlinked (deleted). If True, the file was removed from the directory but may still be accessible if held open by a process. I/O on such files can indicate resource leaks or long-running file handles. | Target Audience: This tool is intended for ext4 filesystem developers and performance engineers who need to analyze I/O behavior at the file level.
1 parent 19b09b4 commit 7aa459e

208 files changed

Lines changed: 41838 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

libbpf-tools/ext4file.bpf.c

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
// Copyright (c) 2025 Samsung Electronics Co., Ltd.
3+
#include <vmlinux.h>
4+
#include <bpf/bpf_helpers.h>
5+
#include <bpf/bpf_tracing.h>
6+
#include <bpf/bpf_core_read.h>
7+
8+
#include "ext4file.h"
9+
10+
volatile __u64 dev_target = 0;
11+
volatile __u64 blocks_per_group = 0;
12+
13+
#define N 16
14+
#define GROUP 64
15+
16+
struct {
17+
__uint(type, BPF_MAP_TYPE_HASH);
18+
__uint(max_entries, 1000000);
19+
__type(key, u32);
20+
__type(value, struct file_info_key);
21+
} ino_name_map SEC(".maps");
22+
23+
struct {
24+
__uint(type, BPF_MAP_TYPE_HASH);
25+
__uint(max_entries, 1000000);
26+
__type(key, struct file_info_key);
27+
__type(value, struct file_info_val);
28+
} file_info_map SEC(".maps");
29+
30+
static __always_inline bool str_equal(const char *a, const char *b) {
31+
for (size_t i = 0; i < MAX_FILE_NAME; i++) {
32+
if (a[i] == '\0' && b[i] == '\0')
33+
return true;
34+
if (a[i] != b[i])
35+
return false;
36+
}
37+
return true;
38+
}
39+
40+
SEC("fexit/ext4_add_entry")
41+
int BPF_PROG(my_ext4_add_entry, handle_t* handle,
42+
struct dentry* dentry, struct inode* inode)
43+
{
44+
bpf_printk("ext4_add_entry");
45+
struct inode* pa_inode = dentry->d_parent->d_inode;
46+
dev_t dev_cur = pa_inode->i_sb->s_dev;
47+
u64 ino_id = inode->i_ino;
48+
struct file_info_key fik = {};
49+
struct file_info_val fiv = {};
50+
51+
if (dev_target && dev_target != dev_cur)
52+
return 0;
53+
54+
fik.fk_ino = ino_id;
55+
fik.fk_pa_ino = pa_inode->i_ino;
56+
bpf_probe_read_str(&fik.fk_name,
57+
sizeof(fik.fk_name), dentry->d_name.name);
58+
59+
if (bpf_map_update_elem(&file_info_map, &fik, &fiv, BPF_ANY))
60+
bpf_printk("failed to update file_info_map\n");
61+
if (bpf_map_update_elem(&ino_name_map, &ino_id, &fik, BPF_ANY))
62+
bpf_printk("failed to update ino_name_map\n");
63+
bpf_printk("the file(%s %u %u) inserted", fik.fk_name, fik.fk_ino, fik.fk_pa_ino);
64+
return 0;
65+
}
66+
67+
SEC("tp_btf/ext4_unlink_enter")
68+
int BPF_PROG(my_ext4_unlink,
69+
struct inode * pa_inode, struct dentry *dentry)
70+
{
71+
bpf_printk("ext4_unlink_enter");
72+
struct inode* inode = dentry->d_inode;
73+
struct file_info_key fik = {}, *fikp;
74+
struct file_info_val* fivp = NULL;
75+
u64 ino_id = inode->i_ino;
76+
dev_t dev_cur = pa_inode->i_sb->s_dev;
77+
if (dev_target && dev_target != dev_cur)
78+
return 0;
79+
80+
fik.fk_ino = inode->i_ino;
81+
fik.fk_pa_ino = pa_inode->i_ino;
82+
bpf_probe_read_str(&fik.fk_name,
83+
sizeof(fik.fk_name), dentry->d_name.name);
84+
fivp = bpf_map_lookup_elem(&file_info_map, &fik);
85+
if (!fivp) {
86+
bpf_printk("the file(%s %u %u) is not in monitor", fik.fk_name, fik.fk_ino, fik.fk_pa_ino);
87+
return 0;
88+
}
89+
fivp->fv_delete = true;
90+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
91+
if (!fikp) {
92+
return 0;
93+
}
94+
if (str_equal(fikp->fk_name, fik.fk_name))
95+
bpf_map_delete_elem(&ino_name_map, &ino_id);
96+
return 0;
97+
}
98+
99+
SEC("fentry/ext4_rmdir")
100+
int BPF_PROG(my_ext4_rmdir,
101+
struct inode* dir, struct dentry* dentry)
102+
{
103+
struct inode* inode = dentry->d_inode;
104+
struct file_info_key fik = {}, *fikp;
105+
struct file_info_val* fivp = NULL;
106+
u64 ino_id = inode->i_ino;
107+
dev_t dev_cur = inode->i_sb->s_dev;
108+
if (dev_target && dev_target != dev_cur)
109+
return 0;
110+
111+
fik.fk_ino = inode->i_ino;
112+
fik.fk_pa_ino = dir->i_ino;
113+
bpf_probe_read_str(&fik.fk_name,
114+
sizeof(fik.fk_name), dentry->d_name.name);
115+
fivp = bpf_map_lookup_elem(&file_info_map, &fik);
116+
if (!fivp) {
117+
bpf_printk("the file(%s) is not in monitor", fik.fk_name);
118+
return 0;
119+
}
120+
fivp->fv_delete = true;
121+
122+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
123+
if (!fikp) {
124+
return 0;
125+
}
126+
if (str_equal(fikp->fk_name, fik.fk_name))
127+
bpf_map_delete_elem(&ino_name_map, &ino_id);
128+
return 0;
129+
}
130+
131+
SEC("fentry/ext4_file_write_iter")
132+
int BPF_PROG(my_ext4_file_write_iter,
133+
struct kiocb *iocb, struct iov_iter *from)
134+
{
135+
struct inode* inode = iocb->ki_filp->f_inode;
136+
dev_t dev_cur = inode->i_sb->s_dev;
137+
struct file_info_key* fikp = NULL;
138+
struct file_info_val* fivp = NULL;
139+
u64 ino_id = inode->i_ino;
140+
141+
if (dev_target && dev_target != dev_cur)
142+
return 0;
143+
144+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
145+
if (!fikp) {
146+
bpf_printk("fail to find fikp: %d", ino_id);
147+
return 0;
148+
}
149+
fivp = bpf_map_lookup_elem(&file_info_map, fikp);
150+
if (!fivp) {
151+
bpf_printk("failed to lookup file_info_map\n");
152+
return 0;
153+
}
154+
155+
fivp->fv_hint = inode->i_write_hint;
156+
if(iocb->ki_flags & IOCB_DIRECT)
157+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_DIRECT_WRITE], 1);
158+
else
159+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_BUFFER_WRITE], 1);
160+
return 0;
161+
}
162+
163+
SEC("fentry/ext4_file_read_iter")
164+
int BPF_PROG(my_ext4_file_read_iter,
165+
struct kiocb *iocb, struct iov_iter *to)
166+
{
167+
//bpf_printk("ext4_file_read_iter\n");
168+
struct file* file = iocb->ki_filp;
169+
struct inode* inode = file->f_inode;
170+
dev_t dev_cur = inode->i_sb->s_dev;
171+
struct file_info_key* fikp = NULL;
172+
struct file_info_val* fivp = NULL;
173+
u64 ino_id = inode->i_ino;
174+
//bpf_printk("device_num:%d dev:%d\n", device_num, dev);
175+
if (dev_target && dev_target != dev_cur)
176+
return 0;
177+
178+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
179+
if (!fikp) {
180+
bpf_printk("fail to find fikp: %d", ino_id);
181+
return 0;
182+
}
183+
fivp = bpf_map_lookup_elem(&file_info_map, fikp);
184+
if (!fivp) {
185+
bpf_printk("failed to lookup file_info_map\n");
186+
return 0;
187+
}
188+
189+
if (iocb->ki_flags & IOCB_DIRECT)
190+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_DIRECT_READ], 1);
191+
else
192+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_BUFFER_READ], 1);
193+
return 0;
194+
}
195+
196+
char LICENSE[] SEC("license") = "Dual BSD/GPL";

0 commit comments

Comments
 (0)