diff --git a/libbpf-tools/Makefile b/libbpf-tools/Makefile index 66183659828d..ce86ef9faef9 100644 --- a/libbpf-tools/Makefile +++ b/libbpf-tools/Makefile @@ -94,6 +94,7 @@ APPS = \ tcptop \ vfsstat \ wakeuptime \ + xfsAG \ $(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/xfsAG.bpf.c b/libbpf-tools/xfsAG.bpf.c new file mode 100644 index 000000000000..9b17ed5164e4 --- /dev/null +++ b/libbpf-tools/xfsAG.bpf.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +#include +#include +#include +#include + +#include "xfsAG.h" + +const volatile __u32 ag_count = 0; +const volatile __u32 device_num = 0; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, MAX_AG_CNT); + __type(key, u32); + __type(value, ag_infos); +} map_ag_infos SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10000); + __type(key, struct file_rwu_key); + __type(value, enum rwu_type); +} map_file_rwu SEC(".maps"); + + +static __always_inline enum rwu_type get_rwu_type(xfs_ino_t ino_id, long long offset, u64 lenth) { + struct file_rwu_key rwu_key = { ino_id, offset, lenth }; + enum rwu_type* rwutp = bpf_map_lookup_elem(&map_file_rwu, &rwu_key); + if (rwutp) { + return *rwutp; + } + return RWU_TYPE_CNT; +} + +static __always_inline void delete_rwu_type(xfs_ino_t ino_id, long long offset, u64 lenth) { + struct file_rwu_key rwu_key = { ino_id, offset, lenth }; + if (bpf_map_delete_elem(&map_file_rwu, &rwu_key)) + bpf_printk("failed to delete map_file_rwu\n"); +} + +static __always_inline int generic_iomap_deal(u64 agno, enum rwu_type rwut, struct xfs_inode* ip) { + struct ag_infos* aip; + u64 id, yu; + dev_t dev = ip->i_mount->m_super->s_dev; + if (device_num && dev != device_num) + return 0; + + aip = bpf_map_lookup_elem(&map_ag_infos, &agno); + if (!aip) { + bpf_printk("can't find ag_infos\n"); + return 0; + } + __sync_fetch_and_add(&aip->rwu_cnt[rwut], 1); + return 0; +} +/******************************************************************************************* +******** tracepoint ******** +********************************************************************************************/ +/***** read&write *****/ +SEC("tp_btf/xfs_file_buffered_read") +int BPF_PROG(my_xfs_file_buffered_read, struct kiocb* iocb, struct iov_iter* to) { + if(!iocb || !to) + return 0; + struct file_rwu_key rwu_key = { iocb->ki_filp->f_inode->i_ino, iocb->ki_pos, to->count }; + enum rwu_type rwu_val = RWU_TYPE_BUFFER_READ; + + return bpf_map_update_elem(&map_file_rwu, &rwu_key, &rwu_val, BPF_ANY); +} +SEC("tp_btf/xfs_file_direct_read") +int BPF_PROG(my_xfs_file_direct_read, struct kiocb* iocb, struct iov_iter* to) { + if(!iocb || !to) + return 0; + struct file_rwu_key rwu_key = { iocb->ki_filp->f_inode->i_ino, iocb->ki_pos, to->count }; + enum rwu_type rwu_val = RWU_TYPE_DIRECT_READ; + + return bpf_map_update_elem(&map_file_rwu, &rwu_key, &rwu_val, BPF_ANY); +} +SEC("tp_btf/xfs_file_buffered_write") +int BPF_PROG(my_xfs_file_buffered_write, struct kiocb* iocb, struct iov_iter* from) { + if(!iocb || !from) + return 0; + struct file_rwu_key rwu_key = { iocb->ki_filp->f_inode->i_ino, iocb->ki_pos, from->count }; + enum rwu_type rwu_val = RWU_TYPE_BUFFER_WRITE; + + return bpf_map_update_elem(&map_file_rwu, &rwu_key, &rwu_val, BPF_ANY); +} +SEC("tp_btf/xfs_file_direct_write") +int BPF_PROG(my_xfs_file_direct_write, struct kiocb* iocb, struct iov_iter* from) { + if(!iocb || !from) + return 0; + struct file_rwu_key rwu_key = { iocb->ki_filp->f_inode->i_ino, iocb->ki_pos, from->count }; + enum rwu_type rwu_val = RWU_TYPE_DIRECT_WRITE; + + return bpf_map_update_elem(&map_file_rwu, &rwu_key, &rwu_val, BPF_ANY); +} +SEC("tp_btf/xfs_iomap_alloc") +int BPF_PROG(my_xfs_iomap_alloc, struct xfs_inode* ip, xfs_off_t offset, ssize_t count, int whichfork, struct xfs_bmbt_irec* irec) { + if(!ip || !irec) + return 0; + u64 agno = XFS_FSB_TO_AGNO(ip->i_mount, irec->br_startblock); + enum rwu_type rwut = get_rwu_type(ip->i_ino, offset, count); + + if (agno >= ag_count || agno < 0) { + bpf_printk("invalid agno\n"); + goto cleanup; + } + if (rwut >= RWU_TYPE_CNT || rwut < RWU_TYPE_BUFFER_READ) { + bpf_printk("invalid RWU TYPE\n"); + goto cleanup; + } + if (generic_iomap_deal(agno, rwut, ip)) { + goto cleanup; + } +cleanup: + delete_rwu_type(ip->i_ino, offset, count); + return 0; +} +SEC("tp_btf/xfs_iomap_found") +int BPF_PROG(my_xfs_iomap_found, struct xfs_inode* ip, xfs_off_t offset, ssize_t count, int whichfork, struct xfs_bmbt_irec* irec) { + if(!ip || !irec) + return 0; + u64 agno = XFS_FSB_TO_AGNO(ip->i_mount, irec->br_startblock); + enum rwu_type rwut = get_rwu_type(ip->i_ino, offset, count); + + if (agno >= ag_count || agno < 0) { + bpf_printk("invalid agno\n"); + goto cleanup; + } + if (rwut >= RWU_TYPE_CNT || rwut < RWU_TYPE_BUFFER_READ) { + bpf_printk("invalid RWU TYPE\n"); + goto cleanup; + } + if (rwut == RWU_TYPE_BUFFER_WRITE) + rwut = RWU_TYPE_BUFFER_UPDATE; + else if (rwut == RWU_TYPE_DIRECT_WRITE) + rwut = RWU_TYPE_DIRECT_UPDATE; + if (generic_iomap_deal(agno, rwut, ip)) { + goto cleanup; + } +cleanup: + delete_rwu_type(ip->i_ino, offset, count); + return 0; +} +char LICENSE[] SEC("license") = "GPL"; diff --git a/libbpf-tools/xfsAG.c b/libbpf-tools/xfsAG.c new file mode 100644 index 000000000000..e452175634f4 --- /dev/null +++ b/libbpf-tools/xfsAG.c @@ -0,0 +1,392 @@ +// 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 +#include "xfsAG.skel.h" +#include "trace_helpers.h" +#include "xfsAG.h" + + + +bool exiting; + +struct config { + char* dir; + char* dev; + time_t interval; + int times; +}; + +void print_usage(FILE* fp, int argc, char** argv) { + fprintf(fp, + "Count read/write/update number of every AG in an XFS SSD.\n" + "\n" + "Usage: %s [-h, --help] [-d , --dir==] [interval] [count]\n" + "\n" + "Options:\n" + " -d, --dir= Trace the specific device\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/xfs # Trace the device mounted on '/mnt/xfs'\n" + " %s -d /mnt/xfs 1 10 # Print 10 reports at 1 second intervals\n", + argv[0], argv[0], argv[0]); +} + +int parse_opt(struct config* config, int argc, char** argv) { + int c; + int option_index = 0; + static struct option long_options[] = { + {"dir", required_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) { + switch (c) { + case 'd': + config->dir = optarg; + break; + case 'h': + print_usage(stdout, argc, argv); + exit(0); + 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_print(FILE *fp, bool isenter, const char *format, ...){ + char buffer[100]={}; + va_list args; + va_start(args, format); + + vsnprintf(buffer, sizeof(buffer), format, args); + + va_end(args); + + if(isenter){ + if(fp) + fprintf(fp, "%s\n", buffer); + else + printf("%s\n", buffer); + } + else{ + if(fp) + fprintf(fp, "%s", buffer); + else + printf("%s", buffer); + } + +} + +void sig_handler(int sig) +{ + exiting = true; +} + +int endian_convert_b_to_l(__u64 num, int lenth) +{ + if (lenth > 8) + return -1; + unsigned char *p = (unsigned char*) & num; + __u64 res = 0; + for(int i = 0; i < lenth; i++){ + int off = (lenth - 1 - i) * 8; + __u64 it = (__u64)*(p + i); + it = it << off; + res += it; + } + return res; +} + +void print_ag_infos(int fd_ag_infos, FILE* fp_ag, char* time, struct xfs_config* xfs_config) +{ + struct ag_infos ag_infos_val = {}; + + my_print(fp_ag, true, "%s", time); + my_print(fp_ag, false, "%-5s %-12s %-20s %-20s ", + "agno", "ag_size(MB)", "direct_read_cnt", "buffer_write_cnt"); + my_print(fp_ag, true, "%-20s %-20s %-20s", + "direct_write_cnt", "buffer_update_cnt", "direct_update_cnt"); + for(int key = 0; key < xfs_config->ag_count; key++){ + bool bre = true; + if(bpf_map_lookup_elem(fd_ag_infos, &key, &ag_infos_val)) + continue; + for (int i = 0; i < RWU_TYPE_CNT; i++) { + if (ag_infos_val.rwu_cnt[i]) + bre = false; + } + if (bre) + continue; + my_print(fp_ag, false, "%-5d %-12lld %-20lld %-20lld ", + key, ag_infos_val.ag_size, + ag_infos_val.rwu_cnt[RWU_TYPE_DIRECT_READ], ag_infos_val.rwu_cnt[RWU_TYPE_BUFFER_WRITE]); + my_print(fp_ag, true, "%-20lld %-20lld %-20lld", + ag_infos_val.rwu_cnt[RWU_TYPE_DIRECT_WRITE], ag_infos_val.rwu_cnt[RWU_TYPE_BUFFER_UPDATE], + ag_infos_val.rwu_cnt[RWU_TYPE_DIRECT_UPDATE]); + } +} + +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 initialize_map_ag_infos(int fd_ag_infos, struct xfs_config* xfs_config, __u64* ag_sizes) +{ + struct ag_infos ag_infos_val = {}; + + for(int key = 0; key < xfs_config->ag_count; key++){ + ag_infos_val.ag_size = ag_sizes[key]; + if (bpf_map_update_elem(fd_ag_infos, &key, &ag_infos_val, BPF_ANY) != 0) { + fprintf(stderr, "failed to init map\n"); + continue; + } + } +} + +void xfs_info_get(struct xfs_config* xfs_config, struct config* config, __u64* ag_sizes) +{ + struct sysinfo info; + time_t curtime; + if (sysinfo(&info)) { + fprintf(stderr, "Failed to get sysinfo, errno:%u, reason:%s\n", errno, strerror(errno)); + } + time(&curtime); + xfs_config->system_up = curtime - info.uptime; + int fd_xfs_dev = open(config->dev, O_RDONLY); + if (fd_xfs_dev < 0) { + fprintf(stderr, "failed to open %s\n", config->dev); + goto cleanup; + } + struct xfs_sb sb; + if(pread(fd_xfs_dev, &sb, sizeof(sb), 0) != sizeof(sb)){ + fprintf(stderr, "failed to read super block of %s\n", config->dev); + goto cleanup; + } + xfs_config->block_size = endian_convert_b_to_l(sb.sb_blocksize, sizeof(sb.sb_blocksize)); + xfs_config->sector_size = endian_convert_b_to_l(sb.sb_sectsize, sizeof(sb.sb_sectsize)); + xfs_config->inode_size = endian_convert_b_to_l(sb.sb_inodesize, sizeof(sb.sb_inodesize)); + xfs_config->ag_blocks = endian_convert_b_to_l(sb.sb_agblocks, sizeof(sb.sb_agblocks)); + xfs_config->data_blocks = endian_convert_b_to_l(sb.sb_dblocks, sizeof(sb.sb_dblocks)); + xfs_config->ag_count = endian_convert_b_to_l(sb.sb_agcount, sizeof(sb.sb_agcount)); + + for(int i = 0; i < xfs_config->ag_count; i++){ + // AG Size info + if(i == xfs_config->ag_count - 1) + ag_sizes[i] = xfs_config->data_blocks % xfs_config->ag_blocks; + else + ag_sizes[i] = xfs_config->ag_blocks; + ag_sizes[i] *= xfs_config->block_size; + ag_sizes[i] /= 1024*1024; + } + +cleanup: + if (fd_xfs_dev > 0) + close(fd_xfs_dev); +} + +void get_device_name_from_path(char *device_name, struct config* config) +{ + int len = strlen(config->dev); + int pos; + for (pos = len - 1; pos >= 0; pos--) { + if (config->dev[pos] == '/') break; + } + pos = pos >= 0 ? pos + 1 : 0; + memcpy(device_name, config->dev + pos, len - pos); +} + +int program_configure(int argc, char** argv, FILE** fp_ag, struct config *config, + struct partitions** partitions, const struct partition** partition) +{ + const char* fs_type = "xfs"; + const char* log_path = "ag_infos.log"; + char device_name[20]; + char mount_type[50]; + config->dev = malloc(256); + memset(config->dev, 0, 256); + + if (parse_opt(config, argc, argv)) { + fprintf(stderr, "error: parse_opt failed!\n"); + return -1; + } + + if (!config->dir) { + fprintf(stderr, "error: the FS mouned dir is needed\n"); + return -1; + } + + // if the dev is mounted or made by xfs + if (get_mounts_dev_by_dir(config->dev, config->dir, mount_type)) { + fprintf(stderr, "error: failed to find %s\n", config->dir); + return -1; + } + if (strcmp(fs_type, mount_type)) { + fprintf(stderr, "error: the fs is not xfs\n"); + return -1; + } + + // get the device_name(eg. get 'nvme0n1' from '/dev/nvme0n1') + get_device_name_from_path(device_name, config); + // get the dev num, which will be send to eBPF program + *partitions = partitions__load(); + if (!*partitions) { + fprintf(stderr, "error: failed to load partitions\n"); + return -1; + } + *partition = partitions__get_by_name(*partitions, device_name); + if (!*partition) { + fprintf(stderr, "error: failed to find the %s in partitions\n", device_name); + return -1; + } + // output + *fp_ag = fopen(log_path, "w"); + if (!*fp_ag) { + fprintf(stderr, "error: failed to open %s\n", log_path); + return -1; + } + + /* INT EVENT */ + signal(SIGINT, sig_handler); + + return 0; +} + +int bpf_initialize_and_load(struct xfsAG_bpf** objp, int* fd_ag_infos, struct config* config, + __u64* ag_sizes, const struct partition* partition, struct xfs_config* xfs_config) +{ + LIBBPF_OPTS(bpf_object_open_opts, open_opts); + /* bpf open */ + *objp = xfsAG_bpf__open_opts(&open_opts); + if (!(*objp)) { + fprintf(stderr, "error: failed to open BPF object\n"); + return -1; + } + + /* bpf load */ + xfs_info_get(xfs_config, config, ag_sizes); + (*objp)->rodata->ag_count = xfs_config->ag_count; + (*objp)->rodata->device_num = partition->dev; + + if (xfsAG_bpf__load(*objp)) { + fprintf(stderr, "failed to load BPF object\n"); + return -1; + } + + /* bpf attach */ + if (xfsAG_bpf__attach(*objp)) { + fprintf(stderr, "failed to attach BPF programs\n"); + return -1; + } + + /* initialize the MAP map_ag_infos */ + *fd_ag_infos = bpf_map__fd((*objp)->maps.map_ag_infos); + initialize_map_ag_infos(*fd_ag_infos, xfs_config, ag_sizes); + + return 0; +} + +int main(int argc, char **argv) +{ + struct config config = { + .times = 100000000, + .interval = 100000000 + }; + struct partitions* partitions = NULL; + const struct partition* partition = NULL; + struct xfs_config xfs_config = {}; + char time_buffer[20]; + int fd_ag_infos = 0; + __u64 *ag_sizes = malloc(MAX_AG_CNT * sizeof(__u64)); + FILE* fp_ag = NULL; + time_t cur_time; // used to get the current timestamp + struct tm* info; // translate the cur_time to local time + struct xfsAG_bpf* obj = NULL; // bpf object + if(program_configure(argc, argv, &fp_ag, &config, &partitions, &partition)) + goto cleanup; + if(bpf_initialize_and_load(&obj, &fd_ag_infos, &config, ag_sizes, partition, &xfs_config)) + goto cleanup; + printf("Tracing a device with XFS filesystem... Hit Ctrl-C to end.\n"); + /* main: poll */ + my_print(fp_ag, true, "ag_count:%d", xfs_config.ag_count); + while (!exiting) { + sleep(config.interval); + time(&cur_time); + info = localtime(&cur_time); + strftime(time_buffer, 80, "%Y-%m-%d %H:%M:%S", info); + + if(config.times <= 0) + break; + print_ag_infos(fd_ag_infos, fp_ag, time_buffer, &xfs_config); + config.times--; + if(config.times <= 0) + break; + } +cleanup: + if(obj) + xfsAG_bpf__destroy(obj); + if(partitions) + partitions__free(partitions); + if(fp_ag) + fclose(fp_ag); + if(fd_ag_infos) + close(fd_ag_infos); + return 0; +} diff --git a/libbpf-tools/xfsAG.h b/libbpf-tools/xfsAG.h new file mode 100644 index 000000000000..6726d44dc1b8 --- /dev/null +++ b/libbpf-tools/xfsAG.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +#ifndef __XFSAG_H +#define __XFSAG_H + +#define XFS_SUPER_MAGIC 0x58465342 //you can find it in the source code:include/uapi/linux/magic.h +#define MAX_AG_CNT 256 + +#define XFS_FSB_TO_AGNO(mp,fsbno) \ + ((xfs_agnumber_t)((fsbno) >> (mp)->m_sb.sb_agblklog)) + + +struct xfs_config{ + unsigned int block_size; + unsigned int sector_size; + unsigned int inode_size; + unsigned int ag_blocks; + unsigned int data_blocks; + unsigned int ag_count; + unsigned long long system_up; +}; + +enum rwu_type { + RWU_TYPE_BUFFER_READ, + RWU_TYPE_DIRECT_READ, + RWU_TYPE_BUFFER_WRITE, + RWU_TYPE_DIRECT_WRITE, + RWU_TYPE_BUFFER_UPDATE, + RWU_TYPE_DIRECT_UPDATE, + RWU_TYPE_CNT +}; + +typedef struct ag_infos { + __u64 ag_size; + __u64 rwu_cnt[RWU_TYPE_CNT]; +}ag_infos; + +typedef struct file_rwu_key { + __u64 ino_id; + __s64 offset; + __u64 lenth; +}file_rwu_key; + +#endif