Skip to content

Commit fa23d18

Browse files
committed
This commit include the source code of the tool named ext4file.
## Overview ext4file is used to monitor the I/O patterns (buffer or direct) of each file in the target ext4 filesystem, as well as the hint used by each file. ## Why ext4file ext4file is a tool used to track file-level buffer or direct I/O. Currently, in the repository, there exist block-layer tools to monitor I/O patterns of whole disk (such as biopattern, biolatency, etc.) and VFS-layer tools to trace file lifecycle and I/O behavior of files throughout the entire VFS (such as filelife and vfsstat, etc.). Below is a comparative summary: | Tool Name | Layer | Main Function | Tracks Filename? | Differences | |---------------|---------------------|---------------------------------------------------------------------------------------------------------------|------------------|------------------------| | biopattern | Block | Measures the proportion of random vs. sequential I/O on a storage device | No | Can not achieve file-layer tracing(The other bio tools all have this problem) | | fsdist | VFS | Tracks latency distribution of operations like read, write, open, and sync | No | Focus on latency, not I/O pattern | | fsslower | VFS | Traces slow file operations (e.g., long-latency reads/writes), focuses on I/O size | Yes| Trace the I/O size and latency, not I/O pattern | | filelife | VFS | Monitors file lifecycle events (creation and deletion) | Yes| Only focus on file creation and deletion | | filetop | VFS | Shows real-time I/O activity of active files (displays only top entries to avoid verbosity) | Yes| There exists no distinction between buffer and direct I/O | | ext4file | ext4 Filesystem | Tracks **buffer vs. direct I/O patterns** per file, enables fine-grained file-level monitoring using inode | Yes| | ## How to use Run ext4file before executing your test. You can refer to ./ext4file -h to get the usage of the tool 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: root@server:/home/nbw/OpenSource/biohint/libbpf-tools# ./ext4file -d /mnt/ext4File/ 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 ec8415b commit fa23d18

4 files changed

Lines changed: 547 additions & 0 deletions

File tree

libbpf-tools/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ APPS = \
5555
drsnoop \
5656
execsnoop \
5757
exitsnoop \
58+
ext4file \
5859
filelife \
5960
filetop \
6061
fsdist \

libbpf-tools/ext4file.bpf.c

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
// Copyright (c) 2026 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+
struct inode *pa_inode = dentry->d_parent->d_inode;
45+
dev_t dev_cur = pa_inode->i_sb->s_dev;
46+
u64 ino_id = inode->i_ino;
47+
struct file_info_key fik = {};
48+
struct file_info_val fiv = {};
49+
50+
if (dev_target && dev_target != dev_cur)
51+
return 0;
52+
53+
fik.fk_ino = ino_id;
54+
fik.fk_pa_ino = pa_inode->i_ino;
55+
bpf_probe_read_str(&fik.fk_name,
56+
sizeof(fik.fk_name), dentry->d_name.name);
57+
58+
if (bpf_map_update_elem(&file_info_map, &fik, &fiv, BPF_ANY))
59+
return 0;
60+
if (bpf_map_update_elem(&ino_name_map, &ino_id, &fik, BPF_ANY))
61+
return 0;
62+
return 0;
63+
}
64+
65+
SEC("tp_btf/ext4_unlink_enter")
66+
int BPF_PROG(my_ext4_unlink,
67+
struct inode *pa_inode, struct dentry *dentry)
68+
{
69+
struct inode *inode = dentry->d_inode;
70+
struct file_info_key fik = {}, *fikp;
71+
struct file_info_val *fivp = NULL;
72+
u64 ino_id = inode->i_ino;
73+
dev_t dev_cur = pa_inode->i_sb->s_dev;
74+
if (dev_target && dev_target != dev_cur)
75+
return 0;
76+
77+
fik.fk_ino = inode->i_ino;
78+
fik.fk_pa_ino = pa_inode->i_ino;
79+
bpf_probe_read_str(&fik.fk_name,
80+
sizeof(fik.fk_name), dentry->d_name.name);
81+
fivp = bpf_map_lookup_elem(&file_info_map, &fik);
82+
if (!fivp)
83+
return 0;
84+
fivp->fv_delete = true;
85+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
86+
if (!fikp)
87+
return 0;
88+
if (str_equal(fikp->fk_name, fik.fk_name))
89+
bpf_map_delete_elem(&ino_name_map, &ino_id);
90+
return 0;
91+
}
92+
93+
SEC("fentry/ext4_rmdir")
94+
int BPF_PROG(my_ext4_rmdir,
95+
struct inode *dir, struct dentry *dentry)
96+
{
97+
struct inode *inode = dentry->d_inode;
98+
struct file_info_key fik = {}, *fikp;
99+
struct file_info_val *fivp = NULL;
100+
u64 ino_id = inode->i_ino;
101+
dev_t dev_cur = inode->i_sb->s_dev;
102+
if (dev_target && dev_target != dev_cur)
103+
return 0;
104+
105+
fik.fk_ino = inode->i_ino;
106+
fik.fk_pa_ino = dir->i_ino;
107+
bpf_probe_read_str(&fik.fk_name,
108+
sizeof(fik.fk_name), dentry->d_name.name);
109+
fivp = bpf_map_lookup_elem(&file_info_map, &fik);
110+
if (!fivp)
111+
return 0;
112+
fivp->fv_delete = true;
113+
114+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
115+
if (!fikp)
116+
return 0;
117+
if (str_equal(fikp->fk_name, fik.fk_name))
118+
bpf_map_delete_elem(&ino_name_map, &ino_id);
119+
return 0;
120+
}
121+
122+
SEC("fentry/ext4_file_write_iter")
123+
int BPF_PROG(my_ext4_file_write_iter,
124+
struct kiocb *iocb, struct iov_iter *from)
125+
{
126+
struct inode *inode = iocb->ki_filp->f_inode;
127+
dev_t dev_cur = inode->i_sb->s_dev;
128+
struct file_info_key *fikp = NULL;
129+
struct file_info_val *fivp = NULL;
130+
u64 ino_id = inode->i_ino;
131+
132+
if (dev_target && dev_target != dev_cur)
133+
return 0;
134+
135+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
136+
if (!fikp)
137+
return 0;
138+
fivp = bpf_map_lookup_elem(&file_info_map, fikp);
139+
if (!fivp)
140+
return 0;
141+
142+
fivp->fv_hint = inode->i_write_hint;
143+
if(iocb->ki_flags & IOCB_DIRECT)
144+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_DIRECT_WRITE], 1);
145+
else
146+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_BUFFER_WRITE], 1);
147+
return 0;
148+
}
149+
150+
SEC("fentry/ext4_file_read_iter")
151+
int BPF_PROG(my_ext4_file_read_iter,
152+
struct kiocb *iocb, struct iov_iter *to)
153+
{
154+
struct file *file = iocb->ki_filp;
155+
struct inode *inode = file->f_inode;
156+
dev_t dev_cur = inode->i_sb->s_dev;
157+
struct file_info_key *fikp = NULL;
158+
struct file_info_val *fivp = NULL;
159+
u64 ino_id = inode->i_ino;
160+
if (dev_target && dev_target != dev_cur)
161+
return 0;
162+
163+
fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id);
164+
if (!fikp)
165+
return 0;
166+
fivp = bpf_map_lookup_elem(&file_info_map, fikp);
167+
if (!fivp)
168+
return 0;
169+
170+
if (iocb->ki_flags & IOCB_DIRECT)
171+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_DIRECT_READ], 1);
172+
else
173+
__sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_BUFFER_READ], 1);
174+
return 0;
175+
}
176+
177+
char LICENSE[] SEC("license") = "Dual BSD/GPL";

0 commit comments

Comments
 (0)