diff --git a/libbpf-tools/Makefile b/libbpf-tools/Makefile index 66183659828d..90da35335c67 100644 --- a/libbpf-tools/Makefile +++ b/libbpf-tools/Makefile @@ -9,7 +9,7 @@ LIBBPF_SRC := $(abspath ../src/cc/libbpf/src) LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) LIBBLAZESYM_SRC := $(abspath blazesym/target/release/libblazesym.a) INCLUDES := -I$(OUTPUT) -I../src/cc/libbpf/include/uapi -CFLAGS := -g -O2 -Wall -Wmissing-field-initializers -Werror -Werror=undef +CFLAGS := -g -O2 -lm -Wall -Wmissing-field-initializers -Werror -Werror=undef BPFCFLAGS := -g -O2 -Wall -Werror=undef BPFCFLAGS_softirqs := $(BPFCFLAGS) -mcpu=v3 INSTALL ?= install @@ -94,6 +94,7 @@ APPS = \ tcptop \ vfsstat \ wakeuptime \ + ext4File \ $(BZ_APPS) \ # @@ -120,7 +121,6 @@ COMMON_OBJ = \ ifeq ($(USE_BLAZESYM),1) COMMON_OBJ += \ $(OUTPUT)/libblazesym.a \ - $(OUTPUT)/blazesym.h \ # endif @@ -156,7 +156,7 @@ CFLAGS += -DUSE_BLAZESYM=1 endif ifeq ($(USE_BLAZESYM),1) -LDFLAGS += $(OUTPUT)/libblazesym.a -lrt -lpthread -ldl +LDFLAGS += -lrt -lpthread -ldl endif .PHONY: clean diff --git a/libbpf-tools/ext4File.bpf.c b/libbpf-tools/ext4File.bpf.c new file mode 100644 index 000000000000..bcba4db823f3 --- /dev/null +++ b/libbpf-tools/ext4File.bpf.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +#include +#include +#include +#include + +#include "ext4file.h" + +volatile __u64 dev_target = 0; +volatile __u64 blocks_per_group = 0; + +#define N 16 +#define GROUP 64 + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1000000); + __type(key, u32); + __type(value, struct file_info_key); +} ino_name_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1000000); + __type(key, struct file_info_key); + __type(value, struct file_info_val); +} file_info_map SEC(".maps"); + +static __always_inline bool str_equal(const char *a, const char *b) { + for (size_t i = 0; i < MAX_FILE_NAME; i++) { + if (a[i] == '\0' && b[i] == '\0') + return true; + if (a[i] != b[i]) + return false; + } + return true; +} + +SEC("fexit/ext4_add_entry") +int BPF_PROG(my_ext4_add_entry, handle_t* handle, + struct dentry* dentry, struct inode* inode) +{ + bpf_printk("ext4_add_entry"); + struct inode* pa_inode = dentry->d_parent->d_inode; + dev_t dev_cur = pa_inode->i_sb->s_dev; + u64 ino_id = inode->i_ino; + struct file_info_key fik = {}; + struct file_info_val fiv = {}; + + if (dev_target && dev_target != dev_cur) + return 0; + + fik.fk_ino = ino_id; + fik.fk_pa_ino = pa_inode->i_ino; + bpf_probe_read_str(&fik.fk_name, + sizeof(fik.fk_name), dentry->d_name.name); + + if (bpf_map_update_elem(&file_info_map, &fik, &fiv, BPF_ANY)) + bpf_printk("failed to update file_info_map\n"); + if (bpf_map_update_elem(&ino_name_map, &ino_id, &fik, BPF_ANY)) + bpf_printk("failed to update ino_name_map\n"); + bpf_printk("the file(%s %u %u) inserted", fik.fk_name, fik.fk_ino, fik.fk_pa_ino); + return 0; +} + +SEC("tp_btf/ext4_unlink_enter") +int BPF_PROG(my_ext4_unlink, + struct inode * pa_inode, struct dentry *dentry) +{ + bpf_printk("ext4_unlink_enter"); + struct inode* inode = dentry->d_inode; + struct file_info_key fik = {}, *fikp; + struct file_info_val* fivp = NULL; + u64 ino_id = inode->i_ino; + dev_t dev_cur = pa_inode->i_sb->s_dev; + if (dev_target && dev_target != dev_cur) + return 0; + + fik.fk_ino = inode->i_ino; + fik.fk_pa_ino = pa_inode->i_ino; + bpf_probe_read_str(&fik.fk_name, + sizeof(fik.fk_name), dentry->d_name.name); + fivp = bpf_map_lookup_elem(&file_info_map, &fik); + if (!fivp) { + bpf_printk("the file(%s %u %u) is not in monitor", fik.fk_name, fik.fk_ino, fik.fk_pa_ino); + return 0; + } + fivp->fv_delete = true; + fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id); + if (!fikp) { + return 0; + } + if (str_equal(fikp->fk_name, fik.fk_name)) + bpf_map_delete_elem(&ino_name_map, &ino_id); + return 0; +} + +SEC("fentry/ext4_rmdir") +int BPF_PROG(my_ext4_rmdir, + struct inode* dir, struct dentry* dentry) +{ + struct inode* inode = dentry->d_inode; + struct file_info_key fik = {}, *fikp; + struct file_info_val* fivp = NULL; + u64 ino_id = inode->i_ino; + dev_t dev_cur = inode->i_sb->s_dev; + if (dev_target && dev_target != dev_cur) + return 0; + + fik.fk_ino = inode->i_ino; + fik.fk_pa_ino = dir->i_ino; + bpf_probe_read_str(&fik.fk_name, + sizeof(fik.fk_name), dentry->d_name.name); + fivp = bpf_map_lookup_elem(&file_info_map, &fik); + if (!fivp) { + bpf_printk("the file(%s) is not in monitor", fik.fk_name); + return 0; + } + fivp->fv_delete = true; + + fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id); + if (!fikp) { + return 0; + } + if (str_equal(fikp->fk_name, fik.fk_name)) + bpf_map_delete_elem(&ino_name_map, &ino_id); + return 0; +} + +SEC("fentry/ext4_file_write_iter") +int BPF_PROG(my_ext4_file_write_iter, + struct kiocb *iocb, struct iov_iter *from) +{ + struct inode* inode = iocb->ki_filp->f_inode; + dev_t dev_cur = inode->i_sb->s_dev; + struct file_info_key* fikp = NULL; + struct file_info_val* fivp = NULL; + u64 ino_id = inode->i_ino; + + if (dev_target && dev_target != dev_cur) + return 0; + + fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id); + if (!fikp) { + bpf_printk("fail to find fikp: %d", ino_id); + return 0; + } + fivp = bpf_map_lookup_elem(&file_info_map, fikp); + if (!fivp) { + bpf_printk("failed to lookup file_info_map\n"); + return 0; + } + + fivp->fv_hint = inode->i_write_hint; + if(iocb->ki_flags & IOCB_DIRECT) + __sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_DIRECT_WRITE], 1); + else + __sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_BUFFER_WRITE], 1); + return 0; +} + +SEC("fentry/ext4_file_read_iter") +int BPF_PROG(my_ext4_file_read_iter, + struct kiocb *iocb, struct iov_iter *to) +{ + //bpf_printk("ext4_file_read_iter\n"); + struct file* file = iocb->ki_filp; + struct inode* inode = file->f_inode; + dev_t dev_cur = inode->i_sb->s_dev; + struct file_info_key* fikp = NULL; + struct file_info_val* fivp = NULL; + u64 ino_id = inode->i_ino; + //bpf_printk("device_num:%d dev:%d\n", device_num, dev); + if (dev_target && dev_target != dev_cur) + return 0; + + fikp = bpf_map_lookup_elem(&ino_name_map, &ino_id); + if (!fikp) { + bpf_printk("fail to find fikp: %d", ino_id); + return 0; + } + fivp = bpf_map_lookup_elem(&file_info_map, fikp); + if (!fivp) { + bpf_printk("failed to lookup file_info_map\n"); + return 0; + } + + if (iocb->ki_flags & IOCB_DIRECT) + __sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_DIRECT_READ], 1); + else + __sync_fetch_and_add(&fivp->fv_rw_cnt[RW_TYPE_BUFFER_READ], 1); + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; \ No newline at end of file diff --git a/libbpf-tools/ext4File.c b/libbpf-tools/ext4File.c new file mode 100644 index 000000000000..10a1a8beedfa --- /dev/null +++ b/libbpf-tools/ext4File.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +#include +#include +#include +#include +#include +#include +#include +#include "ext4file.skel.h" +#include "trace_helpers.h" +#include "ext4file.h" + +#define BG_LIST_NUM 57232 + +struct config { + char* dir; + char* output_file; + char* dev; + time_t interval; + int times; +}config = { + .interval = 100000000, + .times = 100000000, +}; + +static volatile bool exiting; + +void print_usage(FILE* fp, int argc, char** argv) { + fprintf(fp, + "Show I/O pattern of every ext4 file.\n" + "\n" + "Usage: %s [-h, --help] [-d , --dir==] [-o , --output==] [interval] [count]\n" + "\n" + "Options:\n" + " -d, --dir= Trace the specific device(FS) mounted on this dir\n" + " -o, --output= Output to a specific file(selective)\n" + " -h, --help Show this help\n" + " interval Specify the amount of time in seconds between each report\n" + " count Limit the number of reports (default: unlimited)\n" + "\n" + "Examples:\n" + " %s -d /mnt/ext4 # Trace the device mounted on '/mnt/ext4'\n" + " %s -d /mnt/ext4 1 10 # Print 10 reports at 1 second intervals\n" + " %s -d /mnt/ext4 -o output 1 10 # Print 10 reports at 1 second intervals to ./output\n", + argv[0], argv[0], argv[0], argv[0]); +} + +int parse_opt(int argc, char** argv) { + int c; + int option_index = 0; + static struct option long_options[] = { + {"dir", required_argument, 0, 'd'}, + {"output", required_argument, 0, 'o'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "d:o:h", long_options, &option_index)) != -1) { + switch (c) { + case 'd': + config.dir = optarg; + break; + case 'h': + print_usage(stdout, argc, argv); + exit(0); + case 'o': + if (optarg) + config.output_file = optarg; + else + config.output_file = NULL; + break; + case '?': + print_usage(stderr, argc, argv); + exit(1); + default: + print_usage(stderr, argc, argv); + exit(1); + } + } + + if (optind < argc) { + if (optind + 1 > argc) { + fprintf(stderr, "Missing value for interval\n"); + print_usage(stderr, argc, argv); + exit(1); + } + config.interval = atoi(argv[optind++]); + if (optind < argc) { + config.times = atoi(argv[optind++]); + } + } + + return 0; +} + +void my_printf(int fd_output, const char *format, ...){ + va_list args; + va_start(args, format); + + char buf[1024]; + int len; + len = vsnprintf(buf, sizeof(buf), format, args); + + va_end(args); + if (fd_output >= 0) + write(fd_output, buf, len); + else if (fd_output == FD_STDOUT) + fwrite(buf, 1, len, stdout); + else if (fd_output == FD_STDERR) + fwrite(buf, 1, len, stderr); +} + +void sig_handler(int sig) { + exiting = true; +} + +int get_mounts_dev_by_dir(const char* dev, char* dir, char* type) { + FILE* f = NULL; + char mount_dev[256] = { 0 }; + char mount_dir[256] = { 0 }; + char mount_type[50] = { 0 }; + int match; + + if (dir[strlen(dir) - 1] == '/') + dir[strlen(dir) - 1] = '\0'; + + f = fopen("/proc/mounts", "r"); + if (!f) { + fprintf(stderr, "could not open /proc/mounts\n"); + return -1; + } + + do { + match = fscanf(f, "%255s %255s %49s\n", + mount_dev, mount_dir, mount_type); + if (match == 3 && strcmp(dir, mount_dir) == 0) { + memcpy((void*)dev, mount_dev, sizeof(mount_dev)); + memcpy(type, mount_type, sizeof(mount_type)); + fclose(f); + return 0; + } + memset(mount_dev, 0, strlen(mount_dev)); + memset(mount_dir, 0, strlen(mount_dir)); + memset(mount_type, 0, strlen(mount_type)); + } while (match != EOF); + + fclose(f); + return -1; +} + +void get_device_name_from_path(const char* mount_dev, char* device_name) { + int len = strlen(mount_dev); + int pos; + for (pos = len - 1; pos >= 0; pos--) { + if (mount_dev[pos] == '/') break; + } + pos = pos >= 0 ? pos + 1 : 0; + memcpy(device_name, mount_dev + pos, len - pos); +} + +void ext4_info_get(struct ext4_config* ext4_config) { + int fd_ext4_dev; + + fd_ext4_dev = open(config.dev, O_RDONLY); + if (fd_ext4_dev < 0) { + my_printf(FD_STDERR, "failed to open %s\n", config.dev); + goto cleanup; + } + pread(fd_ext4_dev, &ext4_config->blocks_per_group, 4, 1056); + pread(fd_ext4_dev, &ext4_config->blocks_count, 4, 1028); + ext4_config->bg_cnt = + ext4_config->blocks_count / ext4_config->blocks_per_group; + +cleanup: + if (fd_ext4_dev > 0) + close(fd_ext4_dev); +} + +void print_file_infos(int fdT, char* time_buffer, int fd_output) { + int err; + struct file_info_key lookup_key = {}, next_key; + struct file_info_val fiv; + my_printf(fd_output, "%s\n", time_buffer); + my_printf(fd_output, "%-10s %-20s %-10s %-6s " + "%-15s %-15s %-15s %-15s %-8s\n", + "file_name", "inode", "pa_inode", "hint", + "buffer_read", "direct_read", "buffer_write", "direct_write", "delete"); + while (!bpf_map_get_next_key(fdT, &lookup_key, &next_key)) { + err = bpf_map_lookup_elem(fdT, &next_key, &fiv); + if (err < 0) { + my_printf(FD_STDERR, + "failed to lookup err: %u\n", err); + return; + } + my_printf(fd_output, "%-10u %-20s %-10u %-6u ", + next_key.fk_name, next_key.fk_ino, next_key.fk_pa_ino, fiv.fv_hint); + my_printf(fd_output, "%-15d %-15d %-15d %-15d ", + fiv.fv_rw_cnt[RW_TYPE_BUFFER_READ], fiv.fv_rw_cnt[RW_TYPE_DIRECT_READ], + fiv.fv_rw_cnt[RW_TYPE_BUFFER_WRITE], fiv.fv_rw_cnt[RW_TYPE_DIRECT_WRITE]); + my_printf(fd_output, "%-8s\n", + fiv.fv_delete ? "True": "False"); + lookup_key = next_key; + } +} + +int program_configure(int argc, char** argv, int* fd_output, + struct partitions** partitions, const struct partition** partition) { + const char* fs_type = "ext4"; + char device_name[20]; + char mount_type[10]; + config.dev = malloc(256); + memset(config.dev, 0, 256); + + if (parse_opt(argc, argv)) { + my_printf(FD_STDERR, "error: parse_opt failed!\n"); + return -1; + } + if (!config.dir) { + my_printf(FD_STDERR, "error: the FS mounted dir is needed\n"); + return -1; + } + + //if the dev is mounted or made by ext4 + if (get_mounts_dev_by_dir(config.dev, config.dir, mount_type)) { + my_printf(FD_STDERR, + "error: failed to find %s, you can refer to \"df -h\"\n", config.dir); + return -1; + } + if (strcmp(fs_type, mount_type)) { + my_printf(FD_STDERR, "error: the fs is not ext4\n"); + return -1; + } + + // get the device_name(eg. get 'nvme0n1' from '/dev/nvme0n1') + get_device_name_from_path(config.dev, device_name); + + *partitions = partitions__load(); + if (!*partitions) { + my_printf(FD_STDERR, "error: failed to load partitions\n"); + return -1; + } + *partition = partitions__get_by_name(*partitions, device_name); + if (!*partition) { + my_printf(FD_STDERR, "error: failed to find the %s in partitions\n", device_name); + return -1; + } + if (config.output_file) { + *fd_output = open(config.output_file, O_WRONLY | O_CREAT); + if (*fd_output == -1) { + my_printf(FD_STDERR, "error: failed to open %s\n", config.output_file); + return -1; + } + } + /* INT EVENT */ + signal(SIGINT, sig_handler); + return 0; +} + +int bpf_initialize_and_load(struct ext4file_bpf** objp, const struct partition* partition, struct ext4_config* ext4_config) { + LIBBPF_OPTS(bpf_object_open_opts, open_opts); + *objp = ext4file_bpf__open_opts(&open_opts); + if (!*objp) { + my_printf(FD_STDERR, "failed to open BPF object\n"); + return -1; + } + + if (ext4file_bpf__load(*objp)) { + my_printf(FD_STDERR, "failed to load BPF object\n"); + return -1; + } + + ext4_info_get(ext4_config); + (*objp)->bss->blocks_per_group = ext4_config->blocks_per_group; + (*objp)->bss->dev_target = partition->dev; + + if (ext4file_bpf__attach(*objp)) { + my_printf(FD_STDERR, "failed to attach BPF programs\n"); + return -1; + } + + return 0; +} + +int main(int argc, char **argv) { + int fd_output = FD_STDOUT; + char time_buffer[20]; + struct ext4_config ext4_config = {}; + struct partitions* partitions = NULL; + const struct partition* partition = NULL; + struct ext4file_bpf* obj = NULL; + time_t cur_time; + struct tm* info; + if (program_configure(argc, argv, &fd_output, &partitions, &partition)) + goto cleanup; + if (bpf_initialize_and_load(&obj, partition, &ext4_config)) + goto cleanup; + + int fd_map_ffm = bpf_map__fd(obj->maps.file_info_map); + + my_printf(FD_STDOUT, "interval: %u\n", config.interval); + my_printf(FD_STDOUT, "EXT4 FS Info: blocks_count=%u blocks_per_group=%u bg_cnt=%u\n", ext4_config.blocks_count, ext4_config.blocks_per_group, ext4_config.bg_cnt); + my_printf(FD_STDOUT, "Tracing Ext4 read/write... Hit Ctrl-C to end.\n"); + + while (!exiting) { + sleep(config.interval); + time(&cur_time); + info = localtime(&cur_time); + strftime(time_buffer, 80, "%Y-%m-%d %H:%M:%S", info); + + print_file_infos(fd_map_ffm, time_buffer, fd_output); + config.times--; + if (config.times <= 0) + break; + } + close(fd_map_ffm); +cleanup: + if(obj) + ext4file_bpf__destroy(obj); + if(partitions) + partitions__free(partitions); + if(fd_output > -1) + close(fd_output); + return 0; +} diff --git a/libbpf-tools/ext4File.h b/libbpf-tools/ext4File.h new file mode 100644 index 000000000000..29a801b3fd8c --- /dev/null +++ b/libbpf-tools/ext4File.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +#ifndef __EXT4FILE_H +#define __EXT4FILE_H + +#define IOCB_DIRECT (1 << 17) + +#define FD_STDOUT -1 +#define FD_STDERR -2 + +#define MAX_FILE_NAME 48 + +enum rw_type { + RW_TYPE_BUFFER_READ, + RW_TYPE_DIRECT_READ, + RW_TYPE_BUFFER_WRITE, + RW_TYPE_DIRECT_WRITE, + RW_TYPE_CNT +}; + +struct file_info_key{ + __u32 fk_ino; + __u32 fk_pa_ino; + char fk_name[MAX_FILE_NAME]; +}; + +struct file_info_val { + __u64 fv_rw_cnt[RW_TYPE_CNT]; + int fv_hint; + bool fv_delete; +}; + +struct unique_file { + int ino; + char filename[MAX_FILE_NAME]; +}; + +struct ext4_config { + uint32_t blocks_per_group; + uint32_t blocks_count; + uint32_t bg_cnt; +}; + +#endif