diff --git a/.gitignore b/.gitignore index 7152a7e3a..a3f27e445 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,5 @@ eBPF_Supermarket/CPU_Subsystem/eBPF_proc_image/proc_image # Stack_Analyser eBPF_Supermarket/Stack_Analyser/stack_analyzer -eBPF_Supermarket/Stack_Analyser/exporter/exporter \ No newline at end of file +eBPF_Supermarket/Stack_Analyser/exporter/exporter +eBPF_Supermarket/Stack_Analyser/bpf_skel \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/CMakeLists.txt b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/CMakeLists.txt index df6f7192b..ebe631dab 100644 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/CMakeLists.txt +++ b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/CMakeLists.txt @@ -13,7 +13,8 @@ set(TOOL_NAME stack_analyzer) set(TOOL_BELONG_TO_MODULE system_diagnosis) -file(GLOB STACK_ANALYZER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) +file(GLOB STACK_ANALYZER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +file(GLOB STACK_ANALYZER_WAPPER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/bpf/*.cpp) file(GLOB apps ${CMAKE_CURRENT_SOURCE_DIR}/bpf/*.bpf.c) # 若不用Rust,则排除 profile.bpf.c @@ -28,6 +29,7 @@ if (NOT EXISTS ${SRC_GEN_TARGET_DIR}) message(STATUS "directory create success") endif () +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/ ${SRC_GEN_TARGET_DIR}) # 遍历文件夹内所有的bpf.c foreach(app ${apps}) get_filename_component(app_stem ${app} NAME_WE) @@ -36,7 +38,7 @@ foreach(app ${apps}) add_dependencies(${app_stem}_skel libbpf-build bpftool-build) endforeach() -add_executable(${TOOL_NAME} ${STACK_ANALYZER_SOURCE_FILES}) +add_executable(${TOOL_NAME} ${STACK_ANALYZER_WAPPER_FILES} ${STACK_ANALYZER_SOURCE_FILES}) foreach (app ${apps}) get_filename_component(app_stem ${app} NAME_WE) target_link_libraries(${TOOL_NAME} ${app_stem}_skel -lstdc++) diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf new file mode 120000 index 000000000..93c3bcab6 --- /dev/null +++ b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf @@ -0,0 +1 @@ +../../../../../eBPF_Supermarket/Stack_Analyser/bpf/ \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/io_count.bpf.c b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/io_count.bpf.c deleted file mode 100644 index 774fd1a9a..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/io_count.bpf.c +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 内核态bpf的io-cpu模块代码 - -#include "vmlinux.h" -#include -#include -#include - -#include "sa_ebpf.h" -#include "task.h" - -DeclareCommonMaps(io_tuple); -DeclareCommonVar(); -int apid = 0; - -const char LICENSE[] SEC("license") = "GPL"; - -static int do_stack(struct trace_event_raw_sys_enter *ctx) -{ - struct task_struct *curr = (struct task_struct *)bpf_get_current_task(); // 利用bpf_get_current_task()获得当前的进程tsk - ignoreKthread(curr); - u32 pid = get_task_ns_pid(curr); // 利用帮助函数获得当前进程的pid - if ((apid >= 0 && pid != apid) || !pid || pid == self_pid) - return 0; - u64 len = BPF_CORE_READ(ctx, args[2]); // 读取IO类系统调用的第三个参数,都表示数据大小 - if (len <= min || len > max) - return 0; - u32 tgid = get_task_ns_tgid(curr); // 利用帮助函数获取进程的tgid - bpf_map_update_elem(&pid_tgid, &pid, &tgid, BPF_ANY); // 将pid_tgid表中的pid选项更新为tgid,若没有该表项,则创建 - comm *p = bpf_map_lookup_elem(&pid_comm, &pid); // p指向pid_comm哈希表中的pid表项对应的value - if (!p) // 如果p不为空,获取当前进程名保存至name中,如果pid_comm当中不存在pid name项,则更新 - { - comm name; - bpf_get_current_comm(&name, COMM_LEN); - bpf_map_update_elem(&pid_comm, &pid, &name, BPF_NOEXIST); - } - psid apsid = { - .pid = pid, - .usid = u ? USER_STACK : -1, // u存在,则USER_STACK - .ksid = k ? KERNEL_STACK : -1, // K存在,则KERNEL_STACK - }; - - // record time delta - io_tuple *d = bpf_map_lookup_elem(&psid_count, &apsid); // count指向psid_count表当中的apsid表项,即size - - if (!d) - { - io_tuple nd = {.count = 1, .size = len}; - bpf_map_update_elem(&psid_count, &apsid, &nd, BPF_NOEXIST); - } - else - { - d->count++; - d->size += len; - } - return 0; -} - -#define io_sec_tp(name) \ - SEC("tp/syscalls/sys_enter_" #name) \ - int prog_t_##name(struct trace_event_raw_sys_enter *ctx) { return do_stack(ctx); } - -io_sec_tp(write); -io_sec_tp(read); -io_sec_tp(recvfrom); -io_sec_tp(sendto); - -// tracepoint:syscalls:sys_exit_select -// tracepoint:syscalls:sys_enter_poll -// tracepoint:syscalls:sys_enter_epoll_wait - -// 1. 设置挂载点 -// tracepoint/syscalls/sys_enter_write 读操作 -// tracepoint/syscalls/sys_enter_read 写操作 -// tracepoint/syscalls/sys_enter_recvfrom 接收数据 -// tracepoint/syscalls/sys_enter_sendto 发送数据 - -// 2. 执行程序 int prog_t_##name(struct trace_event_raw_sys_enter *ctx) { return do_stack(ctx); } -// 最终调用上面的do_stack函数 \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/off_cpu_count.bpf.c b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/off_cpu_count.bpf.c deleted file mode 100644 index 7bdaf0527..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/off_cpu_count.bpf.c +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 内核态bpf的off-cpu模块代码 - -#include "vmlinux.h" -#include -#include -#include - -#include "sa_ebpf.h" -#include "task.h" - -DeclareCommonMaps(u32); -DeclareCommonVar(); - -int apid = 0; -BPF_HASH(start, u32, u64); //记录进程运行的起始时间 - -const char LICENSE[] SEC("license") = "GPL"; - -SEC("kprobe/finish_task_switch.isra.0") //动态挂载点finish_task_switch.isra.0 -int BPF_KPROBE(do_stack, struct task_struct *curr) -{ - // u32 pid = BPF_CORE_READ(curr, pid); - u32 pid = get_task_ns_pid(curr); //利用帮助函数获取当前进程tsk的pid - ignoreKthread(curr); - if ((apid >= 0 && pid == apid) || (apid < 0 && pid && pid != self_pid)) - { - // record curr block time - u64 ts = bpf_ktime_get_ns(); //ts=当前的时间戳(ns) - bpf_map_update_elem(&start, &pid, &ts, BPF_NOEXIST); //如果start表中不存在pid对应的时间,则就创建pid-->ts - } - - // calculate time delta, next ready to run - struct task_struct *next = (struct task_struct *)bpf_get_current_task();//next指向当前的结构体 - // pid = BPF_CORE_READ(next, pid); - pid = get_task_ns_pid(next); //利用帮助函数获取next指向的tsk的pid - u64 *tsp = bpf_map_lookup_elem(&start, &pid); //tsp指向start表中的pid的值 - if (!tsp) - return 0; - bpf_map_delete_elem(&start, &pid); //存在tsp,则删除pid对应的值 - u32 delta = (bpf_ktime_get_ns() - *tsp) >> 20; //delta为当前时间戳 - 原先tsp指向start表中的pid的值.代表运行时间 - - if ((delta <= min) || (delta > max)) - return 0; - - // record data - // u32 tgid = BPF_CORE_READ(next, tgid); - u32 tgid = get_task_ns_tgid(curr); //利用帮助函数获取当前进程的的tgid - bpf_map_update_elem(&pid_tgid, &pid, &tgid, BPF_ANY); //利用帮助函数更新tgid对应的pid表项 - comm *p = bpf_map_lookup_elem(&pid_comm, &pid); //p指向pid_comm中pid对应的表项 - if (!p) - { - comm name; - bpf_probe_read_kernel_str(&name, COMM_LEN, next->comm); //获取next指向的进程结构体的comm,赋值给comm - bpf_map_update_elem(&pid_comm, &pid, &name, BPF_NOEXIST); //如果pid_comm中不存在pid项,则创建 - } - psid apsid = { - .pid = pid, - .usid = u ? USER_STACK : -1, - .ksid = k ? KERNEL_STACK : -1, - }; - - // record time delta - u32 *count = bpf_map_lookup_elem(&psid_count, &apsid); //count指向psid_count中的apsid对应的值 - if (count) - (*count) += delta; //如果count存在,则psid_count中的apsid对应的值+=时间戳 - else - bpf_map_update_elem(&psid_count, &apsid, &delta, BPF_NOEXIST); //如果不存在,则将psid_count表中的apsid设置为delta - return 0; -} \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/on_cpu_count.bpf.c b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/on_cpu_count.bpf.c deleted file mode 100644 index 1b479dcf9..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/on_cpu_count.bpf.c +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 内核态bpf的on-cpu模块代码 - -#include "vmlinux.h" -#include -#include -#include - -#include "sa_ebpf.h" -#include "task.h" - -const char LICENSE[] SEC("license") = "GPL"; - -DeclareCommonMaps(u32); -DeclareCommonVar(); -unsigned long *load_a = NULL; - -SEC("perf_event") //挂载点为perf_event -int do_stack(void *ctx) -{ - unsigned long load; - bpf_core_read(&load, sizeof(unsigned long), load_a); //load为文件中读出的地址,则该地址开始读取unsigned long大小字节的数据保存到load - load >>= 11; //load右移11 - bpf_printk("%lu %lu", load, min); //输出load 以及min - if (load < min || load > max) - return 0; - // record data - struct task_struct *curr = (void *)bpf_get_current_task(); //curr指向当前进程的tsk - ignoreKthread(curr); // 忽略内核线程 - u32 pid = get_task_ns_pid(curr); //pid保存当前进程的pid,是cgroup pid 对应的level 0 pid - if (!pid || pid == self_pid) - return 0; - u32 tgid = get_task_ns_tgid(curr); //tgid保存当前进程的tgid - bpf_map_update_elem(&pid_tgid, &pid, &tgid, BPF_ANY); //更新pid_tgid表中的pid表项 - comm *p = bpf_map_lookup_elem(&pid_comm, &pid); //p指向pid_comm中的Pid对应的值 - if (!p) - { - comm name; - bpf_probe_read_kernel_str(&name, COMM_LEN, curr->comm); //name中保存的是当前进程tsk的进程名 - bpf_map_update_elem(&pid_comm, &pid, &name, BPF_NOEXIST); //更新pid_comm中的进程号对应的进程名 - } - psid apsid = { - .pid = pid, - .usid = u ? USER_STACK : -1, - .ksid = k ? KERNEL_STACK : -1, - }; - - // add cosunt - u32 *count = bpf_map_lookup_elem(&psid_count, &apsid); //count指向psid_count对应的apsid的值 - if (count) - (*count)++; //count不为空,则psid_count对应的apsid的值+1 - else - { - u32 orig = 1; - bpf_map_update_elem(&psid_count, &apsid, &orig, BPF_ANY); //否则psid_count对应的apsid的值=1 - } - return 0; -} \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/pre_count.bpf.c b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/pre_count.bpf.c deleted file mode 100644 index c2da7829c..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/pre_count.bpf.c +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 内核态bpf的预读取分析模块代码 - -#include "vmlinux.h" -#include -#include -#include - -#include "sa_ebpf.h" -#include "task.h" - -#define MINBLOCK_US 1ULL -#define MAXBLOCK_US 99999999ULL - -DeclareCommonMaps(ra_tuple); -DeclareCommonVar(); - -int apid = 0; -BPF_HASH(in_ra, u32, psid); -BPF_HASH(page_psid, struct page *, psid); - -SEC("fentry/page_cache_ra_unbounded") //fentry在内核函数page_cache_ra_unbounded进入时触发的挂载点 -int BPF_PROG(page_cache_ra_unbounded) -{ - struct task_struct* curr = (struct task_struct*)bpf_get_current_task(); - ignoreKthread(curr); - u32 pid = get_task_ns_pid(curr); //获取当前进程tgid,用户空间的pid即是tgid - - if ((apid >= 0 && pid != apid) || !pid || pid == self_pid) - return 0; - - u32 tgid = get_task_ns_tgid(curr); - bpf_map_update_elem(&pid_tgid, &pid, &tgid, BPF_ANY); //更新pid_tgid表中的pid对应的值 - comm *p = bpf_map_lookup_elem(&pid_comm, &pid); //p指向pid_comm表中pid对应的值 - if (!p) - { - comm name; - bpf_get_current_comm(&name, COMM_LEN); //获取当前进程名 - bpf_map_update_elem(&pid_comm, &pid, &name, BPF_NOEXIST); //在pid_comm表中更新pid对应的值 - } - - psid apsid = { - .pid = pid, - .usid = u ? USER_STACK : -1, - .ksid = k ? KERNEL_STACK : -1, - }; - - ra_tuple *d = bpf_map_lookup_elem(&psid_count, &apsid); //d指向psid_count表中的apsid对应的类型为tuple的值 - if (!d) - { - ra_tuple a = {.expect = 0, .truth = 0}; //初始化为0 - bpf_map_update_elem(&psid_count, &apsid, &a, BPF_ANY); //更新psid_count表中的apsid的值为a - } - bpf_map_update_elem(&in_ra, &pid, &apsid, BPF_ANY); //更新in_ra表中的pid对应的值为apsid - return 0; -} - - -SEC("fexit/alloc_pages") //fexit在内核函数alloc_pages退出时触发,挂载点为alloc_pages -int BPF_PROG(filemap_alloc_folio_ret, gfp_t gfp, unsigned int order, u64 ret) -{ - u32 pid = bpf_get_current_pid_tgid() >> 32; //pid为当前进程的pid - - if ((apid >= 0 && pid != apid) || !pid) - return 0; - - struct psid *apsid = bpf_map_lookup_elem(&in_ra, &pid); //apsid指向了当前in_ra中pid的表项内容 - if (!apsid) - return 0; - - ra_tuple *a = bpf_map_lookup_elem(&psid_count, apsid); //a是指向psid_count的apsid对应的内容 - if (!a) - return 0; - - const u32 lim = 1ul << order; //1 为长整型,左移order位,即2^order 即申请页的大小 - a->expect += lim; //a->expect+=页大小(未访问) - u64 addr; - bpf_core_read(&addr, sizeof(u64), &ret); //alloc_pages返回的值,即申请页的起始地址保存在addr中 - for (int i = 0; i < lim && i < 1024; i++, addr += 0x1000) - bpf_map_update_elem(&page_psid, &addr, apsid, BPF_ANY);//更新page_psid表中的addr(从页的起始地址开始到页的结束地址)所对应的值为apsid - - return 0; -} - -SEC("fexit/page_cache_ra_unbounded") -int BPF_PROG(page_cache_ra_unbounded_ret) //fexit在内核函数page_cache_ra_unbounded退出时触发的挂载点 -{ - u32 pid = bpf_get_current_pid_tgid() >> 32; //获取当前进程的pid - - if ((apid >= 0 && pid != apid) || !pid) - return 0; - - bpf_map_delete_elem(&in_ra, &pid); //删除了in_ra对应的pid的表项,即删除对应的栈计数信息 - return 0; -} - - -SEC("fentry/mark_page_accessed") //fentry在内核函数/mark_page_accessed进入时触发的挂载点,用于标记页面(page)已经被访问 -int BPF_PROG(mark_page_accessed, u64 page) -{ - u32 pid = bpf_get_current_pid_tgid() >> 32; //获取当前进程的pid - - if ((apid >= 0 && pid != apid) || !pid) - return 0; - psid *apsid; - apsid = bpf_map_lookup_elem(&page_psid, &page); //查看page_psid对应的 地址page 对应类型为psid的值,并保存在apsid - if (!apsid) - return 0; - ra_tuple *a = bpf_map_lookup_elem(&psid_count, apsid); //a指向psid_count的apsid的内容 - if (!a) - return 0; - a->truth++; //已访问 - bpf_map_delete_elem(&page_psid, &page); //删除page_psid的page对应的内容 - return 0; -} - -const char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include new file mode 120000 index 000000000..a84d125b4 --- /dev/null +++ b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include @@ -0,0 +1 @@ +../../../../../eBPF_Supermarket/Stack_Analyser/include/ \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/clipp.h b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/clipp.h deleted file mode 100644 index 757e74200..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/clipp.h +++ /dev/null @@ -1,7022 +0,0 @@ -/***************************************************************************** - * ___ _ _ ___ ___ - * | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++ - * | |_ | |_ | | | _/ _/ version 1.2.3 - * |___||___||_| |_| |_| https://github.com/muellan/clipp - * - * Licensed under the MIT License . - * Copyright (c) 2017-2018 André Müller - * - * --------------------------------------------------------------------------- - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef AM_CLIPP_H__ -#define AM_CLIPP_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/*************************************************************************//** - * - * @brief primary namespace - * - *****************************************************************************/ -namespace clipp { - - - -/***************************************************************************** - * - * basic constants and datatype definitions - * - *****************************************************************************/ -using arg_index = int; - -using arg_string = std::string; -using doc_string = std::string; - -using arg_list = std::vector; - - - -/*************************************************************************//** - * - * @brief tristate - * - *****************************************************************************/ -enum class tri : char { no, yes, either }; - -inline constexpr bool operator == (tri t, bool b) noexcept { - return b ? t != tri::no : t != tri::yes; -} -inline constexpr bool operator == (bool b, tri t) noexcept { return (t == b); } -inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); } -inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); } - - - -/*************************************************************************//** - * - * @brief (start,size) index range - * - *****************************************************************************/ -class subrange { -public: - using size_type = arg_string::size_type; - - /** @brief default: no match */ - explicit constexpr - subrange() noexcept : - at_{arg_string::npos}, length_{0} - {} - - /** @brief match length & position within subject string */ - explicit constexpr - subrange(size_type pos, size_type len) noexcept : - at_{pos}, length_{len} - {} - - /** @brief position of the match within the subject string */ - constexpr size_type at() const noexcept { return at_; } - /** @brief length of the matching subsequence */ - constexpr size_type length() const noexcept { return length_; } - - /** @brief returns true, if query string is a prefix of the subject string */ - constexpr bool prefix() const noexcept { - return at_ == 0; - } - - /** @brief returns true, if query is a substring of the query string */ - constexpr explicit operator bool () const noexcept { - return at_ != arg_string::npos; - } - -private: - size_type at_; - size_type length_; -}; - - - -/*************************************************************************//** - * - * @brief match predicates - * - *****************************************************************************/ -using match_predicate = std::function; -using match_function = std::function; - - - - - - -/*************************************************************************//** - * - * @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!) - * no interface guarantees; might be changed or removed in the future - * - *****************************************************************************/ -namespace traits { - -/*************************************************************************//** - * - * @brief function (class) signature type trait - * - *****************************************************************************/ -template -constexpr auto -check_is_callable(int) -> decltype( - std::declval()(std::declval()...), - std::integral_constant::type>::value>{} ); - -template -constexpr auto -check_is_callable(long) -> std::false_type; - -template -constexpr auto -check_is_callable_without_arg(int) -> decltype( - std::declval()(), - std::integral_constant::type>::value>{} ); - -template -constexpr auto -check_is_callable_without_arg(long) -> std::false_type; - - - -template -constexpr auto -check_is_void_callable(int) -> decltype( - std::declval()(std::declval()...), std::true_type{}); - -template -constexpr auto -check_is_void_callable(long) -> std::false_type; - -template -constexpr auto -check_is_void_callable_without_arg(int) -> decltype( - std::declval()(), std::true_type{}); - -template -constexpr auto -check_is_void_callable_without_arg(long) -> std::false_type; - - - -template -struct is_callable; - - -template -struct is_callable : - decltype(check_is_callable(0)) -{}; - -template -struct is_callable : - decltype(check_is_callable_without_arg(0)) -{}; - - -template -struct is_callable : - decltype(check_is_void_callable(0)) -{}; - -template -struct is_callable : - decltype(check_is_void_callable_without_arg(0)) -{}; - - - -/*************************************************************************//** - * - * @brief input range type trait - * - *****************************************************************************/ -template -constexpr auto -check_is_input_range(int) -> decltype( - begin(std::declval()), end(std::declval()), - std::true_type{}); - -template -constexpr auto -check_is_input_range(char) -> decltype( - std::begin(std::declval()), std::end(std::declval()), - std::true_type{}); - -template -constexpr auto -check_is_input_range(long) -> std::false_type; - -template -struct is_input_range : - decltype(check_is_input_range(0)) -{}; - - - -/*************************************************************************//** - * - * @brief size() member type trait - * - *****************************************************************************/ -template -constexpr auto -check_has_size_getter(int) -> - decltype(std::declval().size(), std::true_type{}); - -template -constexpr auto -check_has_size_getter(long) -> std::false_type; - -template -struct has_size_getter : - decltype(check_has_size_getter(0)) -{}; - -} // namespace traits - - - - - - -/*************************************************************************//** - * - * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) - * no interface guarantees; might be changed or removed in the future - * - *****************************************************************************/ -namespace detail { - - -/*************************************************************************//** - * @brief forwards string to first non-whitespace char; - * std string -> unsigned conv yields max value, but we want 0; - * also checks for nullptr - *****************************************************************************/ -inline bool -fwd_to_unsigned_int(const char*& s) -{ - if(!s) return false; - for(; std::isspace(*s); ++s); - if(!s[0] || s[0] == '-') return false; - if(s[0] == '-') return false; - return true; -} - - -/*************************************************************************//** - * - * @brief value limits clamping - * - *****************************************************************************/ -template sizeof(T))> -struct limits_clamped { - static T from(const V& v) { - if(v >= V(std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - if(v <= V(std::numeric_limits::lowest())) { - return std::numeric_limits::lowest(); - } - return T(v); - } -}; - -template -struct limits_clamped { - static T from(const V& v) { return T(v); } -}; - - -/*************************************************************************//** - * - * @brief returns value of v as a T, clamped at T's maximum - * - *****************************************************************************/ -template -inline T clamped_on_limits(const V& v) { - return limits_clamped::from(v); -} - - - - -/*************************************************************************//** - * - * @brief type conversion helpers - * - *****************************************************************************/ -template -struct make { - static inline T from(const char* s) { - if(!s) return false; - //a conversion from const char* to / must exist - return static_cast(s); - } -}; - -template<> -struct make { - static inline bool from(const char* s) { - if(!s) return false; - return static_cast(s); - } -}; - -template<> -struct make { - static inline unsigned char from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned short int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned long int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline unsigned long long int from(const char* s) { - if(!fwd_to_unsigned_int(s)) return (0); - return clamped_on_limits(std::strtoull(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline char from(const char* s) { - //parse as single character? - const auto n = std::strlen(s); - if(n == 1) return s[0]; - //parse as integer - return clamped_on_limits(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline short int from(const char* s) { - return clamped_on_limits(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline int from(const char* s) { - return clamped_on_limits(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline long int from(const char* s) { - return clamped_on_limits(std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline long long int from(const char* s) { - return (std::strtoll(s,nullptr,10)); - } -}; - -template<> -struct make { - static inline float from(const char* s) { - return (std::strtof(s,nullptr)); - } -}; - -template<> -struct make { - static inline double from(const char* s) { - return (std::strtod(s,nullptr)); - } -}; - -template<> -struct make { - static inline long double from(const char* s) { - return (std::strtold(s,nullptr)); - } -}; - -template<> -struct make { - static inline std::string from(const char* s) { - return std::string(s); - } -}; - - - -/*************************************************************************//** - * - * @brief assigns boolean constant to one or multiple target objects - * - *****************************************************************************/ -template -class assign_value -{ -public: - template - explicit constexpr - assign_value(T& target, X&& value) noexcept : - t_{std::addressof(target)}, v_{std::forward(value)} - {} - - void operator () () const { - if(t_) *t_ = v_; - } - -private: - T* t_; - V v_; -}; - - - -/*************************************************************************//** - * - * @brief flips bools - * - *****************************************************************************/ -class flip_bool -{ -public: - explicit constexpr - flip_bool(bool& target) noexcept : - b_{&target} - {} - - void operator () () const { - if(b_) *b_ = !*b_; - } - -private: - bool* b_; -}; - - - -/*************************************************************************//** - * - * @brief increments using operator ++ - * - *****************************************************************************/ -template -class increment -{ -public: - explicit constexpr - increment(T& target) noexcept : t_{std::addressof(target)} {} - - void operator () () const { - if(t_) ++(*t_); - } - -private: - T* t_; -}; - - - -/*************************************************************************//** - * - * @brief decrements using operator -- - * - *****************************************************************************/ -template -class decrement -{ -public: - explicit constexpr - decrement(T& target) noexcept : t_{std::addressof(target)} {} - - void operator () () const { - if(t_) --(*t_); - } - -private: - T* t_; -}; - - - -/*************************************************************************//** - * - * @brief increments by a fixed amount using operator += - * - *****************************************************************************/ -template -class increment_by -{ -public: - explicit constexpr - increment_by(T& target, T by) noexcept : - t_{std::addressof(target)}, by_{std::move(by)} - {} - - void operator () () const { - if(t_) (*t_) += by_; - } - -private: - T* t_; - T by_; -}; - - - - -/*************************************************************************//** - * - * @brief makes a value from a string and assigns it to an object - * - *****************************************************************************/ -template -class map_arg_to -{ -public: - explicit constexpr - map_arg_to(T& target) noexcept : t_{std::addressof(target)} {} - - void operator () (const char* s) const { - if(t_ && s) *t_ = detail::make::from(s); - } - -private: - T* t_; -}; - - -//------------------------------------------------------------------- -/** - * @brief specialization for vectors: append element - */ -template -class map_arg_to> -{ -public: - map_arg_to(std::vector& target): t_{std::addressof(target)} {} - - void operator () (const char* s) const { - if(t_ && s) t_->push_back(detail::make::from(s)); - } - -private: - std::vector* t_; -}; - - -//------------------------------------------------------------------- -/** - * @brief specialization for bools: - * set to true regardless of string content - */ -template<> -class map_arg_to -{ -public: - map_arg_to(bool& target): t_{&target} {} - - void operator () (const char* s) const { - if(t_ && s) *t_ = true; - } - -private: - bool* t_; -}; - - -} // namespace detail - - - - - - -/*************************************************************************//** - * - * @brief string matching and processing tools - * - *****************************************************************************/ - -namespace str { - - -/*************************************************************************//** - * - * @brief converts string to value of target type 'T' - * - *****************************************************************************/ -template -T make(const arg_string& s) -{ - return detail::make::from(s); -} - - - -/*************************************************************************//** - * - * @brief removes trailing whitespace from string - * - *****************************************************************************/ -template -inline void -trimr(std::basic_string& s) -{ - if(s.empty()) return; - - s.erase( - std::find_if_not(s.rbegin(), s.rend(), - [](char c) { return std::isspace(c);} ).base(), - s.end() ); -} - - -/*************************************************************************//** - * - * @brief removes leading whitespace from string - * - *****************************************************************************/ -template -inline void -triml(std::basic_string& s) -{ - if(s.empty()) return; - - s.erase( - s.begin(), - std::find_if_not(s.begin(), s.end(), - [](char c) { return std::isspace(c);}) - ); -} - - -/*************************************************************************//** - * - * @brief removes leading and trailing whitespace from string - * - *****************************************************************************/ -template -inline void -trim(std::basic_string& s) -{ - triml(s); - trimr(s); -} - - -/*************************************************************************//** - * - * @brief removes all whitespaces from string - * - *****************************************************************************/ -template -inline void -remove_ws(std::basic_string& s) -{ - if(s.empty()) return; - - s.erase(std::remove_if(s.begin(), s.end(), - [](char c) { return std::isspace(c); }), - s.end() ); -} - - -/*************************************************************************//** - * - * @brief returns true, if the 'prefix' argument - * is a prefix of the 'subject' argument - * - *****************************************************************************/ -template -inline bool -has_prefix(const std::basic_string& subject, - const std::basic_string& prefix) -{ - if(prefix.size() > subject.size()) return false; - return subject.find(prefix) == 0; -} - - -/*************************************************************************//** - * - * @brief returns true, if the 'postfix' argument - * is a postfix of the 'subject' argument - * - *****************************************************************************/ -template -inline bool -has_postfix(const std::basic_string& subject, - const std::basic_string& postfix) -{ - if(postfix.size() > subject.size()) return false; - return (subject.size() - postfix.size()) == subject.find(postfix); -} - - - -/*************************************************************************//** -* -* @brief returns longest common prefix of several -* sequential random access containers -* -* @details InputRange require begin and end (member functions or overloads) -* the elements of InputRange require a size() member -* -*****************************************************************************/ -template -auto -longest_common_prefix(const InputRange& strs) - -> typename std::decay::type -{ - static_assert(traits::is_input_range(), - "parameter must satisfy the InputRange concept"); - - static_assert(traits::has_size_getter< - typename std::decay::type>(), - "elements of input range must have a ::size() member function"); - - using std::begin; - using std::end; - - using item_t = typename std::decay::type; - using str_size_t = typename std::decaysize())>::type; - - const auto n = size_t(distance(begin(strs), end(strs))); - if(n < 1) return item_t(""); - if(n == 1) return *begin(strs); - - //length of shortest string - auto m = std::min_element(begin(strs), end(strs), - [](const item_t& a, const item_t& b) { - return a.size() < b.size(); })->size(); - - //check each character until we find a mismatch - for(str_size_t i = 0; i < m; ++i) { - for(str_size_t j = 1; j < n; ++j) { - if(strs[j][i] != strs[j-1][i]) - return strs[0].substr(0, i); - } - } - return strs[0].substr(0, m); -} - - - -/*************************************************************************//** - * - * @brief returns longest substring range that could be found in 'arg' - * - * @param arg string to be searched in - * @param substrings range of candidate substrings - * - *****************************************************************************/ -template -subrange -longest_substring_match(const std::basic_string& arg, - const InputRange& substrings) -{ - using string_t = std::basic_string; - - static_assert(traits::is_input_range(), - "parameter must satisfy the InputRange concept"); - - static_assert(std::is_same::type>(), - "substrings must have same type as 'arg'"); - - auto i = string_t::npos; - auto n = string_t::size_type(0); - for(const auto& s : substrings) { - auto j = arg.find(s); - if(j != string_t::npos && s.size() > n) { - i = j; - n = s.size(); - } - } - return subrange{i,n}; -} - - - -/*************************************************************************//** - * - * @brief returns longest prefix range that could be found in 'arg' - * - * @param arg string to be searched in - * @param prefixes range of candidate prefix strings - * - *****************************************************************************/ -template -subrange -longest_prefix_match(const std::basic_string& arg, - const InputRange& prefixes) -{ - using string_t = std::basic_string; - using s_size_t = typename string_t::size_type; - - static_assert(traits::is_input_range(), - "parameter must satisfy the InputRange concept"); - - static_assert(std::is_same::type>(), - "prefixes must have same type as 'arg'"); - - auto i = string_t::npos; - auto n = s_size_t(0); - for(const auto& s : prefixes) { - auto j = arg.find(s); - if(j == 0 && s.size() > n) { - i = 0; - n = s.size(); - } - } - return subrange{i,n}; -} - - - -/*************************************************************************//** - * - * @brief returns the first occurrence of 'query' within 'subject' - * - *****************************************************************************/ -template -inline subrange -substring_match(const std::basic_string& subject, - const std::basic_string& query) -{ - if(subject.empty() && query.empty()) return subrange(0,0); - if(subject.empty() || query.empty()) return subrange{}; - auto i = subject.find(query); - if(i == std::basic_string::npos) return subrange{}; - return subrange{i,query.size()}; -} - - - -/*************************************************************************//** - * - * @brief returns first substring match (pos,len) within the input string - * that represents a number - * (with at maximum one decimal point and digit separators) - * - *****************************************************************************/ -template -subrange -first_number_match(std::basic_string s, - C digitSeparator = C(','), - C decimalPoint = C('.'), - C exponential = C('e')) -{ - using string_t = std::basic_string; - - str::trim(s); - if(s.empty()) return subrange{}; - - auto i = s.find_first_of("0123456789+-"); - if(i == string_t::npos) { - i = s.find(decimalPoint); - if(i == string_t::npos) return subrange{}; - } - - bool point = false; - bool sep = false; - auto exp = string_t::npos; - auto j = i + 1; - for(; j < s.size(); ++j) { - if(s[j] == digitSeparator) { - if(!sep) sep = true; else break; - } - else { - sep = false; - if(s[j] == decimalPoint) { - //only one decimal point before exponent allowed - if(!point && exp == string_t::npos) point = true; else break; - } - else if(std::tolower(s[j]) == std::tolower(exponential)) { - //only one exponent separator allowed - if(exp == string_t::npos) exp = j; else break; - } - else if(exp != string_t::npos && (exp+1) == j) { - //only sign or digit after exponent separator - if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break; - } - else if(!std::isdigit(s[j])) { - break; - } - } - } - - //if length == 1 then must be a digit - if(j-i == 1 && !std::isdigit(s[i])) return subrange{}; - - return subrange{i,j-i}; -} - - - -/*************************************************************************//** - * - * @brief returns first substring match (pos,len) - * that represents an integer (with optional digit separators) - * - *****************************************************************************/ -template -subrange -first_integer_match(std::basic_string s, - C digitSeparator = C(',')) -{ - using string_t = std::basic_string; - - str::trim(s); - if(s.empty()) return subrange{}; - - auto i = s.find_first_of("0123456789+-"); - if(i == string_t::npos) return subrange{}; - - bool sep = false; - auto j = i + 1; - for(; j < s.size(); ++j) { - if(s[j] == digitSeparator) { - if(!sep) sep = true; else break; - } - else { - sep = false; - if(!std::isdigit(s[j])) break; - } - } - - //if length == 1 then must be a digit - if(j-i == 1 && !std::isdigit(s[i])) return subrange{}; - - return subrange{i,j-i}; -} - - - -/*************************************************************************//** - * - * @brief returns true if candidate string represents a number - * - *****************************************************************************/ -template -bool represents_number(const std::basic_string& candidate, - C digitSeparator = C(','), - C decimalPoint = C('.'), - C exponential = C('e')) -{ - const auto match = str::first_number_match(candidate, digitSeparator, - decimalPoint, exponential); - - return (match && match.length() == candidate.size()); -} - - - -/*************************************************************************//** - * - * @brief returns true if candidate string represents an integer - * - *****************************************************************************/ -template -bool represents_integer(const std::basic_string& candidate, - C digitSeparator = C(',')) -{ - const auto match = str::first_integer_match(candidate, digitSeparator); - return (match && match.length() == candidate.size()); -} - -} // namespace str - - - - - - -/*************************************************************************//** - * - * @brief makes function object with a const char* parameter - * that assigns a value to a ref-captured object - * - *****************************************************************************/ -template -inline detail::assign_value -set(T& target, V value) { - return detail::assign_value{target, std::move(value)}; -} - - - -/*************************************************************************//** - * - * @brief makes parameter-less function object - * that assigns value(s) to a ref-captured object; - * value(s) are obtained by converting the const char* argument to - * the captured object types; - * bools are always set to true if the argument is not nullptr - * - *****************************************************************************/ -template -inline detail::map_arg_to -set(T& target) { - return detail::map_arg_to{target}; -} - - - -/*************************************************************************//** - * - * @brief makes function object that sets a bool to true - * - *****************************************************************************/ -inline detail::assign_value -set(bool& target) { - return detail::assign_value{target,true}; -} - -/*************************************************************************//** - * - * @brief makes function object that sets a bool to false - * - *****************************************************************************/ -inline detail::assign_value -unset(bool& target) { - return detail::assign_value{target,false}; -} - -/*************************************************************************//** - * - * @brief makes function object that flips the value of a ref-captured bool - * - *****************************************************************************/ -inline detail::flip_bool -flip(bool& b) { - return detail::flip_bool(b); -} - - - - - -/*************************************************************************//** - * - * @brief makes function object that increments using operator ++ - * - *****************************************************************************/ -template -inline detail::increment -increment(T& target) { - return detail::increment{target}; -} - -/*************************************************************************//** - * - * @brief makes function object that decrements using operator -- - * - *****************************************************************************/ -template -inline detail::increment_by -increment(T& target, T by) { - return detail::increment_by{target, std::move(by)}; -} - -/*************************************************************************//** - * - * @brief makes function object that increments by a fixed amount using operator += - * - *****************************************************************************/ -template -inline detail::decrement -decrement(T& target) { - return detail::decrement{target}; -} - - - - - - -/*************************************************************************//** - * - * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) - * - *****************************************************************************/ -namespace detail { - - -/*************************************************************************//** - * - * @brief mixin that provides action definition and execution - * - *****************************************************************************/ -template -class action_provider -{ -private: - //--------------------------------------------------------------- - using simple_action = std::function; - using arg_action = std::function; - using index_action = std::function; - - //----------------------------------------------------- - class simple_action_adapter { - public: - simple_action_adapter() = default; - simple_action_adapter(const simple_action& a): action_(a) {} - simple_action_adapter(simple_action&& a): action_(std::move(a)) {} - void operator() (const char*) const { action_(); } - void operator() (int) const { action_(); } - private: - simple_action action_; - }; - - -public: - //--------------------------------------------------------------- - /** @brief adds an action that has an operator() that is callable - * with a 'const char*' argument */ - Derived& - call(arg_action a) { - argActions_.push_back(std::move(a)); - return *static_cast(this); - } - - /** @brief adds an action that has an operator()() */ - Derived& - call(simple_action a) { - argActions_.push_back(simple_action_adapter(std::move(a))); - return *static_cast(this); - } - - /** @brief adds an action that has an operator() that is callable - * with a 'const char*' argument */ - Derived& operator () (arg_action a) { return call(std::move(a)); } - - /** @brief adds an action that has an operator()() */ - Derived& operator () (simple_action a) { return call(std::move(a)); } - - - //--------------------------------------------------------------- - /** @brief adds an action that will set the value of 't' from - * a 'const char*' arg */ - template - Derived& - set(Target& t) { - static_assert(!std::is_pointer::value, - "parameter target type must not be a pointer"); - - return call(clipp::set(t)); - } - - /** @brief adds an action that will set the value of 't' to 'v' */ - template - Derived& - set(Target& t, Value&& v) { - return call(clipp::set(t, std::forward(v))); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a parameter - * matches an argument for the 2nd, 3rd, 4th, ... time - */ - Derived& - if_repeated(simple_action a) { - repeatActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast(this); - } - /** @brief adds an action that will be called with the argument's - * index if a parameter matches an argument for - * the 2nd, 3rd, 4th, ... time - */ - Derived& - if_repeated(index_action a) { - repeatActions_.push_back(std::move(a)); - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a required parameter - * is missing - */ - Derived& - if_missing(simple_action a) { - missingActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast(this); - } - /** @brief adds an action that will be called if a required parameter - * is missing; the action will get called with the index of - * the command line argument where the missing event occurred first - */ - Derived& - if_missing(index_action a) { - missingActions_.push_back(std::move(a)); - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a parameter - * was matched, but was unreachable in the current scope - */ - Derived& - if_blocked(simple_action a) { - blockedActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast(this); - } - /** @brief adds an action that will be called if a parameter - * was matched, but was unreachable in the current scope; - * the action will be called with the index of - * the command line argument where the problem occurred - */ - Derived& - if_blocked(index_action a) { - blockedActions_.push_back(std::move(a)); - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief adds an action that will be called if a parameter match - * was in conflict with a different alternative parameter - */ - Derived& - if_conflicted(simple_action a) { - conflictActions_.push_back(simple_action_adapter{std::move(a)}); - return *static_cast(this); - } - /** @brief adds an action that will be called if a parameter match - * was in conflict with a different alternative parameter; - * the action will be called with the index of - * the command line argument where the problem occurred - */ - Derived& - if_conflicted(index_action a) { - conflictActions_.push_back(std::move(a)); - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief adds targets = either objects whose values should be - * set by command line arguments or actions that should - * be called in case of a match */ - template - Derived& - target(T&& t, Ts&&... ts) { - target(std::forward(t)); - target(std::forward(ts)...); - return *static_cast(this); - } - - /** @brief adds action that should be called in case of a match */ - template::type>() && - (traits::is_callable() || - traits::is_callable() ) - >::type> - Derived& - target(T&& t) { - call(std::forward(t)); - return *static_cast(this); - } - - /** @brief adds object whose value should be set by command line arguments - */ - template::type>() || - (!traits::is_callable() && - !traits::is_callable() ) - >::type> - Derived& - target(T& t) { - set(t); - return *static_cast(this); - } - - //TODO remove ugly empty param list overload - Derived& - target() { - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief adds target, see member function 'target' */ - template - inline friend Derived& - operator << (Target&& t, Derived& p) { - p.target(std::forward(t)); - return p; - } - /** @brief adds target, see member function 'target' */ - template - inline friend Derived&& - operator << (Target&& t, Derived&& p) { - p.target(std::forward(t)); - return std::move(p); - } - - //----------------------------------------------------- - /** @brief adds target, see member function 'target' */ - template - inline friend Derived& - operator >> (Derived& p, Target&& t) { - p.target(std::forward(t)); - return p; - } - /** @brief adds target, see member function 'target' */ - template - inline friend Derived&& - operator >> (Derived&& p, Target&& t) { - p.target(std::forward(t)); - return std::move(p); - } - - - //--------------------------------------------------------------- - /** @brief executes all argument actions */ - void execute_actions(const arg_string& arg) const { - for(const auto& a : argActions_) { - a(arg.c_str()); - } - } - - /** @brief executes repeat actions */ - void notify_repeated(arg_index idx) const { - for(const auto& a : repeatActions_) a(idx); - } - /** @brief executes missing error actions */ - void notify_missing(arg_index idx) const { - for(const auto& a : missingActions_) a(idx); - } - /** @brief executes blocked error actions */ - void notify_blocked(arg_index idx) const { - for(const auto& a : blockedActions_) a(idx); - } - /** @brief executes conflict error actions */ - void notify_conflict(arg_index idx) const { - for(const auto& a : conflictActions_) a(idx); - } - -private: - //--------------------------------------------------------------- - std::vector argActions_; - std::vector repeatActions_; - std::vector missingActions_; - std::vector blockedActions_; - std::vector conflictActions_; -}; - - - - - - -/*************************************************************************//** - * - * @brief mixin that provides basic common settings of parameters and groups - * - *****************************************************************************/ -template -class token -{ -public: - //--------------------------------------------------------------- - using doc_string = clipp::doc_string; - - - //--------------------------------------------------------------- - /** @brief returns documentation string */ - const doc_string& doc() const noexcept { - return doc_; - } - - /** @brief sets documentations string */ - Derived& doc(const doc_string& txt) { - doc_ = txt; - return *static_cast(this); - } - - /** @brief sets documentations string */ - Derived& doc(doc_string&& txt) { - doc_ = std::move(txt); - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief returns if a group/parameter is repeatable */ - bool repeatable() const noexcept { - return repeatable_; - } - - /** @brief sets repeatability of group/parameter */ - Derived& repeatable(bool yes) noexcept { - repeatable_ = yes; - return *static_cast(this); - } - - - //--------------------------------------------------------------- - /** @brief returns if a group/parameter is blocking/positional */ - bool blocking() const noexcept { - return blocking_; - } - - /** @brief determines, if a group/parameter is blocking/positional */ - Derived& blocking(bool yes) noexcept { - blocking_ = yes; - return *static_cast(this); - } - - -private: - //--------------------------------------------------------------- - doc_string doc_; - bool repeatable_ = false; - bool blocking_ = false; -}; - - - - -/*************************************************************************//** - * - * @brief sets documentation strings on a token - * - *****************************************************************************/ -template -inline T& -operator % (doc_string docstr, token& p) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template -inline T&& -operator % (doc_string docstr, token&& p) -{ - return std::move(p.doc(std::move(docstr))); -} - -//--------------------------------------------------------- -template -inline T& -operator % (token& p, doc_string docstr) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template -inline T&& -operator % (token&& p, doc_string docstr) -{ - return std::move(p.doc(std::move(docstr))); -} - - - - -/*************************************************************************//** - * - * @brief sets documentation strings on a token - * - *****************************************************************************/ -template -inline T& -doc(doc_string docstr, token& p) -{ - return p.doc(std::move(docstr)); -} -//--------------------------------------------------------- -template -inline T&& -doc(doc_string docstr, token&& p) -{ - return std::move(p.doc(std::move(docstr))); -} - - - -} // namespace detail - - - -/*************************************************************************//** - * - * @brief contains parameter matching functions and function classes - * - *****************************************************************************/ -namespace match { - - -/*************************************************************************//** - * - * @brief predicate that is always true - * - *****************************************************************************/ -inline bool -any(const arg_string&) { return true; } - -/*************************************************************************//** - * - * @brief predicate that is always false - * - *****************************************************************************/ -inline bool -none(const arg_string&) { return false; } - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the argument string is non-empty string - * - *****************************************************************************/ -inline bool -nonempty(const arg_string& s) { - return !s.empty(); -} - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the argument is a non-empty - * string that consists only of alphanumeric characters - * - *****************************************************************************/ -inline bool -alphanumeric(const arg_string& s) { - if(s.empty()) return false; - return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); }); -} - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the argument is a non-empty - * string that consists only of alphabetic characters - * - *****************************************************************************/ -inline bool -alphabetic(const arg_string& s) { - return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); }); -} - - - -/*************************************************************************//** - * - * @brief predicate that returns false if the argument string is - * equal to any string from the exclusion list - * - *****************************************************************************/ -class none_of -{ -public: - none_of(arg_list strs): - excluded_{std::move(strs)} - {} - - template - none_of(arg_string str, Strings&&... strs): - excluded_{std::move(str), std::forward(strs)...} - {} - - template - none_of(const char* str, Strings&&... strs): - excluded_{arg_string(str), std::forward(strs)...} - {} - - bool operator () (const arg_string& arg) const { - return (std::find(begin(excluded_), end(excluded_), arg) - == end(excluded_)); - } - -private: - arg_list excluded_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns the first substring match within the input - * string that rmeepresents a number - * (with at maximum one decimal point and digit separators) - * - *****************************************************************************/ -class numbers -{ -public: - explicit - numbers(char decimalPoint = '.', - char digitSeparator = ' ', - char exponentSeparator = 'e') - : - decpoint_{decimalPoint}, separator_{digitSeparator}, - exp_{exponentSeparator} - {} - - subrange operator () (const arg_string& s) const { - return str::first_number_match(s, separator_, decpoint_, exp_); - } - -private: - char decpoint_; - char separator_; - char exp_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string represents an integer - * (with optional digit separators) - * - *****************************************************************************/ -class integers { -public: - explicit - integers(char digitSeparator = ' '): separator_{digitSeparator} {} - - subrange operator () (const arg_string& s) const { - return str::first_integer_match(s, separator_); - } - -private: - char separator_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string represents - * a non-negative integer (with optional digit separators) - * - *****************************************************************************/ -class positive_integers { -public: - explicit - positive_integers(char digitSeparator = ' '): separator_{digitSeparator} {} - - subrange operator () (const arg_string& s) const { - auto match = str::first_integer_match(s, separator_); - if(!match) return subrange{}; - if(s[match.at()] == '-') return subrange{}; - return match; - } - -private: - char separator_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string - * contains a given substring - * - *****************************************************************************/ -class substring -{ -public: - explicit - substring(arg_string str): str_{std::move(str)} {} - - subrange operator () (const arg_string& s) const { - return str::substring_match(s, str_); - } - -private: - arg_string str_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string starts - * with a given prefix - * - *****************************************************************************/ -class prefix { -public: - explicit - prefix(arg_string p): prefix_{std::move(p)} {} - - bool operator () (const arg_string& s) const { - return s.find(prefix_) == 0; - } - -private: - arg_string prefix_; -}; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the input string does not start - * with a given prefix - * - *****************************************************************************/ -class prefix_not { -public: - explicit - prefix_not(arg_string p): prefix_{std::move(p)} {} - - bool operator () (const arg_string& s) const { - return s.find(prefix_) != 0; - } - -private: - arg_string prefix_; -}; - - -/** @brief alias for prefix_not */ -using noprefix = prefix_not; - - - -/*************************************************************************//** - * - * @brief predicate that returns true if the length of the input string - * is wihtin a given interval - * - *****************************************************************************/ -class length { -public: - explicit - length(std::size_t exact): - min_{exact}, max_{exact} - {} - - explicit - length(std::size_t min, std::size_t max): - min_{min}, max_{max} - {} - - bool operator () (const arg_string& s) const { - return s.size() >= min_ && s.size() <= max_; - } - -private: - std::size_t min_; - std::size_t max_; -}; - - -/*************************************************************************//** - * - * @brief makes function object that returns true if the input string has a - * given minimum length - * - *****************************************************************************/ -inline length min_length(std::size_t min) -{ - return length{min, arg_string::npos-1}; -} - -/*************************************************************************//** - * - * @brief makes function object that returns true if the input string is - * not longer than a given maximum length - * - *****************************************************************************/ -inline length max_length(std::size_t max) -{ - return length{0, max}; -} - - -} // namespace match - - - - - -/*************************************************************************//** - * - * @brief command line parameter that can match one or many arguments. - * - *****************************************************************************/ -class parameter : - public detail::token, - public detail::action_provider -{ - /** @brief adapts a 'match_predicate' to the 'match_function' interface */ - class predicate_adapter { - public: - explicit - predicate_adapter(match_predicate pred): match_{std::move(pred)} {} - - subrange operator () (const arg_string& arg) const { - return match_(arg) ? subrange{0,arg.size()} : subrange{}; - } - - private: - match_predicate match_; - }; - -public: - //--------------------------------------------------------------- - /** @brief makes default parameter, that will match nothing */ - parameter(): - flags_{}, - matcher_{predicate_adapter{match::none}}, - label_{}, required_{false}, greedy_{false} - {} - - /** @brief makes "flag" parameter */ - template - explicit - parameter(arg_string str, Strings&&... strs): - flags_{}, - matcher_{predicate_adapter{match::none}}, - label_{}, required_{false}, greedy_{false} - { - add_flags(std::move(str), std::forward(strs)...); - } - - /** @brief makes "flag" parameter from range of strings */ - explicit - parameter(const arg_list& flaglist): - flags_{}, - matcher_{predicate_adapter{match::none}}, - label_{}, required_{false}, greedy_{false} - { - add_flags(flaglist); - } - - //----------------------------------------------------- - /** @brief makes "value" parameter with custom match predicate - * (= yes/no matcher) - */ - explicit - parameter(match_predicate filter): - flags_{}, - matcher_{predicate_adapter{std::move(filter)}}, - label_{}, required_{false}, greedy_{false} - {} - - /** @brief makes "value" parameter with custom match function - * (= partial matcher) - */ - explicit - parameter(match_function filter): - flags_{}, - matcher_{std::move(filter)}, - label_{}, required_{false}, greedy_{false} - {} - - - //--------------------------------------------------------------- - /** @brief returns if a parameter is required */ - bool - required() const noexcept { - return required_; - } - - /** @brief determines if a parameter is required */ - parameter& - required(bool yes) noexcept { - required_ = yes; - return *this; - } - - - //--------------------------------------------------------------- - /** @brief returns if a parameter should match greedily */ - bool - greedy() const noexcept { - return greedy_; - } - - /** @brief determines if a parameter should match greedily */ - parameter& - greedy(bool yes) noexcept { - greedy_ = yes; - return *this; - } - - - //--------------------------------------------------------------- - /** @brief returns parameter label; - * will be used for documentation, if flags are empty - */ - const doc_string& - label() const { - return label_; - } - - /** @brief sets parameter label; - * will be used for documentation, if flags are empty - */ - parameter& - label(const doc_string& lbl) { - label_ = lbl; - return *this; - } - - /** @brief sets parameter label; - * will be used for documentation, if flags are empty - */ - parameter& - label(doc_string&& lbl) { - label_ = lbl; - return *this; - } - - - //--------------------------------------------------------------- - /** @brief returns either longest matching prefix of 'arg' in any - * of the flags or the result of the custom match operation - */ - subrange - match(const arg_string& arg) const - { - if(flags_.empty()) { - return matcher_(arg); - } - else { - //empty flags are not allowed - if(arg.empty()) return subrange{}; - - if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) { - return subrange{0,arg.size()}; - } - return str::longest_prefix_match(arg, flags_); - } - } - - - //--------------------------------------------------------------- - /** @brief access range of flag strings */ - const arg_list& - flags() const noexcept { - return flags_; - } - - /** @brief access custom match operation */ - const match_function& - matcher() const noexcept { - return matcher_; - } - - - //--------------------------------------------------------------- - /** @brief prepend prefix to each flag */ - inline friend parameter& - with_prefix(const arg_string& prefix, parameter& p) - { - if(prefix.empty() || p.flags().empty()) return p; - - for(auto& f : p.flags_) { - if(f.find(prefix) != 0) f.insert(0, prefix); - } - return p; - } - - - /** @brief prepend prefix to each flag - */ - inline friend parameter& - with_prefixes_short_long( - const arg_string& shortpfx, const arg_string& longpfx, - parameter& p) - { - if(shortpfx.empty() && longpfx.empty()) return p; - if(p.flags().empty()) return p; - - for(auto& f : p.flags_) { - if(f.size() == 1) { - if(f.find(shortpfx) != 0) f.insert(0, shortpfx); - } else { - if(f.find(longpfx) != 0) f.insert(0, longpfx); - } - } - return p; - } - - - //--------------------------------------------------------------- - /** @brief prepend suffix to each flag */ - inline friend parameter& - with_suffix(const arg_string& suffix, parameter& p) - { - if(suffix.empty() || p.flags().empty()) return p; - - for(auto& f : p.flags_) { - if(f.find(suffix) + suffix.size() != f.size()) { - f.insert(f.end(), suffix.begin(), suffix.end()); - } - } - return p; - } - - - /** @brief prepend suffix to each flag - */ - inline friend parameter& - with_suffixes_short_long( - const arg_string& shortsfx, const arg_string& longsfx, - parameter& p) - { - if(shortsfx.empty() && longsfx.empty()) return p; - if(p.flags().empty()) return p; - - for(auto& f : p.flags_) { - if(f.size() == 1) { - if(f.find(shortsfx) + shortsfx.size() != f.size()) { - f.insert(f.end(), shortsfx.begin(), shortsfx.end()); - } - } else { - if(f.find(longsfx) + longsfx.size() != f.size()) { - f.insert(f.end(), longsfx.begin(), longsfx.end()); - } - } - } - return p; - } - -private: - //--------------------------------------------------------------- - void add_flags(arg_string str) { - //empty flags are not allowed - str::remove_ws(str); - if(!str.empty()) flags_.push_back(std::move(str)); - } - - //--------------------------------------------------------------- - void add_flags(const arg_list& strs) { - if(strs.empty()) return; - flags_.reserve(flags_.size() + strs.size()); - for(const auto& s : strs) add_flags(s); - } - - template - void - add_flags(String1&& s1, String2&& s2, Strings&&... ss) { - flags_.reserve(2 + sizeof...(ss)); - add_flags(std::forward(s1)); - add_flags(std::forward(s2), std::forward(ss)...); - } - - arg_list flags_; - match_function matcher_; - doc_string label_; - bool required_ = false; - bool greedy_ = false; -}; - - - - -/*************************************************************************//** - * - * @brief makes required non-blocking exact match parameter - * - *****************************************************************************/ -template -inline parameter -command(String&& flag, Strings&&... flags) -{ - return parameter{std::forward(flag), std::forward(flags)...} - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required non-blocking exact match parameter - * - *****************************************************************************/ -template -inline parameter -required(String&& flag, Strings&&... flags) -{ - return parameter{std::forward(flag), std::forward(flags)...} - .required(true).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, non-blocking exact match parameter - * - *****************************************************************************/ -template -inline parameter -option(String&& flag, Strings&&... flags) -{ - return parameter{std::forward(flag), std::forward(flags)...} - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -value(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -value(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -values(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -values(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -opt_value(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -opt_value(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any non-empty string - * - *****************************************************************************/ -template -inline parameter -opt_values(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::nonempty} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - -template::value || - traits::is_callable::value>::type> -inline parameter -opt_values(Filter&& filter, doc_string label, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -word(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -words(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -opt_word(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string consisting of alphanumeric characters - * - *****************************************************************************/ -template -inline parameter -opt_words(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::alphanumeric} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -number(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -numbers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -opt_number(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string that represents a number - * - *****************************************************************************/ -template -inline parameter -opt_numbers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::numbers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -integer(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes required, blocking, repeatable value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -integers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(true).blocking(true).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -opt_integer(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(false); -} - - - -/*************************************************************************//** - * - * @brief makes optional, blocking, repeatable value parameter; - * matches any string that represents an integer - * - *****************************************************************************/ -template -inline parameter -opt_integers(const doc_string& label, Targets&&... tgts) -{ - return parameter{match::integers{}} - .label(label) - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes catch-all value parameter - * - *****************************************************************************/ -template -inline parameter -any_other(Targets&&... tgts) -{ - return parameter{match::any} - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes catch-all value parameter with custom filter - * - *****************************************************************************/ -template::value || - traits::is_callable::value>::type> -inline parameter -any(Filter&& filter, Targets&&... tgts) -{ - return parameter{std::forward(filter)} - .target(std::forward(tgts)...) - .required(false).blocking(false).repeatable(true); -} - - - - -/*************************************************************************//** - * - * @brief group of parameters and/or other groups; - * can be configured to act as a group of alternatives (exclusive match) - * - *****************************************************************************/ -class group : - public detail::token -{ - //--------------------------------------------------------------- - /** - * @brief tagged union type that either stores a parameter or a group - * and provides a common interface to them - * could be replaced by std::variant in the future - * - * Note to future self: do NOT try again to do this with - * dynamic polymorphism; there are a couple of - * nasty problems associated with it and the implementation - * becomes bloated and needlessly complicated. - */ - template - struct child_t { - enum class type : char {param, group}; - public: - - explicit - child_t(const Param& v) : m_{v}, type_{type::param} {} - child_t( Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {} - - explicit - child_t(const Group& g) : m_{g}, type_{type::group} {} - child_t( Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {} - - child_t(const child_t& src): type_{src.type_} { - switch(type_) { - default: - case type::param: new(&m_)data{src.m_.param}; break; - case type::group: new(&m_)data{src.m_.group}; break; - } - } - - child_t(child_t&& src) noexcept : type_{src.type_} { - switch(type_) { - default: - case type::param: new(&m_)data{std::move(src.m_.param)}; break; - case type::group: new(&m_)data{std::move(src.m_.group)}; break; - } - } - - child_t& operator = (const child_t& src) { - destroy_content(); - type_ = src.type_; - switch(type_) { - default: - case type::param: new(&m_)data{src.m_.param}; break; - case type::group: new(&m_)data{src.m_.group}; break; - } - return *this; - } - - child_t& operator = (child_t&& src) noexcept { - destroy_content(); - type_ = src.type_; - switch(type_) { - default: - case type::param: new(&m_)data{std::move(src.m_.param)}; break; - case type::group: new(&m_)data{std::move(src.m_.group)}; break; - } - return *this; - } - - ~child_t() { - destroy_content(); - } - - const doc_string& - doc() const noexcept { - switch(type_) { - default: - case type::param: return m_.param.doc(); - case type::group: return m_.group.doc(); - } - } - - bool blocking() const noexcept { - switch(type_) { - case type::param: return m_.param.blocking(); - case type::group: return m_.group.blocking(); - default: return false; - } - } - bool repeatable() const noexcept { - switch(type_) { - case type::param: return m_.param.repeatable(); - case type::group: return m_.group.repeatable(); - default: return false; - } - } - bool required() const noexcept { - switch(type_) { - case type::param: return m_.param.required(); - case type::group: - return (m_.group.exclusive() && m_.group.all_required() ) || - (!m_.group.exclusive() && m_.group.any_required() ); - default: return false; - } - } - bool exclusive() const noexcept { - switch(type_) { - case type::group: return m_.group.exclusive(); - case type::param: - default: return false; - } - } - std::size_t param_count() const noexcept { - switch(type_) { - case type::group: return m_.group.param_count(); - case type::param: - default: return std::size_t(1); - } - } - std::size_t depth() const noexcept { - switch(type_) { - case type::group: return m_.group.depth(); - case type::param: - default: return std::size_t(0); - } - } - - void execute_actions(const arg_string& arg) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.execute_actions(arg); break; - } - - } - - void notify_repeated(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_repeated(idx); break; - } - } - void notify_missing(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_missing(idx); break; - } - } - void notify_blocked(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_blocked(idx); break; - } - } - void notify_conflict(arg_index idx) const { - switch(type_) { - default: - case type::group: return; - case type::param: m_.param.notify_conflict(idx); break; - } - } - - bool is_param() const noexcept { return type_ == type::param; } - bool is_group() const noexcept { return type_ == type::group; } - - Param& as_param() noexcept { return m_.param; } - Group& as_group() noexcept { return m_.group; } - - const Param& as_param() const noexcept { return m_.param; } - const Group& as_group() const noexcept { return m_.group; } - - private: - void destroy_content() { - switch(type_) { - default: - case type::param: m_.param.~Param(); break; - case type::group: m_.group.~Group(); break; - } - } - - union data { - data() {} - - data(const Param& v) : param{v} {} - data( Param&& v) noexcept : param{std::move(v)} {} - - data(const Group& g) : group{g} {} - data( Group&& g) noexcept : group{std::move(g)} {} - ~data() {} - - Param param; - Group group; - }; - - data m_; - type type_; - }; - - -public: - //--------------------------------------------------------------- - using child = child_t; - using value_type = child; - -private: - using children_store = std::vector; - -public: - using const_iterator = children_store::const_iterator; - using iterator = children_store::iterator; - using size_type = children_store::size_type; - - - //--------------------------------------------------------------- - /** - * @brief recursively iterates over all nodes - */ - class depth_first_traverser - { - public: - //----------------------------------------------------- - struct context { - context() = default; - context(const group& p): - parent{&p}, cur{p.begin()}, end{p.end()} - {} - const group* parent = nullptr; - const_iterator cur; - const_iterator end; - }; - using context_list = std::vector; - - //----------------------------------------------------- - class memento { - friend class depth_first_traverser; - int level_; - context context_; - public: - int level() const noexcept { return level_; } - const child* param() const noexcept { return &(*context_.cur); } - }; - - depth_first_traverser() = default; - - explicit - depth_first_traverser(const group& cur): stack_{} { - if(!cur.empty()) stack_.emplace_back(cur); - } - - explicit operator bool() const noexcept { - return !stack_.empty(); - } - - int level() const noexcept { - return int(stack_.size()); - } - - bool is_first_in_parent() const noexcept { - if(stack_.empty()) return false; - return (stack_.back().cur == stack_.back().parent->begin()); - } - - bool is_last_in_parent() const noexcept { - if(stack_.empty()) return false; - return (stack_.back().cur+1 == stack_.back().end); - } - - bool is_last_in_path() const noexcept { - if(stack_.empty()) return false; - for(const auto& t : stack_) { - if(t.cur+1 != t.end) return false; - } - const auto& top = stack_.back(); - //if we have to descend into group on next ++ => not last in path - if(top.cur->is_group()) return false; - return true; - } - - /** @brief inside a group of alternatives >= minlevel */ - bool is_alternative(int minlevel = 0) const noexcept { - if(stack_.empty()) return false; - if(minlevel > 0) minlevel -= 1; - if(minlevel >= int(stack_.size())) return false; - return std::any_of(stack_.begin() + minlevel, stack_.end(), - [](const context& c) { return c.parent->exclusive(); }); - } - - /** @brief repeatable or inside a repeatable group >= minlevel */ - bool is_repeatable(int minlevel = 0) const noexcept { - if(stack_.empty()) return false; - if(stack_.back().cur->repeatable()) return true; - if(minlevel > 0) minlevel -= 1; - if(minlevel >= int(stack_.size())) return false; - return std::any_of(stack_.begin() + minlevel, stack_.end(), - [](const context& c) { return c.parent->repeatable(); }); - } - - /** @brief inside a particular group */ - bool is_inside(const group* g) const noexcept { - if(!g) return false; - return std::any_of(stack_.begin(), stack_.end(), - [g](const context& c) { return c.parent == g; }); - } - - /** @brief inside group with joinable flags */ - bool joinable() const noexcept { - if(stack_.empty()) return false; - return std::any_of(stack_.begin(), stack_.end(), - [](const context& c) { return c.parent->joinable(); }); - } - - const context_list& - stack() const { - return stack_; - } - - /** @brief innermost repeat group */ - const group* - innermost_repeat_group() const noexcept { - auto i = std::find_if(stack_.rbegin(), stack_.rend(), - [](const context& c) { return c.parent->repeatable(); }); - return i != stack_.rend() ? i->parent : nullptr; - } - - /** @brief innermost exclusive (alternatives) group */ - const group* - innermost_exclusive_group() const noexcept { - auto i = std::find_if(stack_.rbegin(), stack_.rend(), - [](const context& c) { return c.parent->exclusive(); }); - return i != stack_.rend() ? i->parent : nullptr; - } - - /** @brief innermost blocking group */ - const group* - innermost_blocking_group() const noexcept { - auto i = std::find_if(stack_.rbegin(), stack_.rend(), - [](const context& c) { return c.parent->blocking(); }); - return i != stack_.rend() ? i->parent : nullptr; - } - - /** @brief returns the outermost group that will be left on next ++*/ - const group* - outermost_blocking_group_fully_explored() const noexcept { - if(stack_.empty()) return nullptr; - - const group* g = nullptr; - for(auto i = stack_.rbegin(); i != stack_.rend(); ++i) { - if(i->cur+1 == i->end) { - if(i->parent->blocking()) g = i->parent; - } else { - return g; - } - } - return g; - } - - /** @brief outermost join group */ - const group* - outermost_join_group() const noexcept { - auto i = std::find_if(stack_.begin(), stack_.end(), - [](const context& c) { return c.parent->joinable(); }); - return i != stack_.end() ? i->parent : nullptr; - } - - const group* root() const noexcept { - return stack_.empty() ? nullptr : stack_.front().parent; - } - - /** @brief common flag prefix of all flags in current group */ - arg_string common_flag_prefix() const noexcept { - if(stack_.empty()) return ""; - auto g = outermost_join_group(); - return g ? g->common_flag_prefix() : arg_string(""); - } - - const child& - operator * () const noexcept { - return *stack_.back().cur; - } - - const child* - operator -> () const noexcept { - return &(*stack_.back().cur); - } - - const group& - parent() const noexcept { - return *(stack_.back().parent); - } - - - /** @brief go to next element of depth first search */ - depth_first_traverser& - operator ++ () { - if(stack_.empty()) return *this; - //at group -> decend into group - if(stack_.back().cur->is_group()) { - stack_.emplace_back(stack_.back().cur->as_group()); - } - else { - next_sibling(); - } - return *this; - } - - /** @brief go to next sibling of current */ - depth_first_traverser& - next_sibling() { - if(stack_.empty()) return *this; - ++stack_.back().cur; - //at the end of current group? - while(stack_.back().cur == stack_.back().end) { - //go to parent - stack_.pop_back(); - if(stack_.empty()) return *this; - //go to next sibling in parent - ++stack_.back().cur; - } - return *this; - } - - /** @brief go to next position after siblings of current */ - depth_first_traverser& - next_after_siblings() { - if(stack_.empty()) return *this; - stack_.back().cur = stack_.back().end-1; - next_sibling(); - return *this; - } - - /** - * @brief - */ - depth_first_traverser& - back_to_ancestor(const group* g) { - if(!g) return *this; - while(!stack_.empty()) { - const auto& top = stack_.back().cur; - if(top->is_group() && &(top->as_group()) == g) return *this; - stack_.pop_back(); - } - return *this; - } - - /** @brief don't visit next siblings, go back to parent on next ++ - * note: renders siblings unreachable for *this - **/ - depth_first_traverser& - skip_siblings() { - if(stack_.empty()) return *this; - //future increments won't visit subsequent siblings: - stack_.back().end = stack_.back().cur+1; - return *this; - } - - /** @brief skips all other alternatives in surrounding exclusive groups - * on next ++ - * note: renders alternatives unreachable for *this - */ - depth_first_traverser& - skip_alternatives() { - if(stack_.empty()) return *this; - - //exclude all other alternatives in surrounding groups - //by making their current position the last one - for(auto& c : stack_) { - if(c.parent && c.parent->exclusive() && c.cur < c.end) - c.end = c.cur+1; - } - - return *this; - } - - void invalidate() { - stack_.clear(); - } - - inline friend bool operator == (const depth_first_traverser& a, - const depth_first_traverser& b) - { - if(a.stack_.empty() || b.stack_.empty()) return false; - - //parents not the same -> different position - if(a.stack_.back().parent != b.stack_.back().parent) return false; - - bool aEnd = a.stack_.back().cur == a.stack_.back().end; - bool bEnd = b.stack_.back().cur == b.stack_.back().end; - //either both at the end of the same parent => same position - if(aEnd && bEnd) return true; - //or only one at the end => not at the same position - if(aEnd || bEnd) return false; - return std::addressof(*a.stack_.back().cur) == - std::addressof(*b.stack_.back().cur); - } - inline friend bool operator != (const depth_first_traverser& a, - const depth_first_traverser& b) - { - return !(a == b); - } - - memento - undo_point() const { - memento m; - m.level_ = int(stack_.size()); - if(!stack_.empty()) m.context_ = stack_.back(); - return m; - } - - void undo(const memento& m) { - if(m.level_ < 1) return; - if(m.level_ <= int(stack_.size())) { - stack_.erase(stack_.begin() + m.level_, stack_.end()); - stack_.back() = m.context_; - } - else if(stack_.empty() && m.level_ == 1) { - stack_.push_back(m.context_); - } - } - - private: - context_list stack_; - }; - - - //--------------------------------------------------------------- - group() = default; - - template - explicit - group(doc_string docstr, Param param, Params... params): - children_{}, exclusive_{false}, joinable_{false}, scoped_{true} - { - doc(std::move(docstr)); - push_back(std::move(param), std::move(params)...); - } - - template - explicit - group(parameter param, Params... params): - children_{}, exclusive_{false}, joinable_{false}, scoped_{true} - { - push_back(std::move(param), std::move(params)...); - } - - template - explicit - group(group p1, P2 p2, Ps... ps): - children_{}, exclusive_{false}, joinable_{false}, scoped_{true} - { - push_back(std::move(p1), std::move(p2), std::move(ps)...); - } - - - //----------------------------------------------------- - group(const group&) = default; - group(group&&) = default; - - - //--------------------------------------------------------------- - group& operator = (const group&) = default; - group& operator = (group&&) = default; - - - //--------------------------------------------------------------- - /** @brief determines if a command line argument can be matched by a - * combination of (partial) matches through any number of children - */ - group& joinable(bool yes) { - joinable_ = yes; - return *this; - } - - /** @brief returns if a command line argument can be matched by a - * combination of (partial) matches through any number of children - */ - bool joinable() const noexcept { - return joinable_; - } - - - //--------------------------------------------------------------- - /** @brief turns explicit scoping on or off - * operators , & | and other combinating functions will - * not merge groups that are marked as scoped - */ - group& scoped(bool yes) { - scoped_ = yes; - return *this; - } - - /** @brief returns true if operators , & | and other combinating functions - * will merge groups and false otherwise - */ - bool scoped() const noexcept - { - return scoped_; - } - - - //--------------------------------------------------------------- - /** @brief determines if children are mutually exclusive alternatives */ - group& exclusive(bool yes) { - exclusive_ = yes; - return *this; - } - /** @brief returns if children are mutually exclusive alternatives */ - bool exclusive() const noexcept { - return exclusive_; - } - - - //--------------------------------------------------------------- - /** @brief returns true, if any child is required to match */ - bool any_required() const - { - return std::any_of(children_.begin(), children_.end(), - [](const child& n){ return n.required(); }); - } - /** @brief returns true, if all children are required to match */ - bool all_required() const - { - return std::all_of(children_.begin(), children_.end(), - [](const child& n){ return n.required(); }); - } - - - //--------------------------------------------------------------- - /** @brief returns true if any child is optional (=non-required) */ - bool any_optional() const { - return !all_required(); - } - /** @brief returns true if all children are optional (=non-required) */ - bool all_optional() const { - return !any_required(); - } - - - //--------------------------------------------------------------- - /** @brief returns if the entire group is blocking / positional */ - bool blocking() const noexcept { - return token::blocking() || (exclusive() && all_blocking()); - } - //----------------------------------------------------- - /** @brief determines if the entire group is blocking / positional */ - group& blocking(bool yes) { - return token::blocking(yes); - } - - //--------------------------------------------------------------- - /** @brief returns true if any child is blocking */ - bool any_blocking() const - { - return std::any_of(children_.begin(), children_.end(), - [](const child& n){ return n.blocking(); }); - } - //--------------------------------------------------------------- - /** @brief returns true if all children is blocking */ - bool all_blocking() const - { - return std::all_of(children_.begin(), children_.end(), - [](const child& n){ return n.blocking(); }); - } - - - //--------------------------------------------------------------- - /** @brief returns if any child is a value parameter (recursive) */ - bool any_flagless() const - { - return std::any_of(children_.begin(), children_.end(), - [](const child& p){ - return p.is_param() && p.as_param().flags().empty(); - }); - } - /** @brief returns if all children are value parameters (recursive) */ - bool all_flagless() const - { - return std::all_of(children_.begin(), children_.end(), - [](const child& p){ - return p.is_param() && p.as_param().flags().empty(); - }); - } - - - //--------------------------------------------------------------- - /** @brief adds child parameter at the end */ - group& - push_back(const parameter& v) { - children_.emplace_back(v); - return *this; - } - //----------------------------------------------------- - /** @brief adds child parameter at the end */ - group& - push_back(parameter&& v) { - children_.emplace_back(std::move(v)); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the end */ - group& - push_back(const group& g) { - children_.emplace_back(g); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the end */ - group& - push_back(group&& g) { - children_.emplace_back(std::move(g)); - return *this; - } - - - //----------------------------------------------------- - /** @brief adds children (groups and/or parameters) */ - template - group& - push_back(Param1&& param1, Param2&& param2, Params&&... params) - { - children_.reserve(children_.size() + 2 + sizeof...(params)); - push_back(std::forward(param1)); - push_back(std::forward(param2), std::forward(params)...); - return *this; - } - - - //--------------------------------------------------------------- - /** @brief adds child parameter at the beginning */ - group& - push_front(const parameter& v) { - children_.emplace(children_.begin(), v); - return *this; - } - //----------------------------------------------------- - /** @brief adds child parameter at the beginning */ - group& - push_front(parameter&& v) { - children_.emplace(children_.begin(), std::move(v)); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the beginning */ - group& - push_front(const group& g) { - children_.emplace(children_.begin(), g); - return *this; - } - //----------------------------------------------------- - /** @brief adds child group at the beginning */ - group& - push_front(group&& g) { - children_.emplace(children_.begin(), std::move(g)); - return *this; - } - - - //--------------------------------------------------------------- - /** @brief adds all children of other group at the end */ - group& - merge(group&& g) - { - children_.insert(children_.end(), - std::make_move_iterator(g.begin()), - std::make_move_iterator(g.end())); - return *this; - } - //----------------------------------------------------- - /** @brief adds all children of several other groups at the end */ - template - group& - merge(group&& g1, group&& g2, Groups&&... gs) - { - merge(std::move(g1)); - merge(std::move(g2), std::forward(gs)...); - return *this; - } - - - //--------------------------------------------------------------- - /** @brief indexed, nutable access to child */ - child& operator [] (size_type index) noexcept { - return children_[index]; - } - /** @brief indexed, non-nutable access to child */ - const child& operator [] (size_type index) const noexcept { - return children_[index]; - } - - //--------------------------------------------------------------- - /** @brief mutable access to first child */ - child& front() noexcept { return children_.front(); } - /** @brief non-mutable access to first child */ - const child& front() const noexcept { return children_.front(); } - //----------------------------------------------------- - /** @brief mutable access to last child */ - child& back() noexcept { return children_.back(); } - /** @brief non-mutable access to last child */ - const child& back() const noexcept { return children_.back(); } - - - //--------------------------------------------------------------- - /** @brief returns true, if group has no children, false otherwise */ - bool empty() const noexcept { return children_.empty(); } - - /** @brief returns number of children */ - size_type size() const noexcept { return children_.size(); } - - /** @brief returns number of nested levels; 1 for a flat group */ - size_type depth() const { - size_type n = 0; - for(const auto& c : children_) { - auto l = 1 + c.depth(); - if(l > n) n = l; - } - return n; - } - - - //--------------------------------------------------------------- - /** @brief returns mutating iterator to position of first element */ - iterator begin() noexcept { return children_.begin(); } - /** @brief returns non-mutating iterator to position of first element */ - const_iterator begin() const noexcept { return children_.begin(); } - /** @brief returns non-mutating iterator to position of first element */ - const_iterator cbegin() const noexcept { return children_.begin(); } - - /** @brief returns mutating iterator to position one past the last element */ - iterator end() noexcept { return children_.end(); } - /** @brief returns non-mutating iterator to position one past the last element */ - const_iterator end() const noexcept { return children_.end(); } - /** @brief returns non-mutating iterator to position one past the last element */ - const_iterator cend() const noexcept { return children_.end(); } - - - //--------------------------------------------------------------- - /** @brief returns augmented iterator for depth first searches - * @details traverser knows end of iteration and can skip over children - */ - depth_first_traverser - begin_dfs() const noexcept { - return depth_first_traverser{*this}; - } - - - //--------------------------------------------------------------- - /** @brief returns recursive parameter count */ - size_type param_count() const { - size_type c = 0; - for(const auto& n : children_) { - c += n.param_count(); - } - return c; - } - - - //--------------------------------------------------------------- - /** @brief returns range of all flags (recursive) */ - arg_list all_flags() const - { - std::vector all; - gather_flags(children_, all); - return all; - } - - /** @brief returns true, if no flag occurs as true - * prefix of any other flag (identical flags will be ignored) */ - bool flags_are_prefix_free() const - { - const auto fs = all_flags(); - - using std::begin; using std::end; - for(auto i = begin(fs), e = end(fs); i != e; ++i) { - if(!i->empty()) { - for(auto j = i+1; j != e; ++j) { - if(!j->empty() && *i != *j) { - if(i->find(*j) == 0) return false; - if(j->find(*i) == 0) return false; - } - } - } - } - - return true; - } - - - //--------------------------------------------------------------- - /** @brief returns longest common prefix of all flags */ - arg_string common_flag_prefix() const - { - arg_list prefixes; - gather_prefixes(children_, prefixes); - return str::longest_common_prefix(prefixes); - } - - -private: - //--------------------------------------------------------------- - static void - gather_flags(const children_store& nodes, arg_list& all) - { - for(const auto& p : nodes) { - if(p.is_group()) { - gather_flags(p.as_group().children_, all); - } - else { - const auto& pf = p.as_param().flags(); - using std::begin; - using std::end; - if(!pf.empty()) all.insert(end(all), begin(pf), end(pf)); - } - } - } - //--------------------------------------------------------------- - static void - gather_prefixes(const children_store& nodes, arg_list& all) - { - for(const auto& p : nodes) { - if(p.is_group()) { - gather_prefixes(p.as_group().children_, all); - } - else if(!p.as_param().flags().empty()) { - auto pfx = str::longest_common_prefix(p.as_param().flags()); - if(!pfx.empty()) all.push_back(std::move(pfx)); - } - } - } - - //--------------------------------------------------------------- - children_store children_; - bool exclusive_ = false; - bool joinable_ = false; - bool scoped_ = false; -}; - - - -/*************************************************************************//** - * - * @brief group or parameter - * - *****************************************************************************/ -using pattern = group::child; - - - -/*************************************************************************//** - * - * @brief apply an action to all parameters in a group - * - *****************************************************************************/ -template -void for_all_params(group& g, Action&& action) -{ - for(auto& p : g) { - if(p.is_group()) { - for_all_params(p.as_group(), action); - } - else { - action(p.as_param()); - } - } -} - -template -void for_all_params(const group& g, Action&& action) -{ - for(auto& p : g) { - if(p.is_group()) { - for_all_params(p.as_group(), action); - } - else { - action(p.as_param()); - } - } -} - - - -/*************************************************************************//** - * - * @brief makes a group of parameters and/or groups - * - *****************************************************************************/ -inline group -operator , (parameter a, parameter b) -{ - return group{std::move(a), std::move(b)}.scoped(false); -} - -//--------------------------------------------------------- -inline group -operator , (parameter a, group b) -{ - return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable() - && !b.joinable() && (b.doc().empty() || b.doc() == a.doc()) - ? b.push_front(std::move(a)) - : group{std::move(a), std::move(b)}.scoped(false); -} - -//--------------------------------------------------------- -inline group -operator , (group a, parameter b) -{ - return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable() - && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false); -} - -//--------------------------------------------------------- -inline group -operator , (group a, group b) -{ - return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable() - && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false); -} - - - -/*************************************************************************//** - * - * @brief makes a group of alternative parameters or groups - * - *****************************************************************************/ -template -inline group -one_of(Param param, Params... params) -{ - return group{std::move(param), std::move(params)...}.exclusive(true); -} - - -/*************************************************************************//** - * - * @brief makes a group of alternative parameters or groups - * - *****************************************************************************/ -inline group -operator | (parameter a, parameter b) -{ - return group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - -//------------------------------------------------------------------- -inline group -operator | (parameter a, group b) -{ - return !b.scoped() && !b.blocking() && b.exclusive() && !b.repeatable() - && !b.joinable() - && (b.doc().empty() || b.doc() == a.doc()) - ? b.push_front(std::move(a)) - : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - -//------------------------------------------------------------------- -inline group -operator | (group a, parameter b) -{ - return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable() - && a.blocking() == b.blocking() - && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - -inline group -operator | (group a, group b) -{ - return !a.scoped() && a.exclusive() &&!a.repeatable() && !a.joinable() - && a.blocking() == b.blocking() - && (a.doc().empty() || a.doc() == b.doc()) - ? a.push_back(std::move(b)) - : group{std::move(a), std::move(b)}.scoped(false).exclusive(true); -} - - - -/*************************************************************************//** - * - * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!) - * no interface guarantees; might be changed or removed in the future - * - *****************************************************************************/ -namespace detail { - -inline void set_blocking(bool) {} - -template -void set_blocking(bool yes, P& p, Ps&... ps) { - p.blocking(yes); - set_blocking(yes, ps...); -} - -} // namespace detail - - -/*************************************************************************//** - * - * @brief makes a parameter/group sequence by making all input objects blocking - * - *****************************************************************************/ -template -inline group -in_sequence(Param param, Params... params) -{ - detail::set_blocking(true, param, params...); - return group{std::move(param), std::move(params)...}.scoped(true); -} - - -/*************************************************************************//** - * - * @brief makes a parameter/group sequence by making all input objects blocking - * - *****************************************************************************/ -inline group -operator & (parameter a, parameter b) -{ - a.blocking(true); - b.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); -} - -//--------------------------------------------------------- -inline group -operator & (parameter a, group b) -{ - a.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); -} - -//--------------------------------------------------------- -inline group -operator & (group a, parameter b) -{ - b.blocking(true); - if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable() - && (a.doc().empty() || a.doc() == b.doc())) - { - return a.push_back(std::move(b)); - } - else { - if(!a.all_blocking()) a.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); - } -} - -inline group -operator & (group a, group b) -{ - if(!b.all_blocking()) b.blocking(true); - if(a.all_blocking() && !a.exclusive() && !a.repeatable() - && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())) - { - return a.push_back(std::move(b)); - } - else { - if(!a.all_blocking()) a.blocking(true); - return group{std::move(a), std::move(b)}.scoped(true); - } -} - - - -/*************************************************************************//** - * - * @brief makes a group of parameters and/or groups - * where all single char flag params ("-a", "b", ...) are joinable - * - *****************************************************************************/ -inline group -joinable(group g) { - return g.joinable(true); -} - -//------------------------------------------------------------------- -template -inline group -joinable(parameter param, Params... params) -{ - return group{std::move(param), std::move(params)...}.joinable(true); -} - -template -inline group -joinable(group p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true); -} - -template -inline group -joinable(doc_string docstr, Param param, Params... params) -{ - return group{std::move(param), std::move(params)...} - .joinable(true).doc(std::move(docstr)); -} - - - -/*************************************************************************//** - * - * @brief makes a repeatable copy of a parameter - * - *****************************************************************************/ -inline parameter -repeatable(parameter p) { - return p.repeatable(true); -} - -/*************************************************************************//** - * - * @brief makes a repeatable copy of a group - * - *****************************************************************************/ -inline group -repeatable(group g) { - return g.repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes a group of parameters and/or groups - * that is repeatable as a whole - * Note that a repeatable group consisting entirely of non-blocking - * children is equivalent to a non-repeatable group of - * repeatable children. - * - *****************************************************************************/ -template -inline group -repeatable(parameter p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), - std::move(ps)...}.repeatable(true); -} - -template -inline group -repeatable(group p1, P2 p2, Ps... ps) -{ - return group{std::move(p1), std::move(p2), - std::move(ps)...}.repeatable(true); -} - - - -/*************************************************************************//** - * - * @brief makes a parameter greedy (match with top priority) - * - *****************************************************************************/ -inline parameter -greedy(parameter p) { - return p.greedy(true); -} - -inline parameter -operator ! (parameter p) { - return greedy(p); -} - - - -/*************************************************************************//** - * - * @brief recursively prepends a prefix to all flags - * - *****************************************************************************/ -inline parameter&& -with_prefix(const arg_string& prefix, parameter&& p) { - return std::move(with_prefix(prefix, p)); -} - - -//------------------------------------------------------------------- -inline group& -with_prefix(const arg_string& prefix, group& g) -{ - for(auto& p : g) { - if(p.is_group()) { - with_prefix(prefix, p.as_group()); - } else { - with_prefix(prefix, p.as_param()); - } - } - return g; -} - - -inline group&& -with_prefix(const arg_string& prefix, group&& params) -{ - return std::move(with_prefix(prefix, params)); -} - - -template -inline group -with_prefix(arg_string prefix, Param&& param, Params&&... params) -{ - return with_prefix(prefix, group{std::forward(param), - std::forward(params)...}); -} - - - -/*************************************************************************//** - * - * @brief recursively prepends a prefix to all flags - * - * @param shortpfx : used for single-letter flags - * @param longpfx : used for flags with length > 1 - * - *****************************************************************************/ -inline parameter&& -with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx, - parameter&& p) -{ - return std::move(with_prefixes_short_long(shortpfx, longpfx, p)); -} - - -//------------------------------------------------------------------- -inline group& -with_prefixes_short_long(const arg_string& shortFlagPrefix, - const arg_string& longFlagPrefix, - group& g) -{ - for(auto& p : g) { - if(p.is_group()) { - with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group()); - } else { - with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param()); - } - } - return g; -} - - -inline group&& -with_prefixes_short_long(const arg_string& shortFlagPrefix, - const arg_string& longFlagPrefix, - group&& params) -{ - return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, - params)); -} - - -template -inline group -with_prefixes_short_long(const arg_string& shortFlagPrefix, - const arg_string& longFlagPrefix, - Param&& param, Params&&... params) -{ - return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, - group{std::forward(param), - std::forward(params)...}); -} - - - -/*************************************************************************//** - * - * @brief recursively prepends a suffix to all flags - * - *****************************************************************************/ -inline parameter&& -with_suffix(const arg_string& suffix, parameter&& p) { - return std::move(with_suffix(suffix, p)); -} - - -//------------------------------------------------------------------- -inline group& -with_suffix(const arg_string& suffix, group& g) -{ - for(auto& p : g) { - if(p.is_group()) { - with_suffix(suffix, p.as_group()); - } else { - with_suffix(suffix, p.as_param()); - } - } - return g; -} - - -inline group&& -with_suffix(const arg_string& suffix, group&& params) -{ - return std::move(with_suffix(suffix, params)); -} - - -template -inline group -with_suffix(arg_string suffix, Param&& param, Params&&... params) -{ - return with_suffix(suffix, group{std::forward(param), - std::forward(params)...}); -} - - - -/*************************************************************************//** - * - * @brief recursively prepends a suffix to all flags - * - * @param shortsfx : used for single-letter flags - * @param longsfx : used for flags with length > 1 - * - *****************************************************************************/ -inline parameter&& -with_suffixes_short_long(const arg_string& shortsfx, const arg_string& longsfx, - parameter&& p) -{ - return std::move(with_suffixes_short_long(shortsfx, longsfx, p)); -} - - -//------------------------------------------------------------------- -inline group& -with_suffixes_short_long(const arg_string& shortFlagSuffix, - const arg_string& longFlagSuffix, - group& g) -{ - for(auto& p : g) { - if(p.is_group()) { - with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_group()); - } else { - with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_param()); - } - } - return g; -} - - -inline group&& -with_suffixes_short_long(const arg_string& shortFlagSuffix, - const arg_string& longFlagSuffix, - group&& params) -{ - return std::move(with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, - params)); -} - - -template -inline group -with_suffixes_short_long(const arg_string& shortFlagSuffix, - const arg_string& longFlagSuffix, - Param&& param, Params&&... params) -{ - return with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, - group{std::forward(param), - std::forward(params)...}); -} - - - - - - - - -/*************************************************************************//** - * - * @brief parsing implementation details - * - *****************************************************************************/ - -namespace detail { - - -/*************************************************************************//** - * - * @brief DFS traverser that keeps track of 'scopes' - * scope = all parameters that are either bounded by - * two blocking parameters on the same depth level - * or the beginning/end of the outermost group - * - *****************************************************************************/ -class scoped_dfs_traverser -{ -public: - using dfs_traverser = group::depth_first_traverser; - - scoped_dfs_traverser() = default; - - explicit - scoped_dfs_traverser(const group& g): - pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{}, - ignoreBlocks_{false}, - repeatGroupStarted_{false}, repeatGroupContinues_{false} - {} - - const dfs_traverser& base() const noexcept { return pos_; } - const dfs_traverser& last_match() const noexcept { return lastMatch_; } - - const group& parent() const noexcept { return pos_.parent(); } - - const group* innermost_repeat_group() const noexcept { - return pos_.innermost_repeat_group(); - } - const group* outermost_join_group() const noexcept { - return pos_.outermost_join_group(); - } - const group* innermost_blocking_group() const noexcept { - return pos_.innermost_blocking_group(); - } - const group* innermost_exclusive_group() const noexcept { - return pos_.innermost_exclusive_group(); - } - - const pattern* operator ->() const noexcept { return pos_.operator->(); } - const pattern& operator *() const noexcept { return *pos_; } - - const pattern* ptr() const noexcept { return pos_.operator->(); } - - explicit operator bool() const noexcept { return bool(pos_); } - - bool joinable() const noexcept { return pos_.joinable(); } - arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); } - - void ignore_blocking(bool yes) { ignoreBlocks_ = yes; } - - void invalidate() { - pos_.invalidate(); - } - - bool matched() const noexcept { - return (pos_ == lastMatch_); - } - - bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; } - - //----------------------------------------------------- - scoped_dfs_traverser& - next_sibling() { pos_.next_sibling(); return *this; } - - scoped_dfs_traverser& - next_after_siblings() { pos_.next_after_siblings(); return *this; } - - - //----------------------------------------------------- - scoped_dfs_traverser& - operator ++ () - { - if(!pos_) return *this; - - if(pos_.is_last_in_path()) { - return_to_outermost_scope(); - return *this; - } - - //current pattern can block if it didn't match already - if(ignoreBlocks_ || matched()) { - ++pos_; - } - else if(!pos_->is_group()) { - //current group can block if we didn't have any match in it - const group* g = pos_.outermost_blocking_group_fully_explored(); - //no match in 'g' before -> skip to after its siblings - if(g && !lastMatch_.is_inside(g)) { - pos_.back_to_ancestor(g).next_after_siblings(); - if(!pos_) return_to_outermost_scope(); - } - else if(pos_->blocking()) { - if(pos_.parent().exclusive()) { - pos_.next_sibling(); - } else { - //no match => skip siblings of blocking param - pos_.next_after_siblings(); - } - if(!pos_) return_to_outermost_scope(); - } else { - ++pos_; - } - } else { - ++pos_; - } - check_if_left_scope(); - return *this; - } - - //----------------------------------------------------- - void next_after_match(scoped_dfs_traverser match) - { - if(!match || ignoreBlocks_) return; - - check_repeat_group_start(match); - - lastMatch_ = match.base(); - - // if there is a blocking ancestor -> go back to it - if(!match->blocking()) { - match.pos_.back_to_ancestor(match.innermost_blocking_group()); - } - - //if match is not in current position & current position is blocking - //=> current position has to be advanced by one so that it is - //no longer reachable within current scope - //(can happen for repeatable, blocking parameters) - if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling(); - - if(match->blocking()) { - if(match.pos_.is_alternative()) { - //discard other alternatives - match.pos_.skip_alternatives(); - } - - if(is_last_in_current_scope(match.pos_)) { - //if current param is not repeatable -> back to previous scope - if(!match->repeatable() && !match->is_group()) { - pos_ = std::move(match.pos_); - if(!scopes_.empty()) pos_.undo(scopes_.top()); - } - else { //stay at match position - pos_ = std::move(match.pos_); - } - } - else { //not last in current group - //if current param is not repeatable, go directly to next - if(!match->repeatable() && !match->is_group()) { - ++match.pos_; - } - - if(match.pos_.level() > pos_.level()) { - scopes_.push(pos_.undo_point()); - pos_ = std::move(match.pos_); - } - else if(match.pos_.level() < pos_.level()) { - return_to_level(match.pos_.level()); - } - else { - pos_ = std::move(match.pos_); - } - } - posAfterLastMatch_ = pos_; - } - else { - if(match.pos_.level() < pos_.level()) { - return_to_level(match.pos_.level()); - } - posAfterLastMatch_ = pos_; - } - repeatGroupContinues_ = repeat_group_continues(); - } - -private: - //----------------------------------------------------- - bool is_last_in_current_scope(const dfs_traverser& pos) const - { - if(scopes_.empty()) return pos.is_last_in_path(); - //check if we would leave the current scope on ++ - auto p = pos; - ++p; - return p.level() < scopes_.top().level(); - } - - //----------------------------------------------------- - void check_repeat_group_start(const scoped_dfs_traverser& newMatch) - { - const auto newrg = newMatch.innermost_repeat_group(); - if(!newrg) { - repeatGroupStarted_ = false; - } - else if(lastMatch_.innermost_repeat_group() != newrg) { - repeatGroupStarted_ = true; - } - else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) { - repeatGroupStarted_ = true; - } - else { - //special case: repeat group is outermost group - //=> we can never really 'leave' and 'reenter' it - //but if the current scope is the first element, then we are - //conceptually at a position 'before' the group - repeatGroupStarted_ = scopes_.empty() || ( - newrg == pos_.root() && - scopes_.top().param() == &(*pos_.root()->begin()) ); - } - repeatGroupContinues_ = repeatGroupStarted_; - } - - //----------------------------------------------------- - bool repeat_group_continues() const - { - if(!repeatGroupContinues_) return false; - const auto curRepGroup = pos_.innermost_repeat_group(); - if(!curRepGroup) return false; - if(curRepGroup != lastMatch_.innermost_repeat_group()) return false; - if(!posAfterLastMatch_) return false; - return true; - } - - //----------------------------------------------------- - void check_if_left_scope() - { - if(posAfterLastMatch_) { - if(pos_.level() < posAfterLastMatch_.level()) { - while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - posAfterLastMatch_.invalidate(); - } - } - while(!scopes_.empty() && scopes_.top().level() > pos_.level()) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - repeatGroupContinues_ = repeat_group_continues(); - } - - //----------------------------------------------------- - void return_to_outermost_scope() - { - posAfterLastMatch_.invalidate(); - - if(scopes_.empty()) { - pos_.invalidate(); - repeatGroupContinues_ = false; - return; - } - - while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - while(!scopes_.empty()) scopes_.pop(); - - repeatGroupContinues_ = repeat_group_continues(); - } - - //----------------------------------------------------- - void return_to_level(int level) - { - if(pos_.level() <= level) return; - while(!scopes_.empty() && pos_.level() > level) { - pos_.undo(scopes_.top()); - scopes_.pop(); - } - }; - - dfs_traverser pos_; - dfs_traverser lastMatch_; - dfs_traverser posAfterLastMatch_; - std::stack scopes_; - bool ignoreBlocks_ = false; - bool repeatGroupStarted_ = false; - bool repeatGroupContinues_ = false; -}; - - - - -/***************************************************************************** - * - * some parameter property predicates - * - *****************************************************************************/ -struct select_all { - bool operator () (const parameter&) const noexcept { return true; } -}; - -struct select_flags { - bool operator () (const parameter& p) const noexcept { - return !p.flags().empty(); - } -}; - -struct select_values { - bool operator () (const parameter& p) const noexcept { - return p.flags().empty(); - } -}; - - - -/*************************************************************************//** - * - * @brief result of a matching operation - * - *****************************************************************************/ -class match_t { -public: - using size_type = arg_string::size_type; - - match_t() = default; - - match_t(arg_string s, scoped_dfs_traverser p): - str_{std::move(s)}, pos_{std::move(p)} - {} - - size_type length() const noexcept { return str_.size(); } - - const arg_string& str() const noexcept { return str_; } - const scoped_dfs_traverser& pos() const noexcept { return pos_; } - - explicit operator bool() const noexcept { return bool(pos_); } - -private: - arg_string str_; - scoped_dfs_traverser pos_; -}; - - - -/*************************************************************************//** - * - * @brief finds the first parameter that matches a given string; - * candidate parameters are traversed using a scoped DFS traverser - * - *****************************************************************************/ -template -match_t -full_match(scoped_dfs_traverser pos, const arg_string& arg, - const ParamSelector& select) -{ - while(pos) { - if(pos->is_param()) { - const auto& param = pos->as_param(); - if(select(param)) { - const auto match = param.match(arg); - if(match && match.length() == arg.size()) { - return match_t{arg, std::move(pos)}; - } - } - } - ++pos; - } - return match_t{}; -} - - - -/*************************************************************************//** - * - * @brief finds the first parameter that matches any (non-empty) prefix - * of a given string; - * candidate parameters are traversed using a scoped DFS traverser - * - *****************************************************************************/ -template -match_t -longest_prefix_match(scoped_dfs_traverser pos, const arg_string& arg, - const ParamSelector& select) -{ - match_t longest; - - while(pos) { - if(pos->is_param()) { - const auto& param = pos->as_param(); - if(select(param)) { - auto match = param.match(arg); - if(match.prefix()) { - if(match.length() == arg.size()) { - return match_t{arg, std::move(pos)}; - } - else if(match.length() > longest.length()) { - longest = match_t{arg.substr(match.at(), match.length()), - pos}; - } - } - } - } - ++pos; - } - return longest; -} - - - -/*************************************************************************//** - * - * @brief finds the first parameter that partially matches a given string; - * candidate parameters are traversed using a scoped DFS traverser - * - *****************************************************************************/ -template -match_t -partial_match(scoped_dfs_traverser pos, const arg_string& arg, - const ParamSelector& select) -{ - while(pos) { - if(pos->is_param()) { - const auto& param = pos->as_param(); - if(select(param)) { - const auto match = param.match(arg); - if(match) { - return match_t{arg.substr(match.at(), match.length()), - std::move(pos)}; - } - } - } - ++pos; - } - return match_t{}; -} - -} //namespace detail - - - - - - -/***************************************************************//** - * - * @brief default command line arguments parser - * - *******************************************************************/ -class parser -{ -public: - using dfs_traverser = group::depth_first_traverser; - using scoped_dfs_traverser = detail::scoped_dfs_traverser; - - - /*****************************************************//** - * @brief arg -> parameter mapping - *********************************************************/ - class arg_mapping { - public: - friend class parser; - - explicit - arg_mapping(arg_index idx, arg_string s, - const dfs_traverser& match) - : - index_{idx}, arg_{std::move(s)}, match_{match}, - repeat_{0}, startsRepeatGroup_{false}, - blocked_{false}, conflict_{false} - {} - - explicit - arg_mapping(arg_index idx, arg_string s) : - index_{idx}, arg_{std::move(s)}, match_{}, - repeat_{0}, startsRepeatGroup_{false}, - blocked_{false}, conflict_{false} - {} - - arg_index index() const noexcept { return index_; } - const arg_string& arg() const noexcept { return arg_; } - - const parameter* param() const noexcept { - return match_ && match_->is_param() - ? &(match_->as_param()) : nullptr; - } - - std::size_t repeat() const noexcept { return repeat_; } - - bool blocked() const noexcept { return blocked_; } - bool conflict() const noexcept { return conflict_; } - - bool bad_repeat() const noexcept { - if(!param()) return false; - return repeat_ > 0 && !param()->repeatable() - && !match_.innermost_repeat_group(); - } - - bool any_error() const noexcept { - return !match_ || blocked() || conflict() || bad_repeat(); - } - - private: - arg_index index_; - arg_string arg_; - dfs_traverser match_; - std::size_t repeat_; - bool startsRepeatGroup_; - bool blocked_; - bool conflict_; - }; - - /*****************************************************//** - * @brief references a non-matched, required parameter - *********************************************************/ - class missing_event { - public: - explicit - missing_event(const parameter* p, arg_index after): - param_{p}, aftIndex_{after} - {} - - const parameter* param() const noexcept { return param_; } - - arg_index after_index() const noexcept { return aftIndex_; } - - private: - const parameter* param_; - arg_index aftIndex_; - }; - - //----------------------------------------------------- - using missing_events = std::vector; - using arg_mappings = std::vector; - - -private: - struct miss_candidate { - miss_candidate(dfs_traverser p, arg_index idx, - bool firstInRepeatGroup = false): - pos{std::move(p)}, index{idx}, - startsRepeatGroup{firstInRepeatGroup} - {} - - dfs_traverser pos; - arg_index index; - bool startsRepeatGroup; - }; - using miss_candidates = std::vector; - - -public: - //--------------------------------------------------------------- - /** @brief initializes parser with a command line interface - * @param offset = argument index offset used for reports - * */ - explicit - parser(const group& root, arg_index offset = 0): - root_{&root}, pos_{root}, - index_{offset-1}, eaten_{0}, - args_{}, missCand_{}, blocked_{false} - { - for_each_potential_miss(dfs_traverser{root}, - [this](const dfs_traverser& p){ - missCand_.emplace_back(p, index_); - }); - } - - - //--------------------------------------------------------------- - /** @brief processes one command line argument */ - bool operator() (const arg_string& arg) - { - ++eaten_; - ++index_; - - if(!valid()) return false; - - if(!blocked_ && try_match(arg)) return true; - - if(try_match_blocked(arg)) return false; - - //skipping of blocking & required patterns is not allowed - if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) { - blocked_ = true; - } - - add_nomatch(arg); - return false; - } - - - //--------------------------------------------------------------- - /** @brief returns range of argument -> parameter mappings */ - const arg_mappings& args() const { - return args_; - } - - /** @brief returns list of missing events */ - missing_events missed() const { - missing_events misses; - misses.reserve(missCand_.size()); - for(auto i = missCand_.begin(); i != missCand_.end(); ++i) { - misses.emplace_back(&(i->pos->as_param()), i->index); - } - return misses; - } - - /** @brief returns number of processed command line arguments */ - arg_index parse_count() const noexcept { return eaten_; } - - /** @brief returns false if previously processed command line arguments - * lead to an invalid / inconsistent parsing result - */ - bool valid() const noexcept { return bool(pos_); } - - /** @brief returns false if previously processed command line arguments - * lead to an invalid / inconsistent parsing result - */ - explicit operator bool() const noexcept { return valid(); } - - -private: - //--------------------------------------------------------------- - using match_t = detail::match_t; - - - //--------------------------------------------------------------- - /** @brief try to match argument with unreachable parameter */ - bool try_match_blocked(const arg_string& arg) - { - //try to match ahead (using temporary parser) - if(pos_) { - auto ahead = *this; - if(try_match_blocked(std::move(ahead), arg)) return true; - } - - //try to match from the beginning (using temporary parser) - if(root_) { - parser all{*root_, index_+1}; - if(try_match_blocked(std::move(all), arg)) return true; - } - - return false; - } - - //--------------------------------------------------------------- - bool try_match_blocked(parser&& parse, const arg_string& arg) - { - const auto nold = int(parse.args_.size()); - - parse.pos_.ignore_blocking(true); - - if(!parse.try_match(arg)) return false; - - for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) { - args_.push_back(*i); - args_.back().blocked_ = true; - } - return true; - } - - //--------------------------------------------------------------- - /** @brief try to find a parameter/pattern that matches 'arg' */ - bool try_match(const arg_string& arg) - { - //match greedy parameters before everything else - if(pos_->is_param() && pos_->blocking() && pos_->as_param().greedy()) { - const auto match = pos_->as_param().match(arg); - if(match && match.length() == arg.size()) { - add_match(detail::match_t{arg,pos_}); - return true; - } - } - - //try flags first (alone, joinable or strict sequence) - if(try_match_full(arg, detail::select_flags{})) return true; - if(try_match_joined_flags(arg)) return true; - if(try_match_joined_sequence(arg, detail::select_flags{})) return true; - //try value params (alone or strict sequence) - if(try_match_full(arg, detail::select_values{})) return true; - if(try_match_joined_sequence(arg, detail::select_all{})) return true; - //try joinable params + values in any order - if(try_match_joined_params(arg)) return true; - return false; - } - - //--------------------------------------------------------------- - /** - * @brief try to match full argument - * @param select : predicate that candidate parameters must satisfy - */ - template - bool try_match_full(const arg_string& arg, const ParamSelector& select) - { - auto match = detail::full_match(pos_, arg, select); - if(!match) return false; - add_match(match); - return true; - } - - //--------------------------------------------------------------- - /** - * @brief try to match argument as blocking sequence of parameters - * @param select : predicate that a parameter matching the prefix of - * 'arg' must satisfy - */ - template - bool try_match_joined_sequence(arg_string arg, - const ParamSelector& acceptFirst) - { - auto fstMatch = detail::longest_prefix_match(pos_, arg, acceptFirst); - - if(!fstMatch) return false; - - if(fstMatch.str().size() == arg.size()) { - add_match(fstMatch); - return true; - } - - if(!fstMatch.pos()->blocking()) return false; - - auto pos = fstMatch.pos(); - pos.ignore_blocking(true); - const auto parent = &pos.parent(); - if(!pos->repeatable()) ++pos; - - arg.erase(0, fstMatch.str().size()); - std::vector matches { std::move(fstMatch) }; - - while(!arg.empty() && pos && - pos->blocking() && pos->is_param() && - (&pos.parent() == parent)) - { - auto match = pos->as_param().match(arg); - - if(match.prefix()) { - matches.emplace_back(arg.substr(0,match.length()), pos); - arg.erase(0, match.length()); - if(!pos->repeatable()) ++pos; - } - else { - if(!pos->repeatable()) return false; - ++pos; - } - - } - //if arg not fully covered => discard temporary matches - if(!arg.empty() || matches.empty()) return false; - - for(const auto& m : matches) add_match(m); - return true; - } - - //----------------------------------------------------- - /** @brief try to match 'arg' as a concatenation of joinable flags */ - bool try_match_joined_flags(const arg_string& arg) - { - return find_join_group(pos_, [&](const group& g) { - return try_match_joined(g, arg, detail::select_flags{}, - g.common_flag_prefix()); - }); - } - - //--------------------------------------------------------------- - /** @brief try to match 'arg' as a concatenation of joinable parameters */ - bool try_match_joined_params(const arg_string& arg) - { - return find_join_group(pos_, [&](const group& g) { - return try_match_joined(g, arg, detail::select_all{}); - }); - } - - //----------------------------------------------------- - /** @brief try to match 'arg' as concatenation of joinable parameters - * that are all contained within one group - */ - template - bool try_match_joined(const group& joinGroup, arg_string arg, - const ParamSelector& select, - const arg_string& prefix = "") - { - //temporary parser with 'joinGroup' as top-level group - parser parse {joinGroup}; - //records temporary matches - std::vector matches; - - while(!arg.empty()) { - auto match = detail::longest_prefix_match(parse.pos_, arg, select); - - if(!match) return false; - - arg.erase(0, match.str().size()); - //make sure prefix is always present after the first match - //so that, e.g., flags "-a" and "-b" will be found in "-ab" - if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 && - prefix != match.str()) - { - arg.insert(0,prefix); - } - - parse.add_match(match); - matches.push_back(std::move(match)); - } - - if(!arg.empty() || matches.empty()) return false; - - if(!parse.missCand_.empty()) return false; - for(const auto& a : parse.args_) if(a.any_error()) return false; - - //replay matches onto *this - for(const auto& m : matches) add_match(m); - return true; - } - - //----------------------------------------------------- - template - bool find_join_group(const scoped_dfs_traverser& start, - const GroupSelector& accept) const - { - if(start && start.parent().joinable()) { - const auto& g = start.parent(); - if(accept(g)) return true; - return false; - } - - auto pos = start; - while(pos) { - if(pos->is_group() && pos->as_group().joinable()) { - const auto& g = pos->as_group(); - if(accept(g)) return true; - pos.next_sibling(); - } - else { - ++pos; - } - } - return false; - } - - - //--------------------------------------------------------------- - void add_nomatch(const arg_string& arg) { - args_.emplace_back(index_, arg); - } - - - //--------------------------------------------------------------- - void add_match(const match_t& match) - { - const auto& pos = match.pos(); - if(!pos || !pos->is_param()) return; - - pos_.next_after_match(pos); - - arg_mapping newArg{index_, match.str(), pos.base()}; - newArg.repeat_ = occurrences_of(&pos->as_param()); - newArg.conflict_ = check_conflicts(pos.base()); - newArg.startsRepeatGroup_ = pos_.start_of_repeat_group(); - args_.push_back(std::move(newArg)); - - add_miss_candidates_after(pos); - clean_miss_candidates_for(pos.base()); - discard_alternative_miss_candidates(pos.base()); - - } - - //----------------------------------------------------- - bool check_conflicts(const dfs_traverser& match) - { - if(pos_.start_of_repeat_group()) return false; - bool conflict = false; - for(const auto& m : match.stack()) { - if(m.parent->exclusive()) { - for(auto i = args_.rbegin(); i != args_.rend(); ++i) { - if(!i->blocked()) { - for(const auto& c : i->match_.stack()) { - //sibling within same exclusive group => conflict - if(c.parent == m.parent && c.cur != m.cur) { - conflict = true; - i->conflict_ = true; - } - } - } - //check for conflicts only within current repeat cycle - if(i->startsRepeatGroup_) break; - } - } - } - return conflict; - } - - //----------------------------------------------------- - void clean_miss_candidates_for(const dfs_traverser& match) - { - auto i = std::find_if(missCand_.rbegin(), missCand_.rend(), - [&](const miss_candidate& m) { - return &(*m.pos) == &(*match); - }); - - if(i != missCand_.rend()) { - missCand_.erase(prev(i.base())); - } - } - - //----------------------------------------------------- - void discard_alternative_miss_candidates(const dfs_traverser& match) - { - if(missCand_.empty()) return; - //find out, if miss candidate is sibling of one of the same - //alternative groups that the current match is a member of - //if so, we can discard the miss - - //go through all exclusive groups of matching pattern - for(const auto& m : match.stack()) { - if(m.parent->exclusive()) { - for(auto i = int(missCand_.size())-1; i >= 0; --i) { - bool removed = false; - for(const auto& c : missCand_[i].pos.stack()) { - //sibling within same exclusive group => discard - if(c.parent == m.parent && c.cur != m.cur) { - missCand_.erase(missCand_.begin() + i); - if(missCand_.empty()) return; - removed = true; - break; - } - } - //remove miss candidates only within current repeat cycle - if(i > 0 && removed) { - if(missCand_[i-1].startsRepeatGroup) break; - } else { - if(missCand_[i].startsRepeatGroup) break; - } - } - } - } - } - - //----------------------------------------------------- - void add_miss_candidates_after(const scoped_dfs_traverser& match) - { - auto npos = match.base(); - if(npos.is_alternative()) npos.skip_alternatives(); - ++npos; - //need to add potential misses if: - //either new repeat group was started - const auto newRepGroup = match.innermost_repeat_group(); - if(newRepGroup) { - if(pos_.start_of_repeat_group()) { - for_each_potential_miss(std::move(npos), - [&,this](const dfs_traverser& pos) { - //only add candidates within repeat group - if(newRepGroup == pos.innermost_repeat_group()) { - missCand_.emplace_back(pos, index_, true); - } - }); - } - } - //... or an optional blocking param was hit - else if(match->blocking() && !match->required() && - npos.level() >= match.base().level()) - { - for_each_potential_miss(std::move(npos), - [&,this](const dfs_traverser& pos) { - //only add new candidates - if(std::find_if(missCand_.begin(), missCand_.end(), - [&](const miss_candidate& c){ - return &(*c.pos) == &(*pos); - }) == missCand_.end()) - { - missCand_.emplace_back(pos, index_); - } - }); - } - - } - - //----------------------------------------------------- - template - static void - for_each_potential_miss(dfs_traverser pos, Action&& action) - { - const auto level = pos.level(); - while(pos && pos.level() >= level) { - if(pos->is_group() ) { - const auto& g = pos->as_group(); - if(g.all_optional() || (g.exclusive() && g.any_optional())) { - pos.next_sibling(); - } else { - ++pos; - } - } else { //param - if(pos->required()) { - action(pos); - ++pos; - } else if(pos->blocking()) { //optional + blocking - pos.next_after_siblings(); - } else { - ++pos; - } - } - } - } - - - //--------------------------------------------------------------- - std::size_t occurrences_of(const parameter* p) const - { - if(!p) return 0; - - auto i = std::find_if(args_.rbegin(), args_.rend(), - [p](const arg_mapping& a){ return a.param() == p; }); - - if(i != args_.rend()) return i->repeat() + 1; - return 0; - } - - - //--------------------------------------------------------------- - const group* root_; - scoped_dfs_traverser pos_; - arg_index index_; - arg_index eaten_; - arg_mappings args_; - miss_candidates missCand_; - bool blocked_; -}; - - - - -/*************************************************************************//** - * - * @brief contains argument -> parameter mappings - * and missing parameters - * - *****************************************************************************/ -class parsing_result -{ -public: - using arg_mapping = parser::arg_mapping; - using arg_mappings = parser::arg_mappings; - using missing_event = parser::missing_event; - using missing_events = parser::missing_events; - using iterator = arg_mappings::const_iterator; - - //----------------------------------------------------- - /** @brief default: empty result */ - parsing_result() = default; - - parsing_result(arg_mappings arg2param, missing_events misses): - arg2param_{std::move(arg2param)}, missing_{std::move(misses)} - {} - - //----------------------------------------------------- - /** @brief returns number of arguments that could not be mapped to - * a parameter - */ - arg_mappings::size_type - unmapped_args_count() const noexcept { - return std::count_if(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return !a.param(); }); - } - - /** @brief returns if any argument could only be matched by an - * unreachable parameter - */ - bool any_blocked() const noexcept { - return std::any_of(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return a.blocked(); }); - } - - /** @brief returns if any argument matched more than one parameter - * that were mutually exclusive */ - bool any_conflict() const noexcept { - return std::any_of(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return a.conflict(); }); - } - - /** @brief returns if any parameter matched repeatedly although - * it was not allowed to */ - bool any_bad_repeat() const noexcept { - return std::any_of(arg2param_.begin(), arg2param_.end(), - [](const arg_mapping& a){ return a.bad_repeat(); }); - } - - /** @brief returns true if any parsing error / violation of the - * command line interface definition occurred */ - bool any_error() const noexcept { - return unmapped_args_count() > 0 || !missing().empty() || - any_blocked() || any_conflict() || any_bad_repeat(); - } - - /** @brief returns true if no parsing error / violation of the - * command line interface definition occurred */ - explicit operator bool() const noexcept { return !any_error(); } - - /** @brief access to range of missing parameter match events */ - const missing_events& missing() const noexcept { return missing_; } - - /** @brief returns non-mutating iterator to position of - * first argument -> parameter mapping */ - iterator begin() const noexcept { return arg2param_.begin(); } - /** @brief returns non-mutating iterator to position one past the - * last argument -> parameter mapping */ - iterator end() const noexcept { return arg2param_.end(); } - -private: - //----------------------------------------------------- - arg_mappings arg2param_; - missing_events missing_; -}; - - - - -namespace detail { -namespace { - -/*************************************************************************//** - * - * @brief correct some common problems - * does not - and MUST NOT - change the number of arguments - * (no insertions or deletions allowed) - * - *****************************************************************************/ -void sanitize_args(arg_list& args) -{ - //e.g. {"-o12", ".34"} -> {"-o", "12.34"} - - if(args.empty()) return; - - for(auto i = begin(args)+1; i != end(args); ++i) { - if(i != begin(args) && i->size() > 1 && - i->find('.') == 0 && std::isdigit((*i)[1]) ) - { - //find trailing digits in previous arg - using std::prev; - auto& prv = *prev(i); - auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(), - [](arg_string::value_type c){ - return std::isdigit(c); - }).base(); - - //handle leading sign - if(fstDigit > prv.begin() && - (*prev(fstDigit) == '+' || *prev(fstDigit) == '-')) - { - --fstDigit; - } - - //prepend digits from previous arg - i->insert(begin(*i), fstDigit, end(prv)); - - //erase digits in previous arg - prv.erase(fstDigit, end(prv)); - } - } -} - - - -/*************************************************************************//** - * - * @brief executes actions based on a parsing result - * - *****************************************************************************/ -void execute_actions(const parsing_result& res) -{ - for(const auto& m : res) { - if(m.param()) { - const auto& param = *(m.param()); - - if(m.repeat() > 0) param.notify_repeated(m.index()); - if(m.blocked()) param.notify_blocked(m.index()); - if(m.conflict()) param.notify_conflict(m.index()); - //main action - if(!m.any_error()) param.execute_actions(m.arg()); - } - } - - for(auto m : res.missing()) { - if(m.param()) m.param()->notify_missing(m.after_index()); - } -} - - - -/*************************************************************************//** - * - * @brief parses input args - * - *****************************************************************************/ -static parsing_result -parse_args(const arg_list& args, const group& cli, - arg_index offset = 0) -{ - //parse args and store unrecognized arg indices - parser parse{cli, offset}; - for(const auto& arg : args) { - parse(arg); - if(!parse.valid()) break; - } - - return parsing_result{parse.args(), parse.missed()}; -} - -/*************************************************************************//** - * - * @brief parses input args & executes actions - * - *****************************************************************************/ -static parsing_result -parse_and_execute(const arg_list& args, const group& cli, - arg_index offset = 0) -{ - auto result = parse_args(args, cli, offset); - - execute_actions(result); - - return result; -} - -} //anonymous namespace -} // namespace detail - - - - -/*************************************************************************//** - * - * @brief parses vector of arg strings and executes actions - * - *****************************************************************************/ -inline parsing_result -parse(arg_list args, const group& cli) -{ - detail::sanitize_args(args); - return detail::parse_and_execute(args, cli); -} - - -/*************************************************************************//** - * - * @brief parses initializer_list of C-style arg strings and executes actions - * - *****************************************************************************/ -inline parsing_result -parse(std::initializer_list arglist, const group& cli) -{ - arg_list args; - args.reserve(arglist.size()); - for(auto a : arglist) { - args.push_back(a); - } - - return parse(std::move(args), cli); -} - - -/*************************************************************************//** - * - * @brief parses range of arg strings and executes actions - * - *****************************************************************************/ -template -inline parsing_result -parse(InputIterator first, InputIterator last, const group& cli) -{ - return parse(arg_list(first,last), cli); -} - - -/*************************************************************************//** - * - * @brief parses the standard array of command line arguments; omits argv[0] - * - *****************************************************************************/ -inline parsing_result -parse(const int argc, char* argv[], const group& cli, arg_index offset = 1) -{ - arg_list args; - if(offset < argc) args.assign(argv+offset, argv+argc); - detail::sanitize_args(args); - return detail::parse_and_execute(args, cli, offset); -} - - - - - - -/*************************************************************************//** - * - * @brief filter predicate for parameters and groups; - * Can be used to limit documentation generation to parameter subsets. - * - *****************************************************************************/ -class param_filter -{ -public: - /** @brief only allow parameters with given prefix */ - param_filter& prefix(const arg_string& p) noexcept { - prefix_ = p; return *this; - } - /** @brief only allow parameters with given prefix */ - param_filter& prefix(arg_string&& p) noexcept { - prefix_ = std::move(p); return *this; - } - const arg_string& prefix() const noexcept { return prefix_; } - - /** @brief only allow parameters with given requirement status */ - param_filter& required(tri t) noexcept { required_ = t; return *this; } - tri required() const noexcept { return required_; } - - /** @brief only allow parameters with given blocking status */ - param_filter& blocking(tri t) noexcept { blocking_ = t; return *this; } - tri blocking() const noexcept { return blocking_; } - - /** @brief only allow parameters with given repeatable status */ - param_filter& repeatable(tri t) noexcept { repeatable_ = t; return *this; } - tri repeatable() const noexcept { return repeatable_; } - - /** @brief only allow parameters with given docstring status */ - param_filter& has_doc(tri t) noexcept { hasDoc_ = t; return *this; } - tri has_doc() const noexcept { return hasDoc_; } - - - /** @brief returns true, if parameter satisfies all filters */ - bool operator() (const parameter& p) const noexcept { - if(!prefix_.empty()) { - if(!std::any_of(p.flags().begin(), p.flags().end(), - [&](const arg_string& flag){ - return str::has_prefix(flag, prefix_); - })) return false; - } - if(required() != p.required()) return false; - if(blocking() != p.blocking()) return false; - if(repeatable() != p.repeatable()) return false; - if(has_doc() != !p.doc().empty()) return false; - return true; - } - -private: - arg_string prefix_; - tri required_ = tri::either; - tri blocking_ = tri::either; - tri repeatable_ = tri::either; - tri hasDoc_ = tri::yes; -}; - - - - - - -/*************************************************************************//** - * - * @brief documentation formatting options - * - *****************************************************************************/ -class doc_formatting -{ -public: - using string = doc_string; - - /** @brief same as 'first_column' */ -#if __cplusplus >= 201402L - [[deprecated]] -#endif - doc_formatting& start_column(int col) { return first_column(col); } -#if __cplusplus >= 201402L - [[deprecated]] -#endif - int start_column() const noexcept { return first_column(); } - - /** @brief determines column where documentation printing starts */ - doc_formatting& - first_column(int col) { - //limit to [0,last_column] but push doc_column to the right if necessary - if(col < 0) col = 0; - else if(col > last_column()) col = last_column(); - if(col > doc_column()) doc_column(first_column()); - firstCol_ = col; - return *this; - } - int first_column() const noexcept { - return firstCol_; - } - - /** @brief determines column where docstrings start */ - doc_formatting& - doc_column(int col) { - //limit to [first_column,last_column] - if(col < 0) col = 0; - else if(col < first_column()) col = first_column(); - else if(col > last_column()) col = last_column(); - docCol_ = col; - return *this; - } - int doc_column() const noexcept { - return docCol_; - } - - /** @brief determines column that no documentation text must exceed; - * (text should be wrapped appropriately after this column) - */ - doc_formatting& - last_column(int col) { - //limit to [first_column,oo] but push doc_column to the left if necessary - if(col < first_column()) col = first_column(); - if(col < doc_column()) doc_column(col); - lastCol_ = col; - return *this; - } - - int last_column() const noexcept { - return lastCol_; - } - - /** @brief determines indent of documentation lines - * for children of a documented group */ - doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; } - int indent_size() const noexcept { return indentSize_; } - - /** @brief determines string to be used - * if a parameter has no flags and no label */ - doc_formatting& empty_label(const string& label) { - emptyLabel_ = label; - return *this; - } - const string& empty_label() const noexcept { return emptyLabel_; } - - /** @brief determines string for separating parameters */ - doc_formatting& param_separator(const string& sep) { - paramSep_ = sep; - return *this; - } - const string& param_separator() const noexcept { return paramSep_; } - - /** @brief determines string for separating groups (in usage lines) */ - doc_formatting& group_separator(const string& sep) { - groupSep_ = sep; - return *this; - } - const string& group_separator() const noexcept { return groupSep_; } - - /** @brief determines string for separating alternative parameters */ - doc_formatting& alternative_param_separator(const string& sep) { - altParamSep_ = sep; - return *this; - } - const string& alternative_param_separator() const noexcept { return altParamSep_; } - - /** @brief determines string for separating alternative groups */ - doc_formatting& alternative_group_separator(const string& sep) { - altGroupSep_ = sep; - return *this; - } - const string& alternative_group_separator() const noexcept { return altGroupSep_; } - - /** @brief determines string for separating flags of the same parameter */ - doc_formatting& flag_separator(const string& sep) { - flagSep_ = sep; - return *this; - } - const string& flag_separator() const noexcept { return flagSep_; } - - /** @brief determines strings surrounding parameter labels */ - doc_formatting& - surround_labels(const string& prefix, const string& postfix) { - labelPre_ = prefix; - labelPst_ = postfix; - return *this; - } - const string& label_prefix() const noexcept { return labelPre_; } - const string& label_postfix() const noexcept { return labelPst_; } - - /** @brief determines strings surrounding optional parameters/groups */ - doc_formatting& - surround_optional(const string& prefix, const string& postfix) { - optionPre_ = prefix; - optionPst_ = postfix; - return *this; - } - const string& optional_prefix() const noexcept { return optionPre_; } - const string& optional_postfix() const noexcept { return optionPst_; } - - /** @brief determines strings surrounding repeatable parameters/groups */ - doc_formatting& - surround_repeat(const string& prefix, const string& postfix) { - repeatPre_ = prefix; - repeatPst_ = postfix; - return *this; - } - const string& repeat_prefix() const noexcept { return repeatPre_; } - const string& repeat_postfix() const noexcept { return repeatPst_; } - - /** @brief determines strings surrounding exclusive groups */ - doc_formatting& - surround_alternatives(const string& prefix, const string& postfix) { - alternPre_ = prefix; - alternPst_ = postfix; - return *this; - } - const string& alternatives_prefix() const noexcept { return alternPre_; } - const string& alternatives_postfix() const noexcept { return alternPst_; } - - /** @brief determines strings surrounding alternative flags */ - doc_formatting& - surround_alternative_flags(const string& prefix, const string& postfix) { - alternFlagPre_ = prefix; - alternFlagPst_ = postfix; - return *this; - } - const string& alternative_flags_prefix() const noexcept { return alternFlagPre_; } - const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; } - - /** @brief determines strings surrounding non-exclusive groups */ - doc_formatting& - surround_group(const string& prefix, const string& postfix) { - groupPre_ = prefix; - groupPst_ = postfix; - return *this; - } - const string& group_prefix() const noexcept { return groupPre_; } - const string& group_postfix() const noexcept { return groupPst_; } - - /** @brief determines strings surrounding joinable groups */ - doc_formatting& - surround_joinable(const string& prefix, const string& postfix) { - joinablePre_ = prefix; - joinablePst_ = postfix; - return *this; - } - const string& joinable_prefix() const noexcept { return joinablePre_; } - const string& joinable_postfix() const noexcept { return joinablePst_; } - - /** @brief determines maximum number of flags per parameter to be printed - * in detailed parameter documentation lines */ - doc_formatting& max_flags_per_param_in_doc(int max) { - maxAltInDocs_ = max > 0 ? max : 0; - return *this; - } - int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; } - - /** @brief determines maximum number of flags per parameter to be printed - * in usage lines */ - doc_formatting& max_flags_per_param_in_usage(int max) { - maxAltInUsage_ = max > 0 ? max : 0; - return *this; - } - int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; } - - /** @brief determines number of empty rows after one single-line - * documentation entry */ - doc_formatting& line_spacing(int lines) { - lineSpc_ = lines > 0 ? lines : 0; - return *this; - } - int line_spacing() const noexcept { return lineSpc_; } - - /** @brief determines number of empty rows before and after a paragraph; - * a paragraph is defined by a documented group or if - * a parameter documentation entry used more than one line */ - doc_formatting& paragraph_spacing(int lines) { - paragraphSpc_ = lines > 0 ? lines : 0; - return *this; - } - int paragraph_spacing() const noexcept { return paragraphSpc_; } - - /** @brief determines if alternative flags with a common prefix should - * be printed in a merged fashion */ - doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) { - mergeAltCommonPfx_ = yes; - return *this; - } - bool merge_alternative_flags_with_common_prefix() const noexcept { - return mergeAltCommonPfx_; - } - - /** @brief determines if joinable flags with a common prefix should - * be printed in a merged fashion */ - doc_formatting& merge_joinable_with_common_prefix(bool yes = true) { - mergeJoinableCommonPfx_ = yes; - return *this; - } - bool merge_joinable_with_common_prefix() const noexcept { - return mergeJoinableCommonPfx_; - } - - /** @brief determines if children of exclusive groups should be printed - * on individual lines if the exceed 'alternatives_min_split_size' - */ - doc_formatting& split_alternatives(bool yes = true) { - splitTopAlt_ = yes; - return *this; - } - bool split_alternatives() const noexcept { - return splitTopAlt_; - } - - /** @brief determines how many children exclusive groups can have before - * their children are printed on individual usage lines */ - doc_formatting& alternatives_min_split_size(int size) { - groupSplitSize_ = size > 0 ? size : 0; - return *this; - } - int alternatives_min_split_size() const noexcept { return groupSplitSize_; } - - /** @brief determines whether to ignore new line characters in docstrings - */ - doc_formatting& ignore_newline_chars(bool yes = true) { - ignoreNewlines_ = yes; - return *this; - } - bool ignore_newline_chars() const noexcept { - return ignoreNewlines_; - } - -private: - string paramSep_ = string(" "); - string groupSep_ = string(" "); - string altParamSep_ = string("|"); - string altGroupSep_ = string(" | "); - string flagSep_ = string(", "); - string labelPre_ = string("<"); - string labelPst_ = string(">"); - string optionPre_ = string("["); - string optionPst_ = string("]"); - string repeatPre_ = string(""); - string repeatPst_ = string("..."); - string groupPre_ = string("("); - string groupPst_ = string(")"); - string alternPre_ = string("("); - string alternPst_ = string(")"); - string alternFlagPre_ = string(""); - string alternFlagPst_ = string(""); - string joinablePre_ = string("("); - string joinablePst_ = string(")"); - string emptyLabel_ = string(""); - int firstCol_ = 8; - int docCol_ = 20; - int lastCol_ = 100; - int indentSize_ = 4; - int maxAltInUsage_ = 1; - int maxAltInDocs_ = 32; - int lineSpc_ = 0; - int paragraphSpc_ = 1; - int groupSplitSize_ = 3; - bool splitTopAlt_ = true; - bool mergeAltCommonPfx_ = false; - bool mergeJoinableCommonPfx_ = true; - bool ignoreNewlines_ = false; -}; - - - -namespace detail { - -/*************************************************************************//** - * - * @brief stream decorator - * that applies formatting like line wrapping - * - *****************************************************************************/ -template -class formatting_ostream -{ -public: - using string_type = StringT; - using size_type = typename string_type::size_type; - using char_type = typename string_type::value_type; - - formatting_ostream(OStream& os): - os_(os), - curCol_{0}, firstCol_{0}, lastCol_{100}, - hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2}, - curBlankLines_{0}, curParagraphLines_{1}, - totalNonBlankLines_{0}, - ignoreInputNls_{false} - {} - - - //--------------------------------------------------------------- - const OStream& base() const noexcept { return os_; } - OStream& base() noexcept { return os_; } - - bool good() const { return os_.good(); } - - - //--------------------------------------------------------------- - /** @brief determines the leftmost border of the text body */ - formatting_ostream& first_column(int c) { - firstCol_ = c < 0 ? 0 : c; - return *this; - } - int first_column() const noexcept { return firstCol_; } - - /** @brief determines the rightmost border of the text body */ - formatting_ostream& last_column(int c) { - lastCol_ = c < 0 ? 0 : c; - return *this; - } - - int last_column() const noexcept { return lastCol_; } - - int text_width() const noexcept { - return lastCol_ - firstCol_; - } - - /** @brief additional indentation for the 2nd, 3rd, ... line of - a paragraph (sequence of soft-wrapped lines) */ - formatting_ostream& hanging_indent(int amount) { - hangingIndent_ = amount; - return *this; - } - int hanging_indent() const noexcept { - return hangingIndent_; - } - - /** @brief amount of blank lines between paragraphs */ - formatting_ostream& paragraph_spacing(int lines) { - paragraphSpacing_ = lines; - return *this; - } - int paragraph_spacing() const noexcept { - return paragraphSpacing_; - } - - /** @brief insert paragraph spacing - if paragraph is at least 'lines' lines long */ - formatting_ostream& min_paragraph_lines_for_spacing(int lines) { - paragraphSpacingThreshold_ = lines; - return *this; - } - int min_paragraph_lines_for_spacing() const noexcept { - return paragraphSpacingThreshold_; - } - - /** @brief if set to true, newline characters will be ignored */ - formatting_ostream& ignore_newline_chars(bool yes) { - ignoreInputNls_ = yes; - return *this; - } - - bool ignore_newline_chars() const noexcept { - return ignoreInputNls_; - } - - - //--------------------------------------------------------------- - /* @brief insert 'n' spaces */ - void write_spaces(int n) { - if(n < 1) return; - os_ << string_type(size_type(n), ' '); - curCol_ += n; - } - - /* @brief go to new line, but continue current paragraph */ - void wrap_soft(int times = 1) { - if(times < 1) return; - if(times > 1) { - os_ << string_type(size_type(times), '\n'); - } else { - os_ << '\n'; - } - curCol_ = 0; - ++curParagraphLines_; - } - - /* @brief go to new line, and start a new paragraph */ - void wrap_hard(int times = 1) { - if(times < 1) return; - - if(paragraph_spacing() > 0 && - paragraph_lines() >= min_paragraph_lines_for_spacing()) - { - times = paragraph_spacing() + 1; - } - - if(times > 1) { - os_ << string_type(size_type(times), '\n'); - curBlankLines_ += times - 1; - } else { - os_ << '\n'; - } - if(at_begin_of_line()) { - ++curBlankLines_; - } - curCol_ = 0; - curParagraphLines_ = 1; - } - - - //--------------------------------------------------------------- - bool at_begin_of_line() const noexcept { - return curCol_ <= current_line_begin(); - } - int current_line_begin() const noexcept { - return in_hanging_part_of_paragraph() - ? firstCol_ + hangingIndent_ - : firstCol_; - } - - int current_column() const noexcept { - return curCol_; - } - - int total_non_blank_lines() const noexcept { - return totalNonBlankLines_; - } - int paragraph_lines() const noexcept { - return curParagraphLines_; - } - int blank_lines_before_paragraph() const noexcept { - return curBlankLines_; - } - - - //--------------------------------------------------------------- - template - friend formatting_ostream& - operator << (formatting_ostream& os, const T& x) { - os.write(x); - return os; - } - - void flush() { - os_.flush(); - } - - -private: - bool in_hanging_part_of_paragraph() const noexcept { - return hanging_indent() > 0 && paragraph_lines() > 1; - } - bool current_line_empty() const noexcept { - return curCol_ < 1; - } - bool left_of_text_area() const noexcept { - return curCol_ < current_line_begin(); - } - bool right_of_text_area() const noexcept { - return curCol_ > lastCol_; - } - int columns_left_in_line() const noexcept { - return lastCol_ - std::max(current_line_begin(), curCol_); - } - - void fix_indent() { - if(left_of_text_area()) { - const auto fst = current_line_begin(); - write_spaces(fst - curCol_); - curCol_ = fst; - } - } - - template - bool only_whitespace(Iter first, Iter last) const { - return last == std::find_if_not(first, last, - [](char_type c) { return std::isspace(c); }); - } - - /** @brief write any object */ - template - void write(const T& x) { - std::ostringstream ss; - ss << x; - write(std::move(ss).str()); - } - - /** @brief write a stringstream */ - void write(const std::ostringstream& s) { - write(s.str()); - } - - /** @brief write a string */ - void write(const string_type& s) { - write(s.begin(), s.end()); - } - - /** @brief partition output into lines */ - template - void write(Iter first, Iter last) - { - if(first == last) return; - if(*first == '\n') { - if(!ignore_newline_chars()) wrap_hard(); - ++first; - if(first == last) return; - } - auto i = std::find(first, last, '\n'); - if(i != last) { - if(ignore_newline_chars()) ++i; - if(i != last) { - write_line(first, i); - write(i, last); - } - } - else { - write_line(first, last); - } - } - - /** @brief handle line wrapping due to column constraints */ - template - void write_line(Iter first, Iter last) - { - if(first == last) return; - if(only_whitespace(first, last)) return; - - if(right_of_text_area()) wrap_soft(); - - if(at_begin_of_line()) { - //discard whitespace, it we start a new line - first = std::find_if(first, last, - [](char_type c) { return !std::isspace(c); }); - if(first == last) return; - } - - const auto n = int(std::distance(first,last)); - const auto m = columns_left_in_line(); - //if text to be printed is too long for one line -> wrap - if(n > m) { - //break before word, if break is mid-word - auto breakat = first + m; - while(breakat > first && !std::isspace(*breakat)) --breakat; - //could not find whitespace before word -> try after the word - if(!std::isspace(*breakat) && breakat == first) { - breakat = std::find_if(first+m, last, - [](char_type c) { return std::isspace(c); }); - } - if(breakat > first) { - if(curCol_ < 1) ++totalNonBlankLines_; - fix_indent(); - std::copy(first, breakat, std::ostream_iterator(os_)); - curBlankLines_ = 0; - } - if(breakat < last) { - wrap_soft(); - write_line(breakat, last); - } - } - else { - if(curCol_ < 1) ++totalNonBlankLines_; - fix_indent(); - std::copy(first, last, std::ostream_iterator(os_)); - curCol_ += n; - curBlankLines_ = 0; - } - } - - /** @brief write a single character */ - void write(char_type c) - { - if(c == '\n') { - if(!ignore_newline_chars()) wrap_hard(); - } - else { - if(at_begin_of_line()) ++totalNonBlankLines_; - fix_indent(); - os_ << c; - ++curCol_; - } - } - - OStream& os_; - int curCol_; - int firstCol_; - int lastCol_; - int hangingIndent_; - int paragraphSpacing_; - int paragraphSpacingThreshold_; - int curBlankLines_; - int curParagraphLines_; - int totalNonBlankLines_; - bool ignoreInputNls_; -}; - - -} - - - - -/*************************************************************************//** - * - * @brief generates usage lines - * - * @details lazily evaluated - * - *****************************************************************************/ -class usage_lines -{ -public: - using string = doc_string; - - usage_lines(const group& cli, string prefix = "", - const doc_formatting& fmt = doc_formatting{}) - : - cli_(cli), fmt_(fmt), prefix_(std::move(prefix)) - { - if(!prefix_.empty()) prefix_ += ' '; - } - - usage_lines(const group& cli, const doc_formatting& fmt): - usage_lines(cli, "", fmt) - {} - - usage_lines& ommit_outermost_group_surrounders(bool yes) { - ommitOutermostSurrounders_ = yes; - return *this; - } - bool ommit_outermost_group_surrounders() const { - return ommitOutermostSurrounders_; - } - - template - inline friend OStream& operator << (OStream& os, const usage_lines& p) { - p.write(os); - return os; - } - - string str() const { - std::ostringstream os; os << *this; return os.str(); - } - - -private: - using stream_t = detail::formatting_ostream<>; - const group& cli_; - doc_formatting fmt_; - string prefix_; - bool ommitOutermostSurrounders_ = false; - - - //----------------------------------------------------- - struct context { - group::depth_first_traverser pos; - std::stack separators; - std::stack postfixes; - int level = 0; - const group* outermost = nullptr; - bool linestart = false; - bool useOutermost = true; - int line = 0; - - bool is_singleton() const noexcept { - return linestart && pos.is_last_in_path(); - } - bool is_alternative() const noexcept { - return pos.parent().exclusive(); - } - }; - - - /***************************************************************//** - * - * @brief writes usage text for command line parameters - * - *******************************************************************/ - template - void write(OStream& os) const - { - detail::formatting_ostream fos(os); - fos.first_column(fmt_.first_column()); - fos.last_column(fmt_.last_column()); - - auto hindent = int(prefix_.size()); - if(fos.first_column() + hindent >= int(0.4 * fos.text_width())) { - hindent = fmt_.indent_size(); - } - fos.hanging_indent(hindent); - - fos.paragraph_spacing(fmt_.paragraph_spacing()); - fos.min_paragraph_lines_for_spacing(2); - fos.ignore_newline_chars(fmt_.ignore_newline_chars()); - - context cur; - cur.pos = cli_.begin_dfs(); - cur.linestart = true; - cur.level = cur.pos.level(); - cur.outermost = &cli_; - - write(fos, cur, prefix_); - } - - - /***************************************************************//** - * - * @brief writes usage text for command line parameters - * - * @param prefix all that goes in front of current things to print - * - *******************************************************************/ - template - void write(OStream& os, context cur, string prefix) const - { - if(!cur.pos) return; - - std::ostringstream buf; - if(cur.linestart) buf << prefix; - const auto initPos = buf.tellp(); - - cur.level = cur.pos.level(); - - if(cur.useOutermost) { - //we cannot start outside of the outermost group - //so we have to treat it separately - start_group(buf, cur.pos.parent(), cur); - if(!cur.pos) { - os << buf.str(); - return; - } - } - else { - //don't visit siblings of starter node - cur.pos.skip_siblings(); - } - check_end_group(buf, cur); - - do { - if(buf.tellp() > initPos) cur.linestart = false; - if(!cur.linestart && !cur.pos.is_first_in_parent()) { - buf << cur.separators.top(); - } - if(cur.pos->is_group()) { - start_group(buf, cur.pos->as_group(), cur); - if(!cur.pos) { - os << buf.str(); - return; - } - } - else { - buf << param_label(cur.pos->as_param(), cur); - ++cur.pos; - } - check_end_group(buf, cur); - } while(cur.pos); - - os << buf.str(); - } - - - /***************************************************************//** - * - * @brief handles pattern group surrounders and separators - * and alternative splitting - * - *******************************************************************/ - void start_group(std::ostringstream& os, - const group& group, context& cur) const - { - //does cur.pos already point to a member or to group itself? - //needed for special treatment of outermost group - const bool alreadyInside = &(cur.pos.parent()) == &group; - - auto lbl = joined_label(group, cur); - if(!lbl.empty()) { - os << lbl; - cur.linestart = false; - //skip over entire group as its label has already been created - if(alreadyInside) { - cur.pos.next_after_siblings(); - } else { - cur.pos.next_sibling(); - } - } - else { - const bool splitAlternatives = group.exclusive() && - fmt_.split_alternatives() && - std::any_of(group.begin(), group.end(), - [this](const pattern& p) { - return int(p.param_count()) >= fmt_.alternatives_min_split_size(); - }); - - if(splitAlternatives) { - cur.postfixes.push(""); - cur.separators.push(""); - //recursively print alternative paths in decision-DAG - //enter group? - if(!alreadyInside) ++cur.pos; - cur.linestart = true; - cur.useOutermost = false; - auto pfx = os.str(); - os.str(""); - //print paths in DAG starting at each group member - for(std::size_t i = 0; i < group.size(); ++i) { - std::stringstream buf; - cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr; - write(buf, cur, pfx); - if(buf.tellp() > int(pfx.size())) { - os << buf.str(); - if(i < group.size()-1) { - if(cur.line > 0) { - os << string(fmt_.line_spacing(), '\n'); - } - ++cur.line; - os << '\n'; - } - } - cur.pos.next_sibling(); //do not descend into members - } - cur.pos.invalidate(); //signal end-of-path - return; - } - else { - //pre & postfixes, separators - auto surround = group_surrounders(group, cur); - os << surround.first; - cur.postfixes.push(std::move(surround.second)); - cur.separators.push(group_separator(group, fmt_)); - //descend into group? - if(!alreadyInside) ++cur.pos; - } - } - cur.level = cur.pos.level(); - } - - - /***************************************************************//** - * - *******************************************************************/ - void check_end_group(std::ostringstream& os, context& cur) const - { - for(; cur.level > cur.pos.level(); --cur.level) { - os << cur.postfixes.top(); - cur.postfixes.pop(); - cur.separators.pop(); - } - cur.level = cur.pos.level(); - } - - - /***************************************************************//** - * - * @brief makes usage label for one command line parameter - * - *******************************************************************/ - string param_label(const parameter& p, const context& cur) const - { - const auto& parent = cur.pos.parent(); - - const bool startsOptionalSequence = - parent.size() > 1 && p.blocking() && cur.pos.is_first_in_parent(); - - const bool outermost = - ommitOutermostSurrounders_ && cur.outermost == &parent; - - const bool showopt = !cur.is_alternative() && !p.required() - && !startsOptionalSequence && !outermost; - - const bool showrep = p.repeatable() && !outermost; - - string lbl; - - if(showrep) lbl += fmt_.repeat_prefix(); - if(showopt) lbl += fmt_.optional_prefix(); - - const auto& flags = p.flags(); - if(!flags.empty()) { - const int n = std::min(fmt_.max_flags_per_param_in_usage(), - int(flags.size())); - - const bool surrAlt = n > 1 && !showopt && !cur.is_singleton(); - - if(surrAlt) lbl += fmt_.alternative_flags_prefix(); - bool sep = false; - for(int i = 0; i < n; ++i) { - if(sep) { - if(cur.is_singleton()) - lbl += fmt_.alternative_group_separator(); - else - lbl += fmt_.flag_separator(); - } - lbl += flags[i]; - sep = true; - } - if(surrAlt) lbl += fmt_.alternative_flags_postfix(); - } - else { - if(!p.label().empty()) { - lbl += fmt_.label_prefix() - + p.label() - + fmt_.label_postfix(); - } else if(!fmt_.empty_label().empty()) { - lbl += fmt_.label_prefix() - + fmt_.empty_label() - + fmt_.label_postfix(); - } else { - return ""; - } - } - - if(showopt) lbl += fmt_.optional_postfix(); - if(showrep) lbl += fmt_.repeat_postfix(); - - return lbl; - } - - - /***************************************************************//** - * - * @brief prints flags in one group in a merged fashion - * - *******************************************************************/ - string joined_label(const group& g, const context& cur) const - { - if(!fmt_.merge_alternative_flags_with_common_prefix() && - !fmt_.merge_joinable_with_common_prefix()) return ""; - - const bool flagsonly = std::all_of(g.begin(), g.end(), - [](const pattern& p){ - return p.is_param() && !p.as_param().flags().empty(); - }); - - if(!flagsonly) return ""; - - const bool showOpt = g.all_optional() && - !(ommitOutermostSurrounders_ && cur.outermost == &g); - - auto pfx = g.common_flag_prefix(); - if(pfx.empty()) return ""; - - const auto n = pfx.size(); - if(g.exclusive() && - fmt_.merge_alternative_flags_with_common_prefix()) - { - string lbl; - if(showOpt) lbl += fmt_.optional_prefix(); - lbl += pfx + fmt_.alternatives_prefix(); - bool first = true; - for(const auto& p : g) { - if(p.is_param()) { - if(first) - first = false; - else - lbl += fmt_.alternative_param_separator(); - lbl += p.as_param().flags().front().substr(n); - } - } - lbl += fmt_.alternatives_postfix(); - if(showOpt) lbl += fmt_.optional_postfix(); - return lbl; - } - //no alternatives, but joinable flags - else if(g.joinable() && - fmt_.merge_joinable_with_common_prefix()) - { - const bool allSingleChar = std::all_of(g.begin(), g.end(), - [&](const pattern& p){ - return p.is_param() && - p.as_param().flags().front().substr(n).size() == 1; - }); - - if(allSingleChar) { - string lbl; - if(showOpt) lbl += fmt_.optional_prefix(); - lbl += pfx; - for(const auto& p : g) { - if(p.is_param()) - lbl += p.as_param().flags().front().substr(n); - } - if(showOpt) lbl += fmt_.optional_postfix(); - return lbl; - } - } - - return ""; - } - - - /***************************************************************//** - * - * @return symbols with which to surround a group - * - *******************************************************************/ - std::pair - group_surrounders(const group& group, const context& cur) const - { - string prefix; - string postfix; - - const bool isOutermost = &group == cur.outermost; - if(isOutermost && ommitOutermostSurrounders_) - return {string{}, string{}}; - - if(group.exclusive()) { - if(group.all_optional()) { - prefix = fmt_.optional_prefix(); - postfix = fmt_.optional_postfix(); - if(group.all_flagless()) { - prefix += fmt_.label_prefix(); - postfix = fmt_.label_prefix() + postfix; - } - } else if(group.all_flagless()) { - prefix = fmt_.label_prefix(); - postfix = fmt_.label_postfix(); - } else if(!cur.is_singleton() || !isOutermost) { - prefix = fmt_.alternatives_prefix(); - postfix = fmt_.alternatives_postfix(); - } - } - else if(group.size() > 1 && - group.front().blocking() && !group.front().required()) - { - prefix = fmt_.optional_prefix(); - postfix = fmt_.optional_postfix(); - } - else if(group.size() > 1 && cur.is_alternative() && - &group != cur.outermost) - { - prefix = fmt_.group_prefix(); - postfix = fmt_.group_postfix(); - } - else if(!group.exclusive() && - group.joinable() && !cur.linestart) - { - prefix = fmt_.joinable_prefix(); - postfix = fmt_.joinable_postfix(); - } - - if(group.repeatable()) { - if(prefix.empty()) prefix = fmt_.group_prefix(); - prefix = fmt_.repeat_prefix() + prefix; - if(postfix.empty()) postfix = fmt_.group_postfix(); - postfix += fmt_.repeat_postfix(); - } - - return {std::move(prefix), std::move(postfix)}; - } - - - /***************************************************************//** - * - * @return symbol that separates members of a group - * - *******************************************************************/ - static string - group_separator(const group& group, const doc_formatting& fmt) - { - const bool only1ParamPerMember = std::all_of(group.begin(), group.end(), - [](const pattern& p) { return p.param_count() < 2; }); - - if(only1ParamPerMember) { - if(group.exclusive()) { - return fmt.alternative_param_separator(); - } else { - return fmt.param_separator(); - } - } - else { //there is at least one large group inside - if(group.exclusive()) { - return fmt.alternative_group_separator(); - } else { - return fmt.group_separator(); - } - } - } -}; - - - - -/*************************************************************************//** - * - * @brief generates parameter and group documentation from docstrings - * - * @details lazily evaluated - * - *****************************************************************************/ -class documentation -{ -public: - using string = doc_string; - using filter_function = std::function; - - documentation(const group& cli, - const doc_formatting& fmt = doc_formatting{}, - filter_function filter = param_filter{}) - : - cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{std::move(filter)} - { - //necessary, because we re-use "usage_lines" to generate - //labels for documented groups - usgFmt_.max_flags_per_param_in_usage( - usgFmt_.max_flags_per_param_in_doc()); - } - - documentation(const group& cli, filter_function filter) : - documentation{cli, doc_formatting{}, std::move(filter)} - {} - - documentation(const group& cli, const param_filter& filter) : - documentation{cli, doc_formatting{}, - [filter](const parameter& p) { return filter(p); }} - {} - - template - inline friend OStream& operator << (OStream& os, const documentation& p) { - p.write(os); - return os; - } - - string str() const { - std::ostringstream os; - write(os); - return os.str(); - } - - -private: - using dfs_traverser = group::depth_first_traverser; - - const group& cli_; - doc_formatting fmt_; - doc_formatting usgFmt_; - filter_function filter_; - enum class paragraph { param, group }; - - - /***************************************************************//** - * - * @brief writes documentation to output stream - * - *******************************************************************/ - template - void write(OStream& os) const { - detail::formatting_ostream fos(os); - fos.first_column(fmt_.first_column()); - fos.last_column(fmt_.last_column()); - fos.hanging_indent(0); - fos.paragraph_spacing(0); - fos.ignore_newline_chars(fmt_.ignore_newline_chars()); - print_doc(fos, cli_); - } - - - /***************************************************************//** - * - * @brief writes full documentation text for command line parameters - * - *******************************************************************/ - template - void print_doc(detail::formatting_ostream& os, - const group& cli, int indentLvl = 0) const - { - if(cli.empty()) return; - - //if group itself doesn't have docstring - if(cli.doc().empty()) { - for(const auto& p : cli) { - print_doc(os, p, indentLvl); - } - } - else { //group itself does have docstring - bool anyDocInside = std::any_of(cli.begin(), cli.end(), - [](const pattern& p){ return !p.doc().empty(); }); - - if(anyDocInside) { //group docstring as title, then child entries - handle_spacing(os, paragraph::group, indentLvl); - os << cli.doc(); - for(const auto& p : cli) { - print_doc(os, p, indentLvl + 1); - } - } - else { //group label first then group docstring - auto lbl = usage_lines(cli, usgFmt_) - .ommit_outermost_group_surrounders(true).str(); - - str::trim(lbl); - handle_spacing(os, paragraph::param, indentLvl); - print_entry(os, lbl, cli.doc()); - } - } - } - - - /***************************************************************//** - * - * @brief writes documentation text for one group or parameter - * - *******************************************************************/ - template - void print_doc(detail::formatting_ostream& os, - const pattern& ptrn, int indentLvl) const - { - if(ptrn.is_group()) { - print_doc(os, ptrn.as_group(), indentLvl); - } - else { - const auto& p = ptrn.as_param(); - if(!filter_(p)) return; - - handle_spacing(os, paragraph::param, indentLvl); - print_entry(os, param_label(p, fmt_), p.doc()); - } - } - - /***************************************************************//** - * - * @brief handles line and paragraph spacings - * - *******************************************************************/ - template - void handle_spacing(detail::formatting_ostream& os, - paragraph p, int indentLvl) const - { - const auto oldIndent = os.first_column(); - const auto indent = fmt_.first_column() + indentLvl * fmt_.indent_size(); - - if(os.total_non_blank_lines() < 1) { - os.first_column(indent); - return; - } - - if(os.paragraph_lines() > 1 || indent < oldIndent) { - os.wrap_hard(fmt_.paragraph_spacing() + 1); - } else { - os.wrap_hard(); - } - - if(p == paragraph::group) { - if(os.blank_lines_before_paragraph() < fmt_.paragraph_spacing()) { - os.wrap_hard(fmt_.paragraph_spacing() - os.blank_lines_before_paragraph()); - } - } - else if(os.blank_lines_before_paragraph() < fmt_.line_spacing()) { - os.wrap_hard(fmt_.line_spacing() - os.blank_lines_before_paragraph()); - } - os.first_column(indent); - } - - /*********************************************************************//** - * - * @brief prints one entry = label + docstring - * - ************************************************************************/ - template - void print_entry(detail::formatting_ostream& os, - const string& label, const string& docstr) const - { - if(label.empty()) return; - - os << label; - - if(!docstr.empty()) { - if(os.current_column() >= fmt_.doc_column()) os.wrap_soft(); - const auto oldcol = os.first_column(); - os.first_column(fmt_.doc_column()); - os << docstr; - os.first_column(oldcol); - } - } - - - /*********************************************************************//** - * - * @brief makes label for one parameter - * - ************************************************************************/ - static doc_string - param_label(const parameter& param, const doc_formatting& fmt) - { - doc_string lbl; - - if(param.repeatable()) lbl += fmt.repeat_prefix(); - - const auto& flags = param.flags(); - if(!flags.empty()) { - lbl += flags[0]; - const int n = std::min(fmt.max_flags_per_param_in_doc(), - int(flags.size())); - for(int i = 1; i < n; ++i) { - lbl += fmt.flag_separator() + flags[i]; - } - } - else if(!param.label().empty() || !fmt.empty_label().empty()) { - lbl += fmt.label_prefix(); - if(!param.label().empty()) { - lbl += param.label(); - } else { - lbl += fmt.empty_label(); - } - lbl += fmt.label_postfix(); - } - - if(param.repeatable()) lbl += fmt.repeat_postfix(); - - return lbl; - } - -}; - - - - -/*************************************************************************//** - * - * @brief stores strings for man page sections - * - *****************************************************************************/ -class man_page -{ -public: - //--------------------------------------------------------------- - using string = doc_string; - - //--------------------------------------------------------------- - /** @brief man page section */ - class section { - public: - using string = doc_string; - - section(string stitle, string scontent): - title_{std::move(stitle)}, content_{std::move(scontent)} - {} - - const string& title() const noexcept { return title_; } - const string& content() const noexcept { return content_; } - - private: - string title_; - string content_; - }; - -private: - using section_store = std::vector
; - -public: - //--------------------------------------------------------------- - using value_type = section; - using const_iterator = section_store::const_iterator; - using size_type = section_store::size_type; - - - //--------------------------------------------------------------- - man_page& - append_section(string title, string content) - { - sections_.emplace_back(std::move(title), std::move(content)); - return *this; - } - //----------------------------------------------------- - man_page& - prepend_section(string title, string content) - { - sections_.emplace(sections_.begin(), - std::move(title), std::move(content)); - return *this; - } - - - //--------------------------------------------------------------- - const section& operator [] (size_type index) const noexcept { - return sections_[index]; - } - - //--------------------------------------------------------------- - size_type size() const noexcept { return sections_.size(); } - - bool empty() const noexcept { return sections_.empty(); } - - - //--------------------------------------------------------------- - const_iterator begin() const noexcept { return sections_.begin(); } - const_iterator end() const noexcept { return sections_.end(); } - - - //--------------------------------------------------------------- - man_page& program_name(const string& n) { - progName_ = n; - return *this; - } - man_page& program_name(string&& n) { - progName_ = std::move(n); - return *this; - } - const string& program_name() const noexcept { - return progName_; - } - - - //--------------------------------------------------------------- - man_page& section_row_spacing(int rows) { - sectionSpc_ = rows > 0 ? rows : 0; - return *this; - } - int section_row_spacing() const noexcept { return sectionSpc_; } - - -private: - int sectionSpc_ = 1; - section_store sections_; - string progName_; -}; - - - -/*************************************************************************//** - * - * @brief generates man sections from command line parameters - * with sections "synopsis" and "options" - * - *****************************************************************************/ -inline man_page -make_man_page(const group& cli, - doc_string progname = "", - const doc_formatting& fmt = doc_formatting{}) -{ - man_page man; - man.append_section("SYNOPSIS", usage_lines(cli,progname,fmt).str()); - man.append_section("OPTIONS", documentation(cli,fmt).str()); - return man; -} - - - -/*************************************************************************//** - * - * @brief generates man page based on command line parameters - * - *****************************************************************************/ -template -OStream& -operator << (OStream& os, const man_page& man) -{ - bool first = true; - const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n'); - for(const auto& section : man) { - if(!section.content().empty()) { - if(first) first = false; else os << secSpc; - if(!section.title().empty()) os << section.title() << '\n'; - os << section.content(); - } - } - os << '\n'; - return os; -} - - - - - -/*************************************************************************//** - * - * @brief printing methods for debugging command line interfaces - * - *****************************************************************************/ -namespace debug { - - -/*************************************************************************//** - * - * @brief prints first flag or value label of a parameter - * - *****************************************************************************/ -inline doc_string doc_label(const parameter& p) -{ - if(!p.flags().empty()) return p.flags().front(); - if(!p.label().empty()) return p.label(); - return doc_string{""}; -} - -inline doc_string doc_label(const group&) -{ - return ""; -} - -inline doc_string doc_label(const pattern& p) -{ - return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param()); -} - - -/*************************************************************************//** - * - * @brief prints parsing result - * - *****************************************************************************/ -template -void print(OStream& os, const parsing_result& result) -{ - for(const auto& m : result) { - os << "#" << m.index() << " " << m.arg() << " -> "; - auto p = m.param(); - if(p) { - os << doc_label(*p) << " \t"; - if(m.repeat() > 0) { - os << (m.bad_repeat() ? "[bad repeat " : "[repeat ") - << m.repeat() << "]"; - } - if(m.blocked()) os << " [blocked]"; - if(m.conflict()) os << " [conflict]"; - os << '\n'; - } - else { - os << " [unmapped]\n"; - } - } - - for(const auto& m : result.missing()) { - auto p = m.param(); - if(p) { - os << doc_label(*p) << " \t"; - os << " [missing after " << m.after_index() << "]\n"; - } - } -} - - -/*************************************************************************//** - * - * @brief prints parameter label and some properties - * - *****************************************************************************/ -template -void print(OStream& os, const parameter& p) -{ - if(p.greedy()) os << '!'; - if(p.blocking()) os << '~'; - if(!p.required()) os << '['; - os << doc_label(p); - if(p.repeatable()) os << "..."; - if(!p.required()) os << "]"; -} - - -//------------------------------------------------------------------- -template -void print(OStream& os, const group& g, int level = 0); - - -/*************************************************************************//** - * - * @brief prints group or parameter; uses indentation - * - *****************************************************************************/ -template -void print(OStream& os, const pattern& param, int level = 0) -{ - if(param.is_group()) { - print(os, param.as_group(), level); - } - else { - os << doc_string(4*level, ' '); - print(os, param.as_param()); - } -} - - -/*************************************************************************//** - * - * @brief prints group and its contents; uses indentation - * - *****************************************************************************/ -template -void print(OStream& os, const group& g, int level) -{ - auto indent = doc_string(4*level, ' '); - os << indent; - if(g.blocking()) os << '~'; - if(g.joinable()) os << 'J'; - os << (g.exclusive() ? "(|\n" : "(\n"); - for(const auto& p : g) { - print(os, p, level+1); - } - os << '\n' << indent << (g.exclusive() ? "|)" : ")"); - if(g.repeatable()) os << "..."; - os << '\n'; -} - - -} // namespace debug -} //namespace clipp - -#endif - diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_ebpf.h b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_ebpf.h deleted file mode 100644 index accbcba88..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_ebpf.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 用于eBPF程序的宏 - -#ifndef STACK_ANALYZER_EBPF -#define STACK_ANALYZER_EBPF - -#include "sa_common.h" - -#define PF_KTHREAD 0x00200000 -#define ignoreKthread(task) \ - do { \ - int flags = BPF_CORE_READ(task, flags); \ - if(flags & PF_KTHREAD) \ - return 0; \ - } while(false) - - -/// @brief 创建一个指定名字的ebpf调用栈表 -/// @param 新栈表的名字 -#define BPF_STACK_TRACE(name) \ - struct { \ - __uint(type, BPF_MAP_TYPE_STACK_TRACE); \ - __uint(key_size, sizeof(__u32)); \ - __uint(value_size, MAX_STACKS * sizeof(__u64)); \ - __uint(max_entries, MAX_ENTRIES); \ - } name SEC(".maps") - -/// @brief 创建一个指定名字和键值类型的ebpf散列表 -/// @param name 新散列表的名字 -/// @param type1 键的类型 -/// @param type2 值的类型 -#define BPF_HASH(name, type1, type2) \ - struct { \ - __uint(type, BPF_MAP_TYPE_HASH); \ - __uint(key_size, sizeof(type1)); \ - __uint(value_size, sizeof(type2)); \ - __uint(max_entries, MAX_ENTRIES); \ - } name SEC(".maps") - -/// @brief 当前进程上下文内核态调用栈id -#define KERNEL_STACK bpf_get_stackid(ctx, &stack_trace, BPF_F_FAST_STACK_CMP) - -/// @brief 当前进程上下文用户态调用栈id -#define USER_STACK bpf_get_stackid(ctx, &stack_trace, BPF_F_FAST_STACK_CMP | BPF_F_USER_STACK) - -/** - * 用于在eBPF代码中声明通用的maps,其中 - * psid_count 存储 键值对,记录了id(由pid、ksid和usid(内核、用户栈id))及相应的值 - * stack_trace 存储 键值对,记录了栈id(ksid或usid)及相应的栈 - * pid_tgid 存储 键值对,记录pid以及对应的tgid - * pid_comm 存储 键值对,记录pid以及对应的命令名 - * type:指定count值的类型 - */ -#define DeclareCommonMaps(type) \ - BPF_HASH(psid_count, psid, type); \ - BPF_STACK_TRACE(stack_trace); \ - BPF_HASH(pid_tgid, u32, u32); \ - BPF_HASH(pid_comm, u32, comm); - -#define DeclareCommonVar() \ - bool u = false, k = false; \ - __u64 min = 0, max = 0; \ - int self_pid = 0; - -#endif \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_user.h b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_user.h deleted file mode 100644 index 790341cc9..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_user.h +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 用户态使用的宏 - -#ifndef STACK_ANALYZER_USER -#define STACK_ANALYZER_USER - -#include -#include -#include -#include -#include -#include -#include - -#include "sa_common.h" - -struct Scale { - const char *Type, *Unit; - int64_t Period; -}; - -/// @brief 获取epbf程序中指定表的文件描述符 -/// @param name 表的名字 -#define OPEN_MAP(name) bpf_map__fd(skel->maps.name) - -/// @brief 获取所有表的文件描述符 -#define OPEN_ALL_MAP() \ - value_fd = OPEN_MAP(psid_count); \ - tgid_fd = OPEN_MAP(pid_tgid); \ - comm_fd = OPEN_MAP(pid_comm); \ - trace_fd = OPEN_MAP(stack_trace); - -/// @brief 加载、初始化参数并打开指定类型的ebpf程序 -/// @param ... 一些ebpf程序全局变量初始化语句 -/// @note 失败会使上层函数返回-1 -#define StackProgLoadOpen(...) \ - skel = skel->open(NULL); \ - CHECK_ERR(!skel, "Fail to open BPF skeleton"); \ - skel->bss->min = min; \ - skel->bss->max = max; \ - skel->bss->u = ustack; \ - skel->bss->k = kstack; \ - skel->bss->self_pid = self_pid; \ - __VA_ARGS__; \ - err = skel->load(skel); \ - CHECK_ERR(err, "Fail to load BPF skeleton"); \ - OPEN_ALL_MAP() - -/// @brief 检查错误,若错误成立则打印带原因的错误信息并使上层函数返回-1 -/// @param cond 被检查的条件表达式 -/// @param info 要打印的错误信息 -#define CHECK_ERR(cond, ...) \ - if (cond) \ - { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, " [%s]\n", strerror(errno)); \ - return -1; \ - } - -#include -/// @brief 检查错误,若错误成立则打印带原因的错误信息并退出 -/// @param cond 被检查的条件表达式 -/// @param info 要打印的错误信息 -#define CHECK_ERR_EXIT(cond, ...) \ - if (cond) \ - { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, " [%s]\n", strerror(errno)); \ - exit(EXIT_FAILURE); \ - } - -#include -#include -/// @brief staring perf event -/// @param hw_event attribution of the perf event -/// @param pid the pid to track. 0 for the calling process. -1 for all processes. -/// @param cpu the cpu to track. -1 for all cpu -/// @param group_fd fd of event group leader -/// @param flags setting -/// @return fd of perf event -static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, - unsigned long flags) -{ - return syscall(SYS_perf_event_open, hw_event, pid, cpu, group_fd, flags); -} - -extern int parse_cpu_mask_file(const char *fcpu, bool **mask, int *mask_sz); - -/// @brief 向指定用户函数附加一个ebpf处理函数 -/// @param skel ebpf程序骨架 -/// @param sym_name 用户态函数名字面量,不加双引号 -/// @param prog_name ebpf处理函数,skel->progs中的成员名 -/// @param is_retprobe 布尔类型,是否附加到符号返回处 -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) -#define __ATTACH_UPROBE(skel, sym_name, prog_name, is_retprobe) \ - do \ - { \ - DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts, \ - .retprobe = is_retprobe); \ - skel->links.prog_name = bpf_program__attach_uprobe_opts( \ - skel->progs.prog_name, \ - pid, \ - object, \ - 1, \ - &uprobe_opts); \ - } while (false) -#else -#define __ATTACH_UPROBE(skel, sym_name, prog_name, is_retprobe) \ - do \ - { \ - LIBBPF_OPTS( \ - bpf_uprobe_opts, uprobe_opts, \ - .retprobe = is_retprobe, \ - .func_name = #sym_name); \ - skel->links.prog_name = bpf_program__attach_uprobe_opts( \ - skel->progs.prog_name, \ - pid, \ - object, \ - 0, \ - &uprobe_opts); \ - } while (false) -#endif - -/// @brief 检查处理函数是否已经被附加到函数上 -/// @param skel ebpf程序骨架 -/// @param prog_name ebpf处理函数 -/// @note 如果检查到没有被附加则使上层函数返回负的错误代码 -#define __CHECK_PROGRAM(skel, prog_name) \ - do \ - { \ - if (!skel->links.prog_name) \ - { \ - fprintf(stderr, "[%s] no program attached for" #prog_name "\n", strerror(errno)); \ - return -errno; \ - } \ - } while (false) - -/// @brief 向指定用户函数附加一个处理函数并检查是否连接成功 -/// @param skel ebpf程序骨架 -/// @param sym_name 要连接的用户函数 -/// @param prog_name ebpf处理函数 -/// @param is_retprobe 布尔类型,是否附加到函数返回处 -/// @note 如果检查到没有被附加则使上层函数返回负的错误代码 -#define __ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name, is_retprobe) \ - do \ - { \ - __ATTACH_UPROBE(skel, sym_name, prog_name, is_retprobe); \ - __CHECK_PROGRAM(skel, prog_name); \ - } while (false) - -/// @brief 向指定用户态函数入口处附加一个处理函数 -/// @param skel ebpf程序骨架 -/// @param sym_name 要附加的用户态函数名 -/// @param prog_name ebpf处理函数 -#define ATTACH_UPROBE(skel, sym_name, prog_name) __ATTACH_UPROBE(skel, sym_name, prog_name, false) - -/// @brief 向指定用户态函数返回处附加一个处理函数 -/// @param skel ebpf程序骨架 -/// @param sym_name 用户态函数名 -/// @param prog_name ebpf处理函数 -#define ATTACH_URETPROBE(skel, sym_name, prog_name) __ATTACH_UPROBE(skel, sym_name, prog_name, true) - -/// @brief 向指定用户态函数入口处附加一个处理函数并检查是否连接成功 -/// @param skel ebpf程序骨架 -/// @param sym_name 要跟踪的用户态函数名字面量,不带双引号 -/// @param prog_name ebpf处理函数,skel->progs中的成员 -/// @note 如果检查到没有被附加则使上层函数返回负的错误代码 -#define ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name) __ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name, false) - -/// @brief 向指定用户态函数返回处附加一个处理函数并检查是否连接成功 -/// @param skel ebpf程序骨架 -/// @param sym_name 要附加的用户态函数名,字面量,不带双引号 -/// @param prog_name ebpf处理函数,skel->progs中的成员 -/// @note 如果检查到没有被附加则使上层函数返回负的错误代码 -#define ATTACH_URETPROBE_CHECKED(skel, sym_name, prog_name) __ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name, true) - -#endif diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/symbol.h b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/symbol.h deleted file mode 100644 index 38237e0bb..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/symbol.h +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Linux内核诊断工具--用户态符号表解析 - * - * Copyright (C) 2020 Alibaba Ltd. - * - * License terms: GNU General Public License (GPL) version 3 - * - */ - -#ifndef __PERF_SYMBOL_H__ -#define __PERF_SYMBOL_H__ - -#include -#include -#include - -//#include - -#define INVALID_ADDR ((size_t)(-1)) -enum { - NATIVE_TYPE = 0, - JIT_TYPE = 1, - UNKNOWN = 2, -}; - -struct elf_file { - unsigned char elf_read_error; - size_t eh_frame_hdr_offset; - size_t fde_count; - size_t table_data; - std::string filename; - int type; - - // TODO get builid from elf header or build hash for elf - elf_file(const std::string &name) : filename(name), type(NATIVE_TYPE) { - elf_read_error = 0; - eh_frame_hdr_offset = 0; - fde_count = 0; - table_data = 0; - } - - elf_file() :type(NATIVE_TYPE) {} - - // TODO get builid from elf header or build hash for elf - void reset(const std::string &name) { - filename = name; - elf_read_error = 0; - eh_frame_hdr_offset = 0; - fde_count = 0; - table_data = 0; - } - - bool operator< (const elf_file &rhs) const { - return filename < rhs.filename; - } -}; - -struct symbol { - size_t start; - size_t end; - size_t ip; - std::string name; - - symbol() :start(0), end(0), ip(0) {} - symbol(size_t pc) :start(0), end(0), ip(pc) {} - - void reset(size_t va) { start = end = 0; ip = va; } - bool operator< (const symbol &sym) const { - return sym.ip < start; - } - - bool operator> (const symbol &sym) const { - return sym.ip > end; - } -}; - -struct vma { - size_t start; - size_t end; - size_t offset; - size_t pc; - int type; - std::string name; - struct { - unsigned char elf_read_error; - size_t eh_frame_hdr_offset; - size_t fde_count; - size_t table_data; - }; - - size_t map(size_t pc) { - return pc - start + offset; - } - - void set_type(int t) { type = t; } - - vma(size_t s, size_t e, size_t o, const std::string &n) - :start(s), end(e), offset(o), pc(0), type(NATIVE_TYPE), name(n) {} - - vma() : start(0), end(0), offset(0), pc(0), type(NATIVE_TYPE) {} - - vma(size_t addr) : start(0), end(0), offset(0), pc(addr), type(NATIVE_TYPE) {} - - bool operator<(const vma &vm) { - return vm.start < vm.pc; - } - - vma &operator=(const vma &vm) { - if (this == &vm) { - return *this; - } - start = vm.start; - end = vm.end; - offset = vm.offset; - name = vm.name; - return *this; - } -}; - -static inline bool operator==(const vma &lhs, const vma &rhs) { - return lhs.start == rhs.start && lhs.end == rhs.end && lhs.name == rhs.name; -} - -class symbol_parser { -private: - typedef std::map proc_vma; - - std::map > file_symbols; - std::map > java_symbols; - std::set kernel_symbols; - std::map machine_vma; - std::set java_procs; - std::map > symbols_cache; -public: - bool load_kernel(); - std::set& get_java_procs() { return java_procs; } - - bool find_kernel_symbol(symbol &sym); - - /// @brief 从elf file中查找sym中地址对应的符号名存入sym - /// @param sym 符号对象 - /// @param file 进程对应的elf file - /// @param pid 进程 - /// @param pid_ns 进程的命名空间? - /// @return 查找成功返回true,否则返回false - bool find_elf_symbol(symbol &sym, const elf_file &file, int pid, int pid_ns); - bool find_java_symbol(symbol &sym, int pid, int pid_ns); - - bool get_symbol_info(int pid, symbol &sym, elf_file &file); - - bool find_vma(pid_t pid, vma &vm); - vma* find_vma(pid_t pid, size_t pc); - void clear_symbol_info(int); - bool add_pid_maps(int pid, size_t start, size_t end, size_t offset, const char *name); - - bool find_symbol_in_cache(int tgid, unsigned long addr, std::string &symbol); - bool putin_symbol_cache(int tgid, unsigned long addr, std::string &symbol); - - void dump(void); -private: - bool load_pid_maps(int pid); - /// @brief 对elf_file对应的符号表进行缓存 -/// @param pid 未使用 -/// @param file elf file -/// @return 缓存成功返回true,否则返回false - bool load_elf(pid_t pid, const elf_file& file); - bool load_perf_map(int pid, int pid_ns); -public: - int java_only; - int user_symbol; -}; - -extern symbol_parser g_symbol_parser; - -std::string demangleCppSym(std::string symbol); -void clearSpace(std::string &sym); - -#endif diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/task.h b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/task.h deleted file mode 100644 index 3fac9ec1d..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/task.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2019 Aqua Security Software Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This product includes software developed by Aqua Security (https://aquasec.com). - -#ifndef __COMMON_TASK_H__ -#define __COMMON_TASK_H__ - -#include "vmlinux.h" - -#include - -#define statfunc static __always_inline - -struct pid_link { - struct hlist_node node; - struct pid *pid; -}; - -struct task_struct___older_v50 { - struct pid_link pids[PIDTYPE_MAX]; -}; - -statfunc u32 get_task_pid_vnr(struct task_struct *task) -{ - unsigned int level = 0; - struct pid *pid = NULL; - - if (bpf_core_type_exists(struct pid_link)) { - struct task_struct___older_v50 *t = (void *) task; - pid = BPF_CORE_READ(t, pids[PIDTYPE_PID].pid); - } else { - pid = BPF_CORE_READ(task, thread_pid); - } - - level = BPF_CORE_READ(pid, level); - - return BPF_CORE_READ(pid, numbers[level].nr); -} - -statfunc u32 get_task_ns_pid(struct task_struct *task) -{ - return get_task_pid_vnr(task); -} - -statfunc u32 get_task_ns_tgid(struct task_struct *task) -{ - struct task_struct *group_leader = BPF_CORE_READ(task, group_leader); - return get_task_pid_vnr(group_leader); -} - -statfunc u32 get_task_ns_ppid(struct task_struct *task) -{ - struct task_struct *real_parent = BPF_CORE_READ(task, real_parent); - return get_task_pid_vnr(real_parent); -} - -#endif \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src new file mode 120000 index 000000000..4bb05db0e --- /dev/null +++ b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src @@ -0,0 +1 @@ +../../../../../eBPF_Supermarket/Stack_Analyser/src/ \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/elf.cc b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/elf.cc deleted file mode 100644 index a31e4caa7..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/elf.cc +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Linux内核诊断工具--elf相关公共函数 - * - * Copyright (C) 2020 Alibaba Ltd. - * - * License terms: GNU General Public License (GPL) version 3 - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "stack_analyzer/include/elf.h" - -#define NOTE_ALIGN(n) (((n) + 3) & -4U) - -struct sym_section_ctx { - Elf_Data *syms; - Elf_Data *symstrs; - Elf_Data *rel_data; - int is_reloc; - int is_plt; - int sym_count; - int plt_rel_type; - unsigned long plt_offset; - unsigned long plt_entsize; -}; - -struct symbol_sections_ctx { - sym_section_ctx symtab; - sym_section_ctx symtab_in_dynsym; - sym_section_ctx dynsymtab; -}; - -struct section_info { - Elf_Scn *sec; - GElf_Shdr *hdr; -}; - -struct plt_ctx { - section_info dynsym; - section_info plt_rel; - section_info plt; -}; - -__attribute__((unused)) static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, - GElf_Shdr *shp, const char *name, - size_t *idx) { - Elf_Scn *sec = NULL; - size_t cnt = 1; - - /* Elf is corrupted/truncated, avoid calling elf_strptr. */ - if (!elf_rawdata(elf_getscn(elf, ep->e_shstrndx), NULL)) - return NULL; - - while ((sec = elf_nextscn(elf, sec)) != NULL) { - char *str; - - gelf_getshdr(sec, shp); - str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name); - - if (!strcmp(name, str)) { - if (idx) - *idx = cnt; - - break; - } - - ++cnt; - } - - return sec; -} - -__attribute__((unused)) static int elf_read_build_id(Elf *elf, char *bf, size_t size) { - int err = -1; - GElf_Ehdr ehdr; - GElf_Shdr shdr; - Elf_Data *data; - Elf_Scn *sec; - Elf_Kind ek; - char *ptr; - - if (size < BUILD_ID_SIZE) - goto out; - - ek = elf_kind(elf); - - if (ek != ELF_K_ELF) - goto out; - - if (gelf_getehdr(elf, &ehdr) == NULL) { - fprintf(stderr, "%s: cannot get elf header.\n", __func__); - goto out; - } - - /* - * Check following sections for notes: - * '.note.gnu.build-id' - * '.notes' - * '.note' (VDSO specific) - */ - do { - sec = elf_section_by_name(elf, &ehdr, &shdr, - ".note.gnu.build-id", NULL); - - if (sec) - break; - - sec = elf_section_by_name(elf, &ehdr, &shdr, - ".notes", NULL); - - if (sec) - break; - - sec = elf_section_by_name(elf, &ehdr, &shdr, - ".note", NULL); - - if (sec) - break; - - return err; - - } while (0); - - data = elf_getdata(sec, NULL); - - if (data == NULL) - goto out; - - ptr = (char *)data->d_buf; - - while ((intptr_t)ptr < (intptr_t)((char *)data->d_buf + data->d_size)) { - GElf_Nhdr *nhdr = (GElf_Nhdr *)ptr; - size_t namesz = NOTE_ALIGN(nhdr->n_namesz), - descsz = NOTE_ALIGN(nhdr->n_descsz); - const char *name; - - ptr += sizeof(*nhdr); - name = (const char *)ptr; - ptr += namesz; - - if (nhdr->n_type == NT_GNU_BUILD_ID && - nhdr->n_namesz == sizeof("GNU")) { - if (memcmp(name, "GNU", sizeof("GNU")) == 0) { - size_t sz = size < descsz ? size : descsz; - memcpy(bf, ptr, sz); - memset(bf + sz, 0, size - sz); - err = descsz; - break; - } - } - - ptr += descsz; - } - -out: - return err; -} - -extern int calc_sha1_1M(const char *filename, unsigned char *buf); - -int filename__read_build_id(int pid, const char *mnt_ns_name, const char *filename, char *bf, size_t size) { - int fd, err = -1; - struct stat sb; - - if (size < BUILD_ID_SIZE) - goto out; - - fd = open(filename, O_RDONLY); - - if (fd < 0) - goto out; - - if (fstat(fd, &sb) == 0) { - snprintf(bf, size, "%s[%lu]", filename, sb.st_size); - err = 0; - } - - close(fd); -out: - return err; -} - -static int is_function(const GElf_Sym *sym) -{ - return GELF_ST_TYPE(sym->st_info) == STT_FUNC && - sym->st_name != 0 && - sym->st_shndx != SHN_UNDEF; -} - -static int get_symbols_in_section(sym_section_ctx *sym, Elf *elf, Elf_Scn *sec, GElf_Shdr *shdr, int is_reloc) -{ - sym->syms = elf_getdata(sec, NULL); - if (!sym->syms) { - return -1; - } - - Elf_Scn *symstrs_sec = elf_getscn(elf, shdr->sh_link); - if (!sec) { - return -1; - } - - sym->symstrs = elf_getdata(symstrs_sec, NULL); - if (!sym->symstrs) { - return -1; - } - - sym->sym_count = shdr->sh_size / shdr->sh_entsize; - sym->is_plt = 0; - sym->is_reloc = is_reloc; - - return 0; -} - -static int get_plt_symbols_in_section(sym_section_ctx *sym, Elf *elf, plt_ctx *plt) -{ - sym->syms = elf_getdata(plt->dynsym.sec, NULL); - if (!sym->syms) { - return -1; - } - - sym->rel_data = elf_getdata(plt->plt_rel.sec, NULL); - if (!sym->rel_data) { - return -1; - } - - Elf_Scn *symstrs_sec = elf_getscn(elf, plt->dynsym.hdr->sh_link); - if (!symstrs_sec) { - return -1; - } - - sym->symstrs = elf_getdata(symstrs_sec, NULL); - if (!sym->symstrs) { - return -1; - } - - sym->is_plt = 1; - sym->plt_entsize = plt->plt.hdr->sh_type; - sym->plt_offset = plt->plt.hdr->sh_offset; - sym->sym_count = plt->plt_rel.hdr->sh_size / plt->plt_rel.hdr->sh_entsize; - sym->plt_rel_type = plt->plt_rel.hdr->sh_type; - - return 0; -} - -static void __get_plt_symbol(std::set &ss, symbol_sections_ctx *si, Elf *elf) -{ - symbol s; - GElf_Sym sym; - int symidx; - int index = 0; - const char *sym_name = NULL; - - s.end = 0; - s.start = 0; - - if (!si->dynsymtab.syms) { - return; - } - - while (index < si->dynsymtab.sym_count) { - if (si->dynsymtab.plt_rel_type == SHT_RELA) { - GElf_Rela pos_mem, *pos; - pos = gelf_getrela(si->dynsymtab.rel_data, index, &pos_mem); - symidx = GELF_R_SYM(pos->r_info); - } - else if (si->dynsymtab.plt_rel_type == SHT_REL) { - GElf_Rel pos_mem, *pos; - pos = gelf_getrel(si->dynsymtab.rel_data, index, &pos_mem); - symidx = GELF_R_SYM(pos->r_info); - } - else { - return; - } - index++; - si->dynsymtab.plt_offset += si->dynsymtab.plt_entsize; - gelf_getsym(si->dynsymtab.syms, symidx, &sym); - - sym_name = (const char *)si->dynsymtab.symstrs->d_buf + sym.st_name; - s.start = si->dynsymtab.plt_offset; - s.end = s.start + si->dynsymtab.plt_entsize; - s.ip = s.start; - s.name = sym_name; - ss.insert(s); - } -} - -static void __get_symbol_without_plt(std::set &ss, sym_section_ctx *tab, Elf *elf) -{ - GElf_Sym sym; - int index = 0; - const char *sym_name; - symbol s; - s.end = 0; - s.start = 0; - - while (index < tab->sym_count) { - gelf_getsym(tab->syms, index, &sym); - index++; - if (sym.st_shndx == SHN_ABS) { - continue; - } - if (!is_function(&sym)) { - continue; - } - sym_name = (const char *)tab->symstrs->d_buf + sym.st_name; - if (tab->is_reloc) { - Elf_Scn *sec = elf_getscn(elf, sym.st_shndx); - if (!sec) { - continue; - } - GElf_Shdr shdr; - gelf_getshdr(sec, &shdr); - sym.st_value -= shdr.sh_addr - shdr.sh_offset; - } - s.start = sym.st_value & 0xffffffff; - s.end = s.start + sym.st_size; - s.ip = s.start; - s.name = sym_name; - ss.insert(s); - } -} - -static void __get_symbol(std::set &ss, symbol_sections_ctx *si, Elf *elf) -{ - symbol s; - s.end = 0; - s.start = 0; - - if (!si->symtab.syms && !si->dynsymtab.syms) { - return; - } - - sym_section_ctx *tab = &si->symtab; - __get_symbol_without_plt(ss, tab, elf); - tab = &si->symtab_in_dynsym; - __get_symbol_without_plt(ss, tab, elf); -} - -static void get_all_symbols(std::set &ss, symbol_sections_ctx *si, Elf *elf) -{ - __get_symbol(ss, si, elf); - __get_plt_symbol(ss, si, elf); -} - -bool search_symbol(const std::set &ss, symbol &sym) -{ - std::set::const_iterator it = ss.find(sym); - - if (it != ss.end()) { - sym.end = it->end; - sym.start = it->start; - sym.name = it->name; - - return true; - } - - return false; -} - -bool get_symbol_from_elf(std::set &ss, const char *path) -{ - // static int first_init = 0; - - // if (!first_init) { - // first_init = true; - // init_global_env(); - // } - - int is_reloc = 0; - elf_version(EV_CURRENT); - int fd = open(path, O_RDONLY); - - Elf *elf = elf_begin(fd, ELF_C_READ, NULL); - if (elf == NULL) { - close(fd); - return false; - } - - Elf_Kind ek = elf_kind(elf); - if (ek != ELF_K_ELF) { - elf_end(elf); - close(fd); - return false; - } - GElf_Ehdr hdr; - if (gelf_getehdr(elf, &hdr) == NULL) { - elf_end(elf); - close(fd); - return false; - } - - if (hdr.e_type == ET_EXEC) { - is_reloc = 1; - } - - if (!elf_rawdata(elf_getscn(elf, hdr.e_shstrndx), NULL)) { - elf_end(elf); - close(fd); - return false; - } - - GElf_Shdr shdr; - GElf_Shdr symtab_shdr; - GElf_Shdr dynsym_shdr; - GElf_Shdr plt_shdr; - GElf_Shdr plt_rel_shdr; - memset(&shdr, 0, sizeof(shdr)); - memset(&symtab_shdr, 0, sizeof(symtab_shdr)); - memset(&dynsym_shdr, 0, sizeof(dynsym_shdr)); - memset(&plt_shdr, 0, sizeof(plt_shdr)); - memset(&plt_rel_shdr, 0, sizeof(plt_rel_shdr)); - - Elf_Scn *sec = NULL; - Elf_Scn *dynsym_sec = NULL; - Elf_Scn *symtab_sec = NULL; - Elf_Scn *plt_sec = NULL; - Elf_Scn *plt_rel_sec = NULL; - - while ((sec = elf_nextscn(elf, sec)) != NULL) { - char *str; - gelf_getshdr(sec, &shdr); - str = elf_strptr(elf, hdr.e_shstrndx, shdr.sh_name); - - if (str && strcmp(".symtab", str) == 0) { - symtab_sec = sec; - memcpy(&symtab_shdr, &shdr, sizeof(dynsym_shdr)); - } - if (str && strcmp(".dynsym", str) == 0) { - dynsym_sec = sec; - memcpy(&dynsym_shdr, &shdr, sizeof(dynsym_shdr)); - } - if (str && strcmp(".rela.plt", str) == 0) { - plt_rel_sec = sec; - memcpy(&plt_rel_shdr, &shdr, sizeof(plt_rel_shdr)); - } - if (str && strcmp(".plt", str) == 0) { - plt_sec = sec; - memcpy(&plt_shdr, &shdr, sizeof(plt_shdr)); - } - if (str && strcmp(".gnu.prelink_undo", str) == 0) { - is_reloc = 1; - } - } - - plt_ctx plt; - plt.dynsym.hdr = &dynsym_shdr; - plt.dynsym.sec = dynsym_sec; - plt.plt.hdr = &plt_shdr; - plt.plt.sec = plt_sec; - plt.plt_rel.hdr = &plt_rel_shdr; - plt.plt_rel.sec = plt_rel_sec; - - symbol_sections_ctx si; - memset(&si, 0, sizeof(si)); - if (symtab_sec) { - get_symbols_in_section(&si.symtab, elf, symtab_sec, &symtab_shdr, is_reloc); - } - if (dynsym_sec) { - get_symbols_in_section(&si.symtab_in_dynsym, elf, dynsym_sec, &dynsym_shdr, is_reloc); - } - if (dynsym_sec && plt_sec) { - get_plt_symbols_in_section(&si.dynsymtab, elf, &plt); - } - - get_all_symbols(ss, &si, elf); - elf_end(elf); - close(fd); - return true; -} - -struct symbol_cache_item { - int start; - int size; - char name[0]; -}; - -bool save_symbol_cache(std::set &ss, const char *path) -{ - char buf[2048]; - int len = 0; - bool status = true; - - int fd = open(path, O_RDONLY); - if (fd < 0) { - status = false; - return status; - } - int ret; - ret = read(fd, &len, 4); - if (ret <= 0) { - close(fd); - status = false; - return status; - } - ret = read(fd, buf, len); - if (ret <= 0) { - close(fd); - status = false; - return status; - } - - while (1) { - struct symbol_cache_item *sym; - symbol s; - ret = read(fd, &len, 4); - if (ret <= 0) { - status = false; - break; - } - ret = read(fd, buf, len); - if (ret < len) { - status = false; - break; - } - sym = (struct symbol_cache_item *)buf; - s.start = sym->start; - s.end = sym->start + sym->size; - s.ip = sym->start; - s.name = sym->name; - ss.insert(s); - } - close(fd); - return status; -} - -bool load_symbol_cache(std::set &ss, const char *path, const char *filename) -{ - int fd = open(path, O_RDWR | O_EXCL); - if (fd < 0) { - return false; - } - int len = strlen(filename); - int ret = write(fd, &len, 4); - if (ret < 0) { - close(fd); - return false; - } - ret = write(fd, filename, len); - if (ret < 0) { - close(fd); - return false; - } - - std::set::iterator it; - int v; - for (it = ss.begin(); it != ss.end(); ++it) { - v = it->start; - ret = write(fd, &v, 4); - v = it->end - it->start; - ret = write(fd, &v, 4); - ret = write(fd, it->name.c_str(), it->name.length()); - } - return true; -} diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/stack_analyzer.cc b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/stack_analyzer.cc deleted file mode 100644 index 7608fd839..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/stack_analyzer.cc +++ /dev/null @@ -1,934 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 用户态bpf的主程序代码,主要用于数据的显示和整理 - -#include -#include -#include -#include -#include -#include - -#include "stack_analyzer/include/symbol.h" -#include "stack_analyzer/include/clipp.h" - -extern "C" -{ -#include -#include -#include -#include -#include - -#include "stack_analyzer/include/sa_user.h" -#include "system_diagnosis/stack_analyzer/on_cpu_count.skel.h" -#include "system_diagnosis/stack_analyzer/off_cpu_count.skel.h" -#include "system_diagnosis/stack_analyzer/mem_count.skel.h" -#include "system_diagnosis/stack_analyzer/io_count.skel.h" -#include "system_diagnosis/stack_analyzer/pre_count.skel.h" -#include "system_diagnosis/stack_analyzer/stack_count.skel.h" -} - -void splitString(std::string symbol, const char split, std::vector &res) -{ - if (symbol == "") - return; - std::string strs = symbol + split; - size_t pos = strs.find(split); - while (pos != strs.npos) - { - std::string temp = strs.substr(0, pos); - res.push_back(temp); - strs = strs.substr(pos + 1, strs.size()); - pos = strs.find(split); - } -} - -std::string getLocalDateTime(void) -{ - auto t = time(NULL); - auto localTm = localtime(&t); - char buff[32]; - strftime(buff, 32, "%Y%m%d_%H_%M_%S", localTm); - return std::string(buff); -} - -// 模板用来统一调用多个类有同样但未被抽象的接口 -// 虚函数用来规范接口来被统一调用 - -class StackCollector -{ -private: - /// @brief count类,主要是为了重载比较运算,便于自动排序 - class CountItem - { - public: - uint32_t pid; - int32_t ksid, usid; - double val; - CountItem(int32_t p, int32_t k, int32_t u, double v) - { - - pid = p; - ksid = k; - usid = u; - val = v; - }; - - /// @brief count对象的大小取决于val的大小 - /// @param b 要比较的对象 - /// @return 小于b则为真,否则为假 - bool operator<(const CountItem b) - { - return val < b.val; - }; - }; - - /// @brief 从count_map中取出数据并按val值生成有序列表 - /// @return 一个列表指针 - std::vector *sortedCountList(void) - { - if (value_fd < 0) - { - return NULL; - } - auto keys = new psid[MAX_ENTRIES]; - auto vals = new char[MAX_ENTRIES * count_size]; - uint32_t count = MAX_ENTRIES; - psid next_key; - int err; - if (showDelta) - { - err = bpf_map_lookup_and_delete_batch(value_fd, NULL, &next_key, keys, vals, &count, NULL); - } - else - { - err = bpf_map_lookup_batch(value_fd, NULL, &next_key, keys, vals, &count, NULL); - } - if (err == EFAULT) - { - return NULL; - } - - auto D = new std::vector(); - for (uint32_t i = 0; i < count; i++) - { - CountItem d(keys[i].pid, keys[i].ksid, keys[i].usid, data_value(vals + count_size * i)); - D->insert(std::lower_bound(D->begin(), D->end(), d), d); - } - delete[] keys; - delete[] vals; - return D; - }; - -protected: - int value_fd = -1; // 栈计数表的文件描述符 - int tgid_fd = -1; // pid-tgid表的文件描述符 - int comm_fd = -1; // pid-进程名表的文件描述符 - int trace_fd = -1; // 栈id-栈轨迹表的文件描述符 - - // 计数变量类型默认为u32 - size_t count_size = sizeof(uint32_t); - - // 默认显示计数的变化情况,即每次输出数据后清除计数 - bool showDelta = true; - - /// @brief 将缓冲区的数据解析为特定值,默认解析为u32 - /// @param 无 - /// @return 解析出的值 - virtual double data_value(void *data) - { - return *(uint32_t *)data; - }; - - // 声明 -#define declareEBPF(eBPFName) struct eBPFName *skel = NULL; - -public: - Scale scale; - - int pid = -1; // 用于设置ebpf程序跟踪的pid - int cpu = -1; // 用于设置ebpf程序跟踪的cpu - int err = 0; // 用于保存错误代码 - - bool ustack = true; // 是否跟踪用户栈 - bool kstack = true; // 是否跟踪内核栈 - uint64_t min = 0; - uint64_t max = __UINT64_MAX__; // 设置采集指标最大值,最小值 - - bool clear = false; // 清除已输出的指标积累量 - int self_pid; - - StackCollector() - { - self_pid = getpid(); - }; - - /// @brief 负责ebpf程序的加载、参数设置和打开操作 - /// @param 无 - /// @return 成功则返回0,否则返回负数 - virtual int load(void) = 0; -#define defaultLoad \ - int load(void) override \ - { \ - StackProgLoadOpen(skel->bss->apid = pid); \ - return 0; \ - }; - - /// @brief 将ebpf程序挂载到跟踪点上 - /// @param 无 - /// @return 成功则返回0,否则返回负数 - virtual int attach(void) = 0; -#define defaultAttach \ - int attach(void) override \ - { \ - err = skel->attach(skel); \ - CHECK_ERR(err, "Failed to attach BPF skeleton"); \ - return 0; \ - }; - - /// @brief 断开ebpf的跟踪点和处理函数间的连接 - /// @param 无 - virtual void detach(void) = 0; -#define defaultDetach \ - void detach(void) override \ - { \ - if (skel) \ - { \ - skel->detach(skel); \ - } \ - }; - - /// @brief 卸载ebpf程序 - /// @param 无 - virtual void unload(void) = 0; -#define defaultUnload \ - void unload(void) override \ - { \ - if (skel) \ - { \ - skel->destroy(skel); \ - } \ - skel = NULL; \ - }; - - operator std::string() - { - std::ostringstream oss; - oss << "Type:" << scale.Type << " Unit:" << scale.Unit << " Period:" << scale.Period << '\n'; - oss << "time:" << getLocalDateTime() << '\n'; - std::map> traces; - oss << "counts:\n"; - { - auto D = sortedCountList(); - if (!D) - return oss.str(); - oss << "pid\tusid\tksid\tcount\n"; - uint64_t trace[MAX_STACKS], *p; - for (auto id : *D) - { - oss << id.pid << '\t' << id.usid << '\t' << id.ksid << '\t' << id.val - << '\n'; - if (id.usid > 0 && traces.find(id.usid) == traces.end()) - { - bpf_map_lookup_elem(trace_fd, &id.usid, trace); - for (p = trace + MAX_STACKS - 1; !*p; p--) - ; - for (; p >= trace; p--) - { - uint64_t &addr = *p; - symbol sym; - sym.reset(addr); - elf_file file; - if (g_symbol_parser.find_symbol_in_cache(id.pid, addr, sym.name)) - ; - else if (g_symbol_parser.get_symbol_info(id.pid, sym, file) && g_symbol_parser.find_elf_symbol(sym, file, id.pid, id.pid)) - { - if (sym.name[0] == '_' && sym.name[1] == 'Z') - // 代表是C++符号,则调用demangle解析 - { - sym.name = demangleCppSym(sym.name); - } - std::stringstream ss(""); - ss << "+0x" << std::hex << (addr - sym.start); - sym.name += ss.str(); - g_symbol_parser.putin_symbol_cache(id.pid, addr, sym.name); - } - else - { - std::stringstream ss(""); - ss << "0x" << std::hex << addr; - sym.name = ss.str(); - g_symbol_parser.putin_symbol_cache(id.pid, addr, sym.name); - } - clearSpace(sym.name); - traces[id.usid].push_back(sym.name); - } - } - if (id.ksid > 0 && traces.find(id.ksid) == traces.end()) - { - bpf_map_lookup_elem(trace_fd, &id.ksid, trace); - for (p = trace + MAX_STACKS - 1; !*p; p--) - ; - for (; p >= trace; p--) - { - uint64_t &addr = *p; - symbol sym; - sym.reset(addr); - if (g_symbol_parser.find_kernel_symbol(sym)) - ; - else - { - std::stringstream ss(""); - ss << "0x" << std::hex << addr; - sym.name = ss.str(); - g_symbol_parser.putin_symbol_cache(pid, addr, sym.name); - } - clearSpace(sym.name); - traces[id.ksid].push_back(sym.name); - } - } - } - delete D; - } - oss << "traces:\n"; - { - oss << "sid\ttrace\n"; - for (auto i : traces) - { - oss << i.first << "\t"; - for (auto s : i.second) - { - oss << s << ';'; - } - oss << "\n"; - } - } - oss << "groups:\n"; - { - if (tgid_fd < 0) - { - return oss.str(); - } - auto keys = new uint32_t[MAX_ENTRIES]; - auto vals = new uint32_t[MAX_ENTRIES]; - uint32_t count = MAX_ENTRIES; - uint32_t next_key; - int err = bpf_map_lookup_batch(tgid_fd, NULL, &next_key, keys, vals, - &count, NULL); - if (err == EFAULT) - { - return oss.str(); - } - oss << "pid\ttgid\n"; - for (uint32_t i = 0; i < count; i++) - { - oss << keys[i] << '\t' << vals[i] << '\n'; - } - delete[] keys; - delete[] vals; - } - oss << "commands:\n"; - { - if (comm_fd < 0) - { - return oss.str(); - } - auto keys = new uint32_t[MAX_ENTRIES]; - auto vals = new char[MAX_ENTRIES][16]; - uint32_t count = MAX_ENTRIES; - uint32_t next_key; - int err = bpf_map_lookup_batch(comm_fd, NULL, &next_key, keys, vals, - &count, NULL); - if (err == EFAULT) - { - return oss.str(); - } - oss << "pid\tcommand\n"; - for (uint32_t i = 0; i < count; i++) - { - oss << keys[i] << '\t' << vals[i] << '\n'; - } - delete[] keys; - delete[] vals; - } - oss << "OK\n"; - return oss.str(); - } -}; - -class OnCPUStackCollector : public StackCollector -{ -private: - declareEBPF(on_cpu_count_bpf); - const char *online_cpus_file = "/sys/devices/system/cpu/online"; - bool *online_mask = NULL; - int *pefds = NULL, num_cpus = 0, num_online_cpus = 0; - struct perf_event_attr attr = {0}; - struct bpf_link **links = NULL; - unsigned long long freq = 49; - -public: - OnCPUStackCollector() - { - setScale(freq); - err = parse_cpu_mask_file(online_cpus_file, &online_mask, &num_online_cpus); - CHECK_ERR_EXIT(err, "Fail to get online CPU numbers"); - num_cpus = libbpf_num_possible_cpus(); - CHECK_ERR_EXIT(num_cpus <= 0, "Fail to get the number of processors"); - }; - - void setScale(uint64_t freq) - { - this->freq = freq; - scale.Period = 1e9 / freq; - scale.Type = "OnCPUTime"; - scale.Unit = "nanoseconds"; - } - - int load(void) override - { - FILE *fp = popen("cat /proc/kallsyms | grep \" avenrun\"", "r"); - CHECK_ERR(!fp, "Failed to draw flame graph"); - unsigned long *load_a; - fscanf(fp, "%p", &load_a); - pclose(fp); - StackProgLoadOpen(skel->bss->load_a = load_a) return 0; - }; - - int attach(void) override - { - attr = { - .type = PERF_TYPE_SOFTWARE, // hardware event can't be used - .size = sizeof(attr), - .config = PERF_COUNT_SW_CPU_CLOCK, - .sample_freq = freq, - .inherit = 1, - .freq = 1, // use freq instead of period - }; - pefds = (int *)malloc(num_cpus * sizeof(int)); - for (int i = 0; i < num_cpus; i++) - { - pefds[i] = -1; - } - links = (struct bpf_link **)calloc(num_cpus, sizeof(struct bpf_link *)); - for (int cpu = 0; cpu < num_cpus; cpu++) - { - /* skip offline/not present CPUs */ - if (cpu >= num_online_cpus || !online_mask[cpu]) - { - continue; - } - /* Set up performance monitoring on a CPU/Core */ - int pefd = perf_event_open(&attr, pid, cpu, -1, 0); - CHECK_ERR(pefd < 0, "Fail to set up performance monitor on a CPU/Core"); - pefds[cpu] = pefd; - /* Attach a BPF program on a CPU */ - links[cpu] = bpf_program__attach_perf_event(skel->progs.do_stack, pefd); // 与内核bpf程序联系 - CHECK_ERR(!links[cpu], "Fail to attach bpf program"); - } - return 0; - } - - void detach(void) override - { - if (links) - { - for (int cpu = 0; cpu < num_cpus; cpu++) - { - - bpf_link__destroy(links[cpu]); - } - free(links); - links = NULL; - } - if (pefds) - { - for (int i = 0; i < num_cpus; i++) - { - if (pefds[i] >= 0) - { - close(pefds[i]); - } - } - free(pefds); - pefds = NULL; - } - } - - defaultUnload; -}; - -class OffCPUStackCollector : public StackCollector -{ -private: - declareEBPF(off_cpu_count_bpf); - -protected: - defaultLoad; - defaultAttach; - defaultDetach; - defaultUnload; - -public: - OffCPUStackCollector() - { - scale.Period = 1 << 20; - scale.Type = "OffCPUTime"; - scale.Unit = "milliseconds"; - }; -}; - -class MemoryStackCollector : public StackCollector -{ -private: - declareEBPF(mem_count_bpf); - -protected: - double data_value(void *d) override - { - return *(uint64_t *)d; - } - -public: - char *object = (char *)"libc.so.6"; - - MemoryStackCollector() - { - count_size = sizeof(uint64_t); - kstack = false; - showDelta = false; - scale.Period = 1; - scale.Type = "LeakedMomery"; - scale.Unit = "bytes"; - }; - - int load(void) override - { - StackProgLoadOpen(); - return 0; - }; - - int attach(void) override - { - ATTACH_UPROBE_CHECKED(skel, malloc, malloc_enter); - ATTACH_URETPROBE_CHECKED(skel, malloc, malloc_exit); - ATTACH_UPROBE_CHECKED(skel, calloc, calloc_enter); - ATTACH_URETPROBE_CHECKED(skel, calloc, calloc_exit); - ATTACH_UPROBE_CHECKED(skel, realloc, realloc_enter); - ATTACH_URETPROBE_CHECKED(skel, realloc, realloc_exit); - ATTACH_UPROBE_CHECKED(skel, free, free_enter); - - ATTACH_UPROBE_CHECKED(skel, mmap, mmap_enter); - ATTACH_URETPROBE_CHECKED(skel, mmap, mmap_exit); - ATTACH_UPROBE_CHECKED(skel, munmap, munmap_enter); - - err = skel->attach(skel); - CHECK_ERR(err, "Failed to attach BPF skeleton"); - return 0; - }; - - void detach(void) override - { - skel->detach(skel); -#define destoryBPFLinkIfExist(name) \ - if (skel->links.name) \ - { \ - bpf_link__destroy(skel->links.name); \ - } - destoryBPFLinkIfExist(malloc_enter); - destoryBPFLinkIfExist(malloc_exit); - destoryBPFLinkIfExist(calloc_enter); - destoryBPFLinkIfExist(calloc_exit); - destoryBPFLinkIfExist(realloc_enter); - destoryBPFLinkIfExist(realloc_exit); - destoryBPFLinkIfExist(free_enter); - destoryBPFLinkIfExist(mmap_enter); - destoryBPFLinkIfExist(mmap_exit); - destoryBPFLinkIfExist(munmap_enter); - }; - - defaultUnload; -}; - -class IOStackCollector : public StackCollector -{ -private: - declareEBPF(io_count_bpf); - -protected: - double data_value(void *data) override - { - io_tuple *p = (io_tuple *)data; - switch (DataType) - { - case AVE: - return 1. * p->size / p->count; - case SIZE: - return p->size; - case COUNT: - return p->count; - default: - return 0; - } - }; - -public: - typedef enum - { - COUNT, - SIZE, - AVE - } io_mod; - - io_mod DataType = io_mod::COUNT; - - void setScale(io_mod mod) - { - DataType = mod; - static const char *Types[] = {"IOCount", "IOSize", "AverageIOSize"}; - static const char *Units[] = {"counts", "bytes", "bytes"}; - scale.Type = Types[mod]; - scale.Unit = Units[mod]; - scale.Period = 1; - }; - - IOStackCollector() - { - count_size = sizeof(io_tuple); - setScale(DataType); - }; - - defaultLoad; - defaultAttach; - defaultDetach; - defaultUnload; -}; - -class ReadaheadStackCollector : public StackCollector -{ -private: - declareEBPF(pre_count_bpf); - -protected: - double data_value(void *data) override - { - ra_tuple *p = (ra_tuple *)data; - return p->expect - p->truth; - }; - -public: - defaultLoad; - defaultAttach; - defaultDetach; - defaultUnload; - - ReadaheadStackCollector() - { - count_size = sizeof(ra_tuple); - showDelta = false; - scale = { - .Type = "UnusedReadaheadPages", - .Unit = "pages", - .Period = 1, - }; - }; -}; - -class StackCountStackCollector : public StackCollector -{ -private: - declareEBPF(stack_count_bpf); - -public: - std::string probe = ""; // 保存命令行的输入 - std::string tp_class = ""; - std::vector strList; - typedef enum - { - KPROBE, - TRACEPOINT, - USTD_TP, - UPROBE - } stack_mod; - - stack_mod ProbeType = stack_mod::KPROBE; - - StackCountStackCollector() - { - scale = { - .Type = "StackCounts", - .Unit = "Counts", - .Period = 1, - }; - }; - - void setProbe(std::string probe) - { - splitString(probe, ':', strList); - if (strList.size() == 1) - { - // probe a kernel function - this->probe = probe; - } - else if (strList.size() == 3) - { - if (strList[0] == "p" && strList[1] == "") - { - // probe a kernel function - this->probe = strList[2]; - } - else if (strList[0] == "t") - { - // probe a kernel tracepoint - this->tp_class = strList[1]; - this->probe = strList[2]; - ProbeType = stack_mod::TRACEPOINT; - } - else if (strList[0] == "p" && strList[1] != "") - { - // probe a user-space function in the library 'lib' - ProbeType = stack_mod::UPROBE; - } - else if (strList[0] == "u") - { - // probe a USDT tracepoint - ProbeType = stack_mod::USTD_TP; - } - else - { - printf("Type must be 'p', 't', or 'u'"); - } - } - else if (strList.size() == 2) - { - // probe a user-space function in the library 'lib' - ProbeType = stack_mod::UPROBE; - } - else - { - printf("Too many args"); - } - scale.Type = (probe + scale.Type).c_str(); - } - - defaultLoad; - int attach(void) override - { - if (ProbeType == KPROBE) - { - skel->links.handle = - bpf_program__attach_kprobe(skel->progs.handle, false, - probe.c_str()); - CHECK_ERR(!skel->links.handle, "Fail to attach kprobe"); - } - else if (ProbeType == TRACEPOINT) - { - skel->links.handle_tp = - bpf_program__attach_tracepoint(skel->progs.handle_tp, tp_class.c_str(), probe.c_str()); - CHECK_ERR(!skel->links.handle_tp, "Fail to attach tracepoint"); - } - return 0; - }; - defaultDetach; - defaultUnload; -}; - -namespace MainConfig -{ -int run_time = __INT_MAX__; // 运行时间 -unsigned delay = 5; // 设置输出间隔 -std::string command = ""; -int32_t target_pid = -1; -} -std::vector StackCollectorList; -void endCollect(void) -{ - signal(SIGINT, SIG_IGN); - for (auto Item : StackCollectorList) - { - if (MainConfig::run_time > 0) - { - std::cout << std::string(*Item) << std::endl; - } - Item->detach(); - Item->unload(); - } - if (MainConfig::command.length()) - { - kill(MainConfig::target_pid, SIGTERM); - } -} - -uint64_t IntTmp; -std::string StrTmp; -int main(int argc, char *argv[]) -{ - auto MainOption = (( - ((clipp::option("-p", "--pid") & clipp::value("pid of sampled process, default -1 for all", MainConfig::target_pid)) % "set pid of process to monitor") | - ((clipp::option("-c", "--command") & clipp::value("to be sampled command to run, default none", MainConfig::command)) % "set command for monitoring the whole life")), - (clipp::option("-d", "--delay") & clipp::value("delay time(seconds) to output, default 5", MainConfig::delay)) % "set the interval to output", - (clipp::option("-t", "--timeout") & clipp::value("run time, default nearly infinite", MainConfig::run_time)) % "set the total simpling time"); - - auto SubOption = (clipp::option("-U", "--user-stack-only").call([] - { StackCollectorList.back()->kstack = false; }) % - "only sample user stacks", - clipp::option("-K", "--kernel-stack-only").call([] - { StackCollectorList.back()->ustack = false; }) % - "only sample kernel stacks", - (clipp::option("-m", "--max-value") & clipp::value("max threshold of sampled value", IntTmp).call([] - { StackCollectorList.back()->max = IntTmp; })) % - "set the max threshold of sampled value", - (clipp::option("-n", "--min-value") & clipp::value("min threshold of sampled value", IntTmp).call([] - { StackCollectorList.back()->min = IntTmp; })) % - "set the min threshold of sampled value"); - - auto OnCpuOption = (clipp::option("on-cpu").call([] - { StackCollectorList.push_back(new OnCPUStackCollector()); }) % - "sample the call stacks of on-cpu processes") & - (clipp::option("-F", "--frequency") & clipp::value("sampling frequency", IntTmp).call([] - { static_cast(StackCollectorList.back())->setScale(IntTmp); }) % - "sampling at a set frequency", - SubOption); - - auto OffCpuOption = clipp::option("off-cpu").call([] - { StackCollectorList.push_back(new OffCPUStackCollector()); }) % - "sample the call stacks of off-cpu processes" & - SubOption; - - auto MemoryOption = clipp::option("mem").call([] - { StackCollectorList.push_back(new MemoryStackCollector()); }) % - "sample the memory usage of call stacks" & - SubOption; - - auto IOOption = clipp::option("io").call([] - { StackCollectorList.push_back(new IOStackCollector()); }) % - "sample the IO data volume of call stacks" & - ((clipp::option("--mod") & (clipp::option("count").call([] - { static_cast(StackCollectorList.back())->setScale(IOStackCollector::io_mod::COUNT); }) % - "Counting the number of I/O operations" | - clipp::option("ave").call([] - { static_cast(StackCollectorList.back())->setScale(IOStackCollector::io_mod::AVE); }) % - "Counting the ave of I/O operations" | - clipp::option("size").call([] - { static_cast(StackCollectorList.back())->setScale(IOStackCollector::io_mod::SIZE); }) % - "Counting the size of I/O operations")) % - "set the statistic mod", - SubOption); - - auto ReadaheadOption = clipp::option("ra").call([] - { StackCollectorList.push_back(new ReadaheadStackCollector()); }) % - "sample the readahead hit rate of call stacks" & - SubOption; - - auto StackCountOption = clipp::option("stackcount").call([] - { StackCollectorList.push_back(new StackCountStackCollector()); }) % - "sample the counts of calling stacks" & - (clipp::option("-S", "--String") & clipp::value("probe String", StrTmp).call([] - { static_cast(StackCollectorList.back())->setProbe(StrTmp); }) % - "sampling at a set probe string", - SubOption); - - auto cli = (MainOption, - clipp::option("-v", "--version").call([] - { std::cout << "verion 2.0\n\n"; }) % - "show version", - OnCpuOption, - OffCpuOption, - MemoryOption, - IOOption, - ReadaheadOption, - StackCountOption) % - "statistic call trace relate with some metrics"; - - if (!clipp::parse(argc, argv, cli)) - { - std::cout << clipp::make_man_page(cli, argv[0]) << '\n'; - return 0; - } - - uint64_t eventbuff = 1; - int child_exec_event_fd = eventfd(0, EFD_CLOEXEC); - CHECK_ERR(child_exec_event_fd < 0, "failed to create event fd"); - if (MainConfig::command.length()) - { - MainConfig::target_pid = fork(); - switch (MainConfig::target_pid) - { - case -1: - { - std::cout << "command create failed." << std::endl; - return -1; - } - case 0: - { - const auto bytes = read(child_exec_event_fd, &eventbuff, sizeof(eventbuff)); - CHECK_ERR(bytes < 0, "failed to read from fd %ld", bytes) - else CHECK_ERR(bytes != sizeof(eventbuff), "read unexpected size %ld", bytes); - printf("child exec %s\n", MainConfig::command.c_str()); - CHECK_ERR_EXIT(execl("/bin/bash", "bash", "-c", MainConfig::command.c_str(), NULL), "failed to execute child command"); - break; - } - default: - { - printf("create child %d\n", MainConfig::target_pid); - break; - } - } - } - - for (auto Item = StackCollectorList.begin(); Item != StackCollectorList.end();) - { - (*Item)->pid = MainConfig::target_pid; - if ((*Item)->load()) - { - goto err; - } - if ((*Item)->attach()) - { - goto err; - } - Item++; - continue; - err: - fprintf(stderr, "%s eBPF prog err\n", (*Item)->scale.Type); - (*Item)->detach(); - (*Item)->unload(); - Item = StackCollectorList.erase(Item); - } - - if (MainConfig::command.length()) - { - printf("wake up child\n"); - write(child_exec_event_fd, &eventbuff, sizeof(eventbuff)); - } - - // printf("display mode: %d\n", MainConfig::d_mode); - - for (; MainConfig::run_time > 0 && (MainConfig::target_pid < 0 || !kill(MainConfig::target_pid, 0)); MainConfig::run_time -= MainConfig::delay) - { - sleep(MainConfig::delay); - for (auto Item : StackCollectorList) - { - Item->detach(); - std::cout << std::string(*Item); - Item->attach(); - } - } - - atexit(endCollect); -} \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/symbol.cc b/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/symbol.cc deleted file mode 100644 index 44d65b7af..000000000 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/src/symbol.cc +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Linux内核诊断工具--用户态符号表解析 - * - * Copyright (C) 2020 Alibaba Ltd. - * - * License terms: GNU General Public License (GPL) version 3 - * - */ - -#include -#include -#include -#include - -#include "stack_analyzer/include/symbol.h" -#include "stack_analyzer/include/elf.h" - -void restore_global_env(); -int attach_ns_env(int pid); - -symbol_parser g_symbol_parser; -const bool debug_mode = false; - -bool symbol_parser::add_pid_maps(int pid, size_t start, size_t end, size_t offset, const char *name) -{ - std::map::iterator it; - it = machine_vma.find(pid); - if (it == machine_vma.end()) { - proc_vma proc; - machine_vma.insert(make_pair(pid, proc)); - it = machine_vma.find(pid); - if (it == machine_vma.end()) { - return false; - } - } - - vma vm(start, end, offset, name); - it->second.insert(std::make_pair(vm.start, std::move(vm))); - - return true; -} - -bool symbol_parser::load_pid_maps(int pid) -{ - std::map::iterator it; - it = machine_vma.find(pid); - if (it != machine_vma.end()) { - return true; - } - - proc_vma proc; - char fn[256]; - sprintf(fn, "/proc/%d/maps", pid); - FILE *fp = fopen(fn, "r"); - if (!fp) { - return false; - } - - char buf[4096]; - char exename[4096]; - size_t start, end, offset; - while (fgets(buf, sizeof(buf), fp) != NULL) { - start = end = offset = 0; - exename[0] = '\0'; - sscanf(buf, "%lx-%lx %*s %lx %*x:%*x %*u %s %*s\n", &start, &end, &offset, exename); - if (exename[0] == '\0') { - strcpy(exename, "[anon]"); - } - vma vm(start, end, offset, exename); - proc.insert(std::make_pair(vm.start, std::move(vm))); - } - - fclose(fp); - - machine_vma.insert(std::make_pair(pid, std::move(proc))); - it = machine_vma.find(pid); - if (it == machine_vma.end()) { - return false; - } - - return true; -} - -bool symbol_parser::load_perf_map(int pid, int pid_ns) -{ -#if 0 - if (pid != pid_ns) { - if (attach_ns_env(pid) < 0) { - return false; - } - } -#endif - char perfmapfile[64]; - snprintf(perfmapfile, sizeof(perfmapfile), "/tmp/perf-%d.map", pid); - FILE *fp = fopen(perfmapfile, "r"); - if (fp == NULL) { - if (debug_mode) { - printf("cannot read perf map %d\n", pid); - } - return false; - } - char line[256]; - char *buf; - long start; - int size; - char name[256]; - std::set syms; - symbol sym; - while ((buf = fgets(line, sizeof(line), fp)) != NULL) { - sscanf(buf, "%lx %x %s\n", &start, &size, name); - sym.start = start; - sym.end = sym.start + size; - sym.ip = sym.start; - sym.name = name; - syms.insert(sym); - } - java_symbols.insert(make_pair(pid, std::move(syms))); -#if 0 - if (pid != pid_ns) { - restore_global_env(); - } -#endif - return true; -} - -bool symbol_parser::find_java_symbol(symbol &sym, int pid, int pid_ns) -{ - std::set ss; - std::map >::iterator it; - //bool load_now = false; - it = java_symbols.find(pid); - if (it == java_symbols.end()) { - if (!load_perf_map(pid, pid_ns)) { - return false; - } - //load_now = true; - it = java_symbols.find(pid); - return search_symbol(it->second, sym); - } else { - return search_symbol(it->second, sym); - } - return true; - - //bool ret = search_symbol(syms, sym); -#if 0 - if (!ret && !load_now) { - java_symbols.erase(pid); - if (!load_perf_map(pid)) { - return false; - } - syms = java_symbols.find(pid)->second; - return search_symbol(syms, sym); - } -#endif - //return ret; -} - -static bool load_kernel_symbol_list(std::vector &sym_list) -{ - FILE *fp = fopen("/proc/kallsyms", "r"); - if (!fp) { - return -1; - } - - char buf[256]; - char type; - int len; - while (fgets(buf, sizeof(buf), fp) != NULL) { - sscanf(buf, "%*p %c %*s\n", &type); - if ((type | 0x20) != 't') { - continue; - } - len = strlen(buf); - if (buf[len-1] == '\n') { - buf[len-1] = '\0'; - } - sym_list.push_back(buf); - } - fclose(fp); - - std::sort(sym_list.begin(), sym_list.end()); - return true; -} - -bool is_space(int ch) { - return std::isspace(ch); -} - -static inline void rtrim(std::string &s) -{ - s.erase(std::find_if(s.rbegin(), s.rend(), is_space).base(), s.end()); -} - -static bool get_next_kernel_symbol( - std::set &syms, - std::vector &sym_list, - std::vector::iterator cursor) -{ - if (cursor == sym_list.end()) { - return false; - } - symbol sym; - size_t start, end; - sscanf(cursor->c_str(), "%p %*c %*s\n", (void **)&start); - sym.name = cursor->c_str() + 19; - // rtrim(sym.name); -// #if 0 - // if (sym.name[sym.name.size()-1] == ' ') { - // // sym.name[sym.name.size()-1] = '\0'; - // sym.name.pop_back(); - // } -// #endif - cursor++; - if (cursor != sym_list.end()) { - sscanf(cursor->c_str(), "%p %*c %*s\n", (void **)&end); - } - else { - end = INVALID_ADDR; - } - sym.start = start; - sym.end = end; - sym.ip = start; - - syms.insert(sym); - return true; -} - -bool symbol_parser::load_kernel() -{ - if (kernel_symbols.size() != 0) { - return true; - } - - std::vector sym_list; - if (!load_kernel_symbol_list(sym_list)) { - exit(0); - return false; - } - - std::vector::iterator cursor = sym_list.begin(); - while (get_next_kernel_symbol(kernel_symbols, sym_list, cursor)) { - cursor++; - } - return true; -} - -bool symbol_parser::load_elf(pid_t pid, const elf_file &file) -{ - std::map >::iterator it; - it = file_symbols.find(file); - std::set tmp; - std::set &syms = tmp; - if (it != file_symbols.end()) { - return true; - } - if (get_symbol_from_elf(syms, file.filename.c_str())) { - file_symbols.insert(make_pair(file, std::move(syms))); - return true; - } - return false; -} - -bool symbol_parser::find_kernel_symbol(symbol &sym) -{ - load_kernel(); - sym.end = sym.start = 0; - std::set::iterator it = kernel_symbols.find(sym); - if (it != kernel_symbols.end()) { - sym.end = it->end; - sym.start = it->start; - sym.name = it->name; - return true; - } - return false; -} - -bool symbol_parser::find_symbol_in_cache(int tgid, unsigned long addr, std::string &symbol) -{ - std::map >::const_iterator it_pid = - symbols_cache.find(tgid); - - if (it_pid != symbols_cache.end()) { - std::map map = symbols_cache[tgid]; - std::map::const_iterator it_symbol = - map.find(addr); - - if (it_symbol != map.end()) { - symbol = map[addr]; - - return true; - } - } - - return false; -} - -bool symbol_parser::putin_symbol_cache(int tgid, unsigned long addr, std::string &symbol) -{ - std::map >::const_iterator it_pid = - symbols_cache.find(tgid); - - if (it_pid == symbols_cache.end()) { - std::map map; - symbols_cache.insert(std::make_pair(tgid, map)); - } - - std::map &map = symbols_cache[tgid]; - std::map::const_iterator it_symbol = - map.find(addr); - - if (it_symbol == map.end()) { - map[addr] = symbol; - return true; - } - - return false; -} - -bool symbol_parser::get_symbol_info(int pid, symbol &sym, elf_file &file) -{ - std::map::iterator proc_vma_info; - - if (java_only) { - file.type = UNKNOWN; - return true; - } - - proc_vma_info = machine_vma.find(pid); - if (proc_vma_info == machine_vma.end()) { - if (!load_pid_maps(pid)) { - if (debug_mode) { - printf("load pid maps failed\n"); - } - return false; - } - } - - vma area(sym.ip); - if (!find_vma(pid, area)) { - if (debug_mode) { - printf("find vma failed\n"); - } - return false; - } - if (area.name == "[anon]") { - file.type = JIT_TYPE; - } - - file.reset(area.name); - if (file.type != JIT_TYPE) { - sym.reset(area.map(sym.ip)); - } - - return true; -} - -bool symbol_parser::find_elf_symbol(symbol &sym, const elf_file &file, int pid, int pid_ns) -{ - if (java_only) { - return find_java_symbol(sym, pid, pid_ns); - } - - if (file.type == JIT_TYPE) { - return find_java_symbol(sym, pid, pid_ns); - } - - std::map >::iterator it; - it = file_symbols.find(file); - std::set ss; - if (it == file_symbols.end()) { - if (!load_elf(pid, file)) { - return false; - } - it = file_symbols.find(file); - } - return search_symbol(it->second, sym); -} - -vma* symbol_parser::find_vma(pid_t pid, size_t pc) -{ - std::map::iterator it; - - it = machine_vma.find(pid); - if (it == machine_vma.end()) { - return NULL; - } - - proc_vma::iterator vma_iter = it->second.upper_bound(pc); - if (vma_iter == it->second.end() || vma_iter->second.end < pc) { - return NULL; - } - - if (vma_iter != it->second.begin()) { - --vma_iter; - } - - return &vma_iter->second; -} - -bool symbol_parser::find_vma(pid_t pid, vma &vm) -{ - std::map::iterator proc_vma_map; - - proc_vma_map = machine_vma.find(pid); - if (proc_vma_map == machine_vma.end()) { - return false; - } - - proc_vma::const_iterator vma_iter = proc_vma_map->second.upper_bound(vm.pc); - if (vma_iter == proc_vma_map->second.end()) { - return false; - } - if (vma_iter->second.end < vm.pc) { - return false; - } - - if (vma_iter != proc_vma_map->second.begin()) { - --vma_iter; - } - - vm.start = vma_iter->second.start; - vm.end = vma_iter->second.end; - vm.name = vma_iter->second.name; - vm.offset = vma_iter->second.offset; - - return true; -} - -class pid_cmdline { - private: - std::map cmdlines; - public: - void clear(void); - std::string & get_pid_cmdline(int pid); -}; - -void pid_cmdline::clear(void) -{ - cmdlines.clear(); -} - -void clear_symbol_info(class pid_cmdline &pid_cmdline, std::set &procs, int dist) -{ - pid_cmdline.clear(); - procs.clear(); - g_symbol_parser.clear_symbol_info(dist); -} - -void symbol_parser::clear_symbol_info(int dist) -{ - machine_vma.clear(); - java_symbols.clear(); - if (dist) { - kernel_symbols.clear(); - file_symbols.clear(); - } -} - -void symbol_parser::dump(void) -{ - int count1, count2, count3; - - if (!debug_mode) - return; - - { - count1 = 0; - count2 = 0; - count3 = 0; - std::map >::iterator iter = file_symbols.begin(); - for(; iter != file_symbols.end(); ++iter) { - std::set& map = iter->second; - const elf_file& file = iter->first; - - count1++; - printf("xby-debug, file_symbols: %s, %lu\n", - file.filename.c_str(), - map.size()); - - count2 += map.size(); - std::set::iterator it = map.begin(); - for(; it != map.end(); ++it) { - count3 += it->name.length(); - } - } - printf("xby-debug, file_symbols: %d, %d, %d\n", count1, count2, count3); - printf("xby-debug, sizeof(symbol): %ld\n", sizeof(symbol)); - } - - { - count1 = 0; - count2 = 0; - std::map >::iterator iter = java_symbols.begin(); - for(; iter != java_symbols.end(); ++iter) { - count1++; - std::set& map = iter->second; - count2 += map.size(); - } - printf("xby-debug, java_symbols: %d, %d\n", count1, count2); - } - - { - printf("xby-debug, kernel_symbols: %lu\n", kernel_symbols.size()); - } - - { - count1 = 0; - count2 = 0; - std::map::iterator iter = machine_vma.begin(); - for(; iter != machine_vma.end(); ++iter) { - count1++; - proc_vma map = iter->second; - count2 += map.size(); - } - printf("xby-debug, machine_vma: %d, %d\n", count1, count2); - } - - { - count1 = 0; - count2 = 0; - std::map >::iterator iter = symbols_cache.begin(); - for(; iter != symbols_cache.end(); ++iter) { - count1++; - std::map& map = iter->second; - count2 += map.size(); - } - printf("xby-debug, symbols_cache: %d, %d\n", count1, count2); - } -} - -std::string demangleCppSym(std::string symbol) -{ - size_t size = 0; - int status = 0; - char *demangled = abi::__cxa_demangle(symbol.c_str(), NULL, &size, &status); - - if (status == 0 && demangled != NULL) - { - std::string FuncName(demangled); - free(demangled); - return FuncName; - } - else - { - // 解码失败,返回原始符号 - return symbol; - } -} - -void clearSpace(std::string &sym) -{ - for (auto i = sym.begin(); i != sym.end();) - { - if (isblank(*i)) - { - sym.erase(i); - } - else - { - i++; - } - } -} \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/Makefile b/eBPF_Supermarket/Stack_Analyser/Makefile index 44a453a83..d4981ce74 100644 --- a/eBPF_Supermarket/Stack_Analyser/Makefile +++ b/eBPF_Supermarket/Stack_Analyser/Makefile @@ -17,6 +17,7 @@ # Makefile OUTPUT := .output +BPF_SKEL := bpf_skel CLANG ?= clang LIBBPF_SRC := $(abspath libbpf-bootstrap/libbpf/src) BPFTOOL_SRC := $(abspath libbpf-bootstrap/bpftool/src) @@ -34,13 +35,13 @@ VMLINUX := libbpf-bootstrap/vmlinux/$(ARCH)/vmlinux.h # Use our own libbpf API headers and Linux UAPI headers distributed with # libbpf to avoid dependency on system-wide headers, which could be missing or # outdated -INCLUDES := -I./include -I$(OUTPUT) -I./libbpf-bootstrap/libbpf/include/uapi -I$(dir $(VMLINUX)) +INCLUDES := -I./include -I$(OUTPUT) -I$(BPF_SKEL) -I./libbpf-bootstrap/libbpf/include/uapi -I$(dir $(VMLINUX)) CFLAGS := -g -Wall ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) -BPF = $(patsubst src/bpf/%.bpf.c, %, ${wildcard src/bpf/*.bpf.c}) +BIN = $(patsubst src/%.cpp, %, ${wildcard src/*.cpp}) BPF_WAPPER = $(patsubst src/bpf/%.cpp, %, ${wildcard src/bpf/*.cpp}) -THIRD_PARTY = $(patsubst src/%.cc, %, ${wildcard src/*.cc}) +BPF = $(patsubst bpf/%.bpf.c, %, ${wildcard bpf/*.bpf.c}) TARGETS = stack_analyzer @@ -80,9 +81,6 @@ args = `arg="$(filter-out $@,$(MAKECMDGOALS))" && echo $${arg:-${1}}` .PHONY: all all: $(TARGETS) -.PHONY: bpf_skel_h -bpf_skel_h: $(BPF) - .PHONY: clean clean: $(call msg,CLEAN) @@ -92,12 +90,16 @@ $(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): $(call msg,MKDIR,$@) $(Q)mkdir -p $@ +$(BPF_SKEL): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + # Build libbpf $(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf $(call msg,LIB,$@) - $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ - OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ - INCLUDEDIR= LIBDIR= UAPIDIR= \ + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ install # Build bpftool @@ -106,7 +108,7 @@ $(BPFTOOL): | $(BPFTOOL_OUTPUT) $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap # Build BPF code -$(OUTPUT)/%.bpf.o: src/bpf/%.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) +$(OUTPUT)/%.bpf.o: bpf/%.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) $(call msg,BPF,$@) $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ @@ -115,26 +117,22 @@ $(OUTPUT)/%.bpf.o: src/bpf/%.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $( # Generate BPF skeletons .PHONY: $(BPF) -$(BPF): %: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) +$(BPF): %: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) $(BPF_SKEL) $(call msg,GEN-SKEL,$@) - $(Q)$(BPFTOOL) gen skeleton $< > include/bpf/$@.skel.h + $(Q)$(BPFTOOL) gen skeleton $< > $(BPF_SKEL)/$@.skel.h -$(patsubst %,$(OUTPUT)/%.o,$(BPF_WAPPER)): $(OUTPUT)/%.o: src/bpf/%.cpp +$(patsubst %,$(OUTPUT)/%.o,$(BPF_WAPPER)): $(OUTPUT)/%.o: src/bpf/%.cpp $(BPF) $(call msg,CXX,$@) $(Q)$(CXX) $(CFLAGS) $(INCLUDES) -c $< -o $@ # Build depending library -$(patsubst %,$(OUTPUT)/%.o,$(THIRD_PARTY)): $(OUTPUT)/%.o: src/%.cc +$(patsubst %,$(OUTPUT)/%.o,$(BIN)): $(OUTPUT)/%.o: src/%.cpp $(call msg,CXX,$@) $(Q)$(CXX) $(CFLAGS) $(INCLUDES) -c $< -o $@ # $(Q)$(CXX) $(CFLAGS) $(INCLUDES) $< -E > log -$(OUTPUT)/$(TARGETS).o: main.cpp $(BPF) $(patsubst %,$(OUTPUT)/%.o,$(BPF_WAPPER)) | $(OUTPUT) - $(call msg,CXX,$@) - $(Q)$(CXX) $(CFLAGS) $(INCLUDES) -c main.cpp -o $@ - # Build application binary -$(TARGETS): %: $(OUTPUT)/%.o $(patsubst %,$(OUTPUT)/%.o,$(THIRD_PARTY)) $(patsubst %,$(OUTPUT)/%.o,$(BPF_WAPPER)) $(LIBBPF_OBJ) | $(OUTPUT) +$(TARGETS): $(patsubst %,$(OUTPUT)/%.o,$(BIN)) $(patsubst %,$(OUTPUT)/%.o,$(BPF_WAPPER)) $(LIBBPF_OBJ) | $(OUTPUT) $(call msg,BINARY,$@) $(Q)$(CXX) $^ $(ALL_LDFLAGS) -lstdc++ -lelf -lz -o $@ diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/io_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/bpf/io.bpf.c similarity index 98% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/io_count.bpf.c rename to eBPF_Supermarket/Stack_Analyser/bpf/io.bpf.c index fa6d1ccb4..629cf1744 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/io_count.bpf.c +++ b/eBPF_Supermarket/Stack_Analyser/bpf/io.bpf.c @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ #include #include "sa_ebpf.h" -#include "bpf/IOStackCollector.h" +#include "bpf/io.h" #include "task.h" DeclareCommonMaps(io_tuple); diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/mem_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/bpf/mem.bpf.c similarity index 95% rename from MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/mem_count.bpf.c rename to eBPF_Supermarket/Stack_Analyser/bpf/mem.bpf.c index 40c96a743..e99a888e5 100644 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/mem_count.bpf.c +++ b/eBPF_Supermarket/Stack_Analyser/bpf/mem.bpf.c @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ BPF_HASH(piddr_meminfo, piddr, mem_info); // 记录了每次申请的内存空 const char LICENSE[] SEC("license") = "GPL"; -int gen_alloc_enter(size_t size) +static int gen_alloc_enter(size_t size) { if (size <= min || size > max) return 0; @@ -90,9 +90,9 @@ int BPF_KPROBE(mmap_enter) return gen_alloc_enter(size); } -int gen_alloc_exit(struct pt_regs *ctx) +static int gen_alloc_exit(struct pt_regs *ctx) { - void *addr = (void *)PT_REGS_RC(ctx); + u64 addr = PT_REGS_RC(ctx); if (!addr) { return 0; @@ -120,7 +120,7 @@ int gen_alloc_exit(struct pt_regs *ctx) } // record pid_addr-info piddr a = { - .addr = (u64)addr, + .addr = addr, .pid = pid, .o = 0, }; @@ -156,7 +156,7 @@ int BPF_KRETPROBE(mmap_exit) return gen_alloc_exit(ctx); } -int gen_free_enter(u64 addr, size_t unsize) +static int gen_free_enter(u64 addr, size_t unsize) { struct task_struct *curr = (struct task_struct *)bpf_get_current_task(); u32 pid = get_task_ns_pid(curr); diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/off_cpu_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/bpf/off_cpu.bpf.c similarity index 96% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/off_cpu_count.bpf.c rename to eBPF_Supermarket/Stack_Analyser/bpf/off_cpu.bpf.c index 7bdaf0527..e04d5fe46 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/off_cpu_count.bpf.c +++ b/eBPF_Supermarket/Stack_Analyser/bpf/off_cpu.bpf.c @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ BPF_HASH(start, u32, u64); // const char LICENSE[] SEC("license") = "GPL"; -SEC("kprobe/finish_task_switch.isra.0") //动态挂载点finish_task_switch.isra.0 +SEC("kprobe/finish_task_switch") //动态挂载点finish_task_switch.isra.0 int BPF_KPROBE(do_stack, struct task_struct *curr) { // u32 pid = BPF_CORE_READ(curr, pid); diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/on_cpu_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/bpf/on_cpu.bpf.c similarity index 98% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/on_cpu_count.bpf.c rename to eBPF_Supermarket/Stack_Analyser/bpf/on_cpu.bpf.c index 1b479dcf9..7db0a095a 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/on_cpu_count.bpf.c +++ b/eBPF_Supermarket/Stack_Analyser/bpf/on_cpu.bpf.c @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/stack_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/bpf/probe.bpf.c similarity index 97% rename from MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/stack_count.bpf.c rename to eBPF_Supermarket/Stack_Analyser/bpf/probe.bpf.c index 5796a2a81..bc697fd84 100644 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/bpf/stack_count.bpf.c +++ b/eBPF_Supermarket/Stack_Analyser/bpf/probe.bpf.c @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,8 +26,6 @@ DeclareCommonMaps(u32); DeclareCommonVar(); - -// 传进来的参数 int apid = 0; const char LICENSE[] SEC("license") = "GPL"; diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/pre_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/bpf/readahead.bpf.c similarity index 98% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/pre_count.bpf.c rename to eBPF_Supermarket/Stack_Analyser/bpf/readahead.bpf.c index 6d5e95581..cdd8e1455 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/pre_count.bpf.c +++ b/eBPF_Supermarket/Stack_Analyser/bpf/readahead.bpf.c @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ #include "sa_ebpf.h" #include "task.h" -#include "bpf/ReadaheadStackCollector.h" +#include "bpf/readahead.h" #define MINBLOCK_US 1ULL #define MAXBLOCK_US 99999999ULL diff --git a/eBPF_Supermarket/Stack_Analyser/bpf/template.bpf.c b/eBPF_Supermarket/Stack_Analyser/bpf/template.bpf.c new file mode 100644 index 000000000..18c88c7ee --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/bpf/template.bpf.c @@ -0,0 +1,31 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// 内核态bpf程序的模板代码 + +#include "vmlinux.h" +#include +#include +#include + +#include "sa_ebpf.h" +#include "bpf/template.h" +#include "task.h" + +DeclareCommonMaps(__u32); +DeclareCommonVar(); + +const char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/IOStackCollector.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/IOStackCollector.h deleted file mode 100644 index 5761cf425..000000000 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/IOStackCollector.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef _SA_IO_H__ -#define _SA_IO_H__ - -#include -typedef struct -{ - __u64 size : 40; - __u64 count : 24; -} io_tuple; - -#ifdef __cplusplus -#include "bpf/io_count.skel.h" -#include "bpf/eBPFStackCollector.h" - -class IOStackCollector : public StackCollector -{ -private: - declareEBPF(io_count_bpf); - -public: - enum io_mod - { - COUNT, - SIZE, - AVE, - } DataType = COUNT; - -protected: - virtual double count_value(void *); - -public: - void setScale(io_mod mod); - IOStackCollector(); - virtual int load(void); - virtual int attach(void); - virtual void detach(void); - virtual void unload(void); -}; -#endif - -#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/OffCPUStackCollector.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/OffCPUStackCollector.h deleted file mode 100644 index 6d3084870..000000000 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/OffCPUStackCollector.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _SA_OFF_CPU_H__ -#define _SA_OFF_CPU_H__ - -#include "bpf/eBPFStackCollector.h" -#include "bpf/off_cpu_count.skel.h" - -class OffCPUStackCollector : public StackCollector -{ -private: - struct off_cpu_count_bpf *skel = __null; - -protected: - virtual double count_value(void*); - -public: - OffCPUStackCollector(); - virtual int load(void); - virtual int attach(void); - virtual void detach(void); - virtual void unload(void); -}; - -#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/OnCPUStackCollector.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/OnCPUStackCollector.h deleted file mode 100644 index 65f209a59..000000000 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/OnCPUStackCollector.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef _SA_ON_CPU_H__ -#define _SA_ON_CPU_H__ - -#include "eBPFStackCollector.h" -#include "on_cpu_count.skel.h" - - -#ifdef __cplusplus -class OnCPUStackCollector : public StackCollector -{ -private: - struct on_cpu_count_bpf *skel = __null; - - int *pefds = NULL; - int num_cpus = 0; - struct bpf_link **links = NULL; - unsigned long long freq = 49; - -protected: - virtual double count_value(void *); - -public: - void setScale(uint64_t freq); - OnCPUStackCollector(); - virtual int load(void); - virtual int attach(void); - virtual void detach(void); - virtual void unload(void); -}; -#endif - -#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/ProbeStackCollector.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/ProbeStackCollector.h deleted file mode 100644 index 90c80fbab..000000000 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/ProbeStackCollector.h +++ /dev/null @@ -1,22 +0,0 @@ -#include "bpf/eBPFStackCollector.h" -#include "bpf/stack_count.skel.h" - -class StackCountStackCollector : public StackCollector -{ -private: - struct stack_count_bpf *skel = __null; - -public: - std::string probe; - -protected: - virtual double count_value(void *); - -public: - void setScale(std::string probe); - StackCountStackCollector(); - virtual int load(void); - virtual int attach(void); - virtual void detach(void); - virtual void unload(void); -}; diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/ReadaheadStackCollector.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/ReadaheadStackCollector.h deleted file mode 100644 index 6df11f971..000000000 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/ReadaheadStackCollector.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef _SA_READAHEAD_H__ -#define _SA_READAHEAD_H__ - -#include -typedef struct -{ - __u32 expect; - __u32 truth; -} ra_tuple; - -#ifdef __cplusplus -#include "bpf/pre_count.skel.h" -#include "bpf/eBPFStackCollector.h" - -class ReadaheadStackCollector : public StackCollector -{ -private: - declareEBPF(pre_count_bpf); - -protected: - virtual double count_value(void *data); - -public: - ReadaheadStackCollector(); - virtual int load(void); - virtual int attach(void); - virtual void detach(void); - virtual void unload(void); -}; -#endif - -#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/TemplateClass.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/TemplateClass.h deleted file mode 100644 index 9b464e9bb..000000000 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/TemplateClass.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef _TEMPLATE_H__ -#define _TEMPLATE_H__ - -// ========== C code part ========== - -// ========== C code end ========== - -#ifdef __cplusplus -// ========== C++ code part ========== -#include "bpf/template.skel.h" -#include "bpf/eBPFStackCollector.h" - -class TemplateClass : public StackCollector -{ -private: - declareEBPF(template_bpf); - -protected: - virtual double count_value(void *); - -public: - TemplateClass(); - virtual int load(void); - virtual int attach(void); - virtual void detach(void); - virtual void unload(void); -}; -// ========== C++ code end ========== -#endif - -#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/eBPFStackCollector.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/eBPFStackCollector.h index 70ccd56d0..137eb723a 100644 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/eBPFStackCollector.h +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/eBPFStackCollector.h @@ -1,3 +1,21 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// 包装用于采集调用栈数据的eBPF程序,声明接口、通用成员和一些辅助结构 + #ifndef _SA_STACK_COLLECTOR_H__ #define _SA_STACK_COLLECTOR_H__ @@ -78,7 +96,7 @@ class StackCollector virtual void unload(void) = 0; // 声明eBPF骨架 -#define declareEBPF(eBPFName) struct eBPFName *skel = NULL; +#define declareEBPF(func) struct func##_bpf *skel = NULL; /// @brief 加载、初始化参数并打开指定类型的ebpf程序 /// @param ... 一些ebpf程序全局变量初始化语句 diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/io.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/io.h new file mode 100644 index 000000000..9370d5f0c --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/io.h @@ -0,0 +1,59 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// io ebpf程序的包装类,声明接口和一些自定义方法 + +#ifndef _SA_IO_H__ +#define _SA_IO_H__ + +#include +typedef struct +{ + __u64 size : 40; + __u64 count : 24; +} io_tuple; + +#ifdef __cplusplus +#include "io.skel.h" +#include "bpf/eBPFStackCollector.h" + +class IOStackCollector : public StackCollector +{ +private: + declareEBPF(io); + +public: + enum io_mod + { + COUNT, + SIZE, + AVE, + } DataType = COUNT; + +protected: + virtual double count_value(void *); + +public: + void setScale(io_mod mod); + IOStackCollector(); + virtual int load(void); + virtual int attach(void); + virtual void detach(void); + virtual void unload(void); +}; +#endif + +#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/MemoryStackCollector.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/mem.h similarity index 78% rename from eBPF_Supermarket/Stack_Analyser/include/bpf/MemoryStackCollector.h rename to eBPF_Supermarket/Stack_Analyser/include/bpf/mem.h index 9ac120bc2..7294dd663 100644 --- a/eBPF_Supermarket/Stack_Analyser/include/bpf/MemoryStackCollector.h +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/mem.h @@ -1,14 +1,32 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// mem ebpf程序的包装类,声明接口和一些自定义方法 + #ifndef _SA_MEMORY_H__ #define _SA_MEMORY_H__ #include "bpf/eBPFStackCollector.h" -#include "bpf/mem_count.skel.h" +#include "mem.skel.h" #include class MemoryStackCollector : public StackCollector { private: - struct mem_count_bpf *skel = __null; + struct mem_bpf *skel = __null; public: char *object = (char *)"libc.so.6"; @@ -29,20 +47,6 @@ class MemoryStackCollector : public StackCollector /// @param sym_name 用户态函数名字面量,不加双引号 /// @param prog_name ebpf处理函数,skel->progs中的成员名 /// @param is_retprobe 布尔类型,是否附加到符号返回处 -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) -#define ATTACH_UPROBE(skel, sym_name, prog_name, is_retprobe) \ - do \ - { \ - DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts, \ - .retprobe = is_retprobe); \ - skel->links.prog_name = bpf_program__attach_uprobe_opts( \ - skel->progs.prog_name, \ - pid, \ - object, \ - 0, \ - &uprobe_opts); \ - } while (false) -#else #define ATTACH_UPROBE(skel, sym_name, prog_name, is_retprobe) \ do \ { \ @@ -57,7 +61,6 @@ class MemoryStackCollector : public StackCollector 0, \ &uprobe_opts); \ } while (false) -#endif /// @brief 向指定用户函数附加一个处理函数并检查是否连接成功 /// @param skel ebpf程序骨架 diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/off_cpu.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/off_cpu.h new file mode 100644 index 000000000..f9fb15e0e --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/off_cpu.h @@ -0,0 +1,41 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// off cpu ebpf程序的包装类,声明接口和一些自定义方法 + +#ifndef _SA_OFF_CPU_H__ +#define _SA_OFF_CPU_H__ + +#include "bpf/eBPFStackCollector.h" +#include "off_cpu.skel.h" + +class OffCPUStackCollector : public StackCollector +{ +private: + struct off_cpu_bpf *skel = __null; + +protected: + virtual double count_value(void*); + +public: + OffCPUStackCollector(); + virtual int load(void); + virtual int attach(void); + virtual void detach(void); + virtual void unload(void); +}; + +#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/on_cpu.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/on_cpu.h new file mode 100644 index 000000000..9fd155035 --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/on_cpu.h @@ -0,0 +1,50 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// on cpu ebpf程序的包装类,声明接口和一些自定义方法 + +#ifndef _SA_ON_CPU_H__ +#define _SA_ON_CPU_H__ + +#include "eBPFStackCollector.h" +#include "on_cpu.skel.h" + + +#ifdef __cplusplus +class OnCPUStackCollector : public StackCollector +{ +private: + struct on_cpu_bpf *skel = __null; + + int *pefds = NULL; + int num_cpus = 0; + struct bpf_link **links = NULL; + unsigned long long freq = 49; + +protected: + virtual double count_value(void *); + +public: + void setScale(uint64_t freq); + OnCPUStackCollector(); + virtual int load(void); + virtual int attach(void); + virtual void detach(void); + virtual void unload(void); +}; +#endif + +#endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/probe.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/probe.h new file mode 100644 index 000000000..60d24f7a4 --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/probe.h @@ -0,0 +1,40 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// probe ebpf程序的包装类,声明接口和一些自定义方法 + +#include "bpf/eBPFStackCollector.h" +#include "probe.skel.h" + +class StackCountStackCollector : public StackCollector +{ +private: + struct probe_bpf *skel = __null; + +public: + std::string probe; + +protected: + virtual double count_value(void *); + +public: + void setScale(std::string probe); + StackCountStackCollector(); + virtual int load(void); + virtual int attach(void); + virtual void detach(void); + virtual void unload(void); +}; diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_common.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/readahead.h similarity index 51% rename from MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_common.h rename to eBPF_Supermarket/Stack_Analyser/include/bpf/readahead.h index a142fa261..5a6f6f23f 100644 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/sa_common.h +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/readahead.h @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,36 +14,37 @@ // // author: luiyanbing@foxmail.com // -// 通用数据结构 +// readahead ebpf程序的包装类,声明接口和一些自定义方法 -#ifndef STACK_ANALYZER_COMMON -#define STACK_ANALYZER_COMMON +#ifndef _SA_READAHEAD_H__ +#define _SA_READAHEAD_H__ #include - -#define COMM_LEN 16 // 进程名最大长度 -#define MAX_STACKS 32 // 栈最大深度 -#define MAX_ENTRIES 102400 // map容量 - -/// @brief 栈计数的键,可以唯一标识一个用户内核栈 -typedef struct { - __u32 pid; - __s32 ksid, usid; -} psid; - -/// @brief 进程名 -typedef struct { - char str[COMM_LEN]; -} comm; - -typedef struct { - __u64 truth; - __u64 expect; +typedef struct +{ + __u32 expect; + __u32 truth; } ra_tuple; -typedef struct { - __u64 count; - __u64 size; -} io_tuple; +#ifdef __cplusplus +#include "readahead.skel.h" +#include "bpf/eBPFStackCollector.h" + +class ReadaheadStackCollector : public StackCollector +{ +private: + declareEBPF(readahead); + +protected: + virtual double count_value(void *data); + +public: + ReadaheadStackCollector(); + virtual int load(void); + virtual int attach(void); + virtual void detach(void); + virtual void unload(void); +}; +#endif #endif \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/include/bpf/template.h b/eBPF_Supermarket/Stack_Analyser/include/bpf/template.h new file mode 100644 index 000000000..2cb4a7509 --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/include/bpf/template.h @@ -0,0 +1,49 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// ebpf程序的包装类的模板,声明接口和一些自定义方法,以及辅助结构 + +#ifndef _TEMPLATE_H__ +#define _TEMPLATE_H__ + +// ========== C code part ========== + +// ========== C code end ========== + +#ifdef __cplusplus +// ========== C++ code part ========== +#include "template.skel.h" +#include "bpf/eBPFStackCollector.h" + +class TemplateClass : public StackCollector +{ +private: + declareEBPF(template_bpf); + +protected: + virtual double count_value(void *); + +public: + TemplateClass(); + virtual int load(void); + virtual int attach(void); + virtual void detach(void); + virtual void unload(void); +}; +// ========== C++ code end ========== +#endif + +#endif \ No newline at end of file diff --git a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/elf.h b/eBPF_Supermarket/Stack_Analyser/include/dt_elf.h similarity index 96% rename from MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/elf.h rename to eBPF_Supermarket/Stack_Analyser/include/dt_elf.h index 7e02c0ea9..a6174a376 100644 --- a/MagicEyes/src/backend/system_diagnosis/stack_analyzer/include/elf.h +++ b/eBPF_Supermarket/Stack_Analyser/include/dt_elf.h @@ -13,7 +13,7 @@ #include #include -#include "symbol.h" +#include "dt_symbol.h" #define BUILD_ID_SIZE 40 bool save_symbol_cache(std::set &ss, const char *path); diff --git a/eBPF_Supermarket/Stack_Analyser/include/symbol.h b/eBPF_Supermarket/Stack_Analyser/include/dt_symbol.h similarity index 99% rename from eBPF_Supermarket/Stack_Analyser/include/symbol.h rename to eBPF_Supermarket/Stack_Analyser/include/dt_symbol.h index 38237e0bb..3f2c3ffe9 100644 --- a/eBPF_Supermarket/Stack_Analyser/include/symbol.h +++ b/eBPF_Supermarket/Stack_Analyser/include/dt_symbol.h @@ -136,6 +136,7 @@ class symbol_parser { std::set& get_java_procs() { return java_procs; } bool find_kernel_symbol(symbol &sym); + bool complete_kernel_symbol(symbol &sym); /// @brief 从elf file中查找sym中地址对应的符号名存入sym /// @param sym 符号对象 diff --git a/eBPF_Supermarket/Stack_Analyser/include/elf.hpp b/eBPF_Supermarket/Stack_Analyser/include/elf.hpp deleted file mode 100644 index 7e02c0ea9..000000000 --- a/eBPF_Supermarket/Stack_Analyser/include/elf.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Linux内核诊断工具--elf相关函数头文件 - * - * Copyright (C) 2020 Alibaba Ltd. - * - * License terms: GNU General Public License (GPL) version 3 - * - */ - -#ifndef _PERF_ELF_H__ -#define _PERF_ELF_H__ - -#include -#include - -#include "symbol.h" - -#define BUILD_ID_SIZE 40 -bool save_symbol_cache(std::set &ss, const char *path); -bool load_symbol_cache(std::set &ss, const char *path, const char *filename); - -bool get_symbol_from_elf(std::set &ss, const char *path); -bool search_symbol(const std::set &ss, symbol &sym); -int filename__read_build_id(int pid, const char *mnt_ns_name, const char *filename, char *bf, size_t size); -#endif diff --git a/eBPF_Supermarket/Stack_Analyser/include/sa_common.h b/eBPF_Supermarket/Stack_Analyser/include/sa_common.h index 5735de509..d85564d5e 100644 --- a/eBPF_Supermarket/Stack_Analyser/include/sa_common.h +++ b/eBPF_Supermarket/Stack_Analyser/include/sa_common.h @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/eBPF_Supermarket/Stack_Analyser/include/sa_ebpf.h b/eBPF_Supermarket/Stack_Analyser/include/sa_ebpf.h index accbcba88..1dc55d83c 100644 --- a/eBPF_Supermarket/Stack_Analyser/include/sa_ebpf.h +++ b/eBPF_Supermarket/Stack_Analyser/include/sa_ebpf.h @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/eBPF_Supermarket/Stack_Analyser/include/sa_user.h b/eBPF_Supermarket/Stack_Analyser/include/sa_user.h index cee3b9c19..16efa7119 100644 --- a/eBPF_Supermarket/Stack_Analyser/include/sa_user.h +++ b/eBPF_Supermarket/Stack_Analyser/include/sa_user.h @@ -1,4 +1,4 @@ -// Copyright 2023 The LMP Authors. +// Copyright 2024 The LMP Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/eBPF_Supermarket/Stack_Analyser/new_bpf.sh b/eBPF_Supermarket/Stack_Analyser/new_bpf.sh index c5266bcfd..afa503a12 100755 --- a/eBPF_Supermarket/Stack_Analyser/new_bpf.sh +++ b/eBPF_Supermarket/Stack_Analyser/new_bpf.sh @@ -13,18 +13,18 @@ done class_name=$name"StackCollector" -cp include/bpf/TemplateClass.h include/bpf/$class_name.h -sed -i 's/_TEMPLATE_H__/_SA_'$upper_name'_H__/g' include/bpf/$class_name.h -sed -i 's/TemplateClass/'$class_name'/g' include/bpf/$class_name.h -sed -i 's/template/'$origin_name'/g' include/bpf/$class_name.h +cp include/bpf/TemplateClass.h include/bpf/$origin_name.h +sed -i 's/_TEMPLATE_H__/_SA_'$upper_name'_H__/g' include/bpf/$origin_name.h +sed -i 's/TemplateClass/'$class_name'/g' include/bpf/$origin_name.h +sed -i 's/template/'$origin_name'/g' include/bpf/$origin_name.h -cp src/bpf/TemplateClass.cpp src/bpf/$class_name.cpp -sed -i 's/TemplateClass/'$class_name'/g' src/bpf/$class_name.cpp +cp src/bpf/TemplateClass.cpp src/bpf/$origin_name.cpp +sed -i 's/TemplateClass/'$class_name'/g' src/bpf/$origin_name.cpp cp src/bpf/template.bpf.c src/bpf/$origin_name.bpf.c sed -i 's/TemplateClass/'$class_name'/g' src/bpf/$origin_name.bpf.c -sed -i '/#include "bpf\/OnCPUStackCollector.h"/a#include "bpf\/'$class_name'.h"' main.cpp +sed -i '/#include "bpf\/on_cpu.h"/a#include "bpf\/'$origin_name'.h"' main.cpp sed -i '/auto cli = (MainOption,/iauto '$name'Option = (clipp::option("'$origin_name'").call([]{ StackCollectorList.push_back(new '$class_name'()); }) %"sample the '$origin_name' of calling stacks") & (SubOption);' main.cpp diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/OffCPUStackCollector.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/OffCPUStackCollector.cpp deleted file mode 100644 index b3c9864f9..000000000 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/OffCPUStackCollector.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "bpf/OffCPUStackCollector.h" - -OffCPUStackCollector::OffCPUStackCollector() -{ - scale.Period = 1 << 20; - scale.Type = "OffCPUTime"; - scale.Unit = "milliseconds"; -}; - -double OffCPUStackCollector::count_value(void *data) -{ - return *(uint32_t *)data; -}; - -int OffCPUStackCollector::load(void) -{ - StackProgLoadOpen(skel->bss->apid = pid;); - return 0; -} - -int OffCPUStackCollector::attach(void) -{ - defaultAttach; - return 0; -} - -void OffCPUStackCollector::detach(void) { - defaultDetach; -} - -void OffCPUStackCollector::unload(void) { - defaultUnload; -} \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/ReadaheadStackCollector.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/ReadaheadStackCollector.cpp deleted file mode 100644 index e4b292658..000000000 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/ReadaheadStackCollector.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "bpf/ReadaheadStackCollector.h" - -double ReadaheadStackCollector::count_value(void *data) -{ - ra_tuple *p = (ra_tuple *)data; - return p->expect - p->truth; -}; - -ReadaheadStackCollector::ReadaheadStackCollector() -{ - showDelta = false; - scale = { - .Type = "UnusedReadaheadPages", - .Unit = "pages", - .Period = 1, - }; -}; - -int ReadaheadStackCollector::load(void) -{ - StackProgLoadOpen(); - return 0; -} - -int ReadaheadStackCollector::attach(void) -{ - defaultAttach; - return 0; -} - -void ReadaheadStackCollector::detach(void) -{ - defaultDetach; -} - -void ReadaheadStackCollector::unload(void) -{ - defaultUnload; -} \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/TemplateClass.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/TemplateClass.cpp deleted file mode 100644 index 12a60eaf1..000000000 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/TemplateClass.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "bpf/TemplateClass.h" - -// ========== implement virtual func ========== - -double TemplateClass::count_value(void *data) -{ - return *(uint32_t*)data; -}; - -int TemplateClass::load(void) -{ - return 0; -}; - -int TemplateClass::attach(void) -{ - return 0; -}; - -void TemplateClass::detach(void){}; - -void TemplateClass::unload(void){}; - -// ========== other implementations ========== - -TemplateClass::TemplateClass(){}; \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/eBPFStackCollector.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/eBPFStackCollector.cpp index b73c07f5a..18f1ff32f 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/eBPFStackCollector.cpp +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/eBPFStackCollector.cpp @@ -1,6 +1,24 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// 包装用于采集调用栈数据的eBPF程序,规定一些抽象接口和通用变量 + #include "bpf/eBPFStackCollector.h" #include "sa_user.h" -#include "symbol.h" +#include "dt_symbol.h" #include #include diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/IOStackCollector.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/io.cpp similarity index 55% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/IOStackCollector.cpp rename to eBPF_Supermarket/Stack_Analyser/src/bpf/io.cpp index b12d38ee7..92177f6fb 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/IOStackCollector.cpp +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/io.cpp @@ -1,5 +1,22 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// io ebpf程序的包装类,实现接口和一些自定义方法 -#include "bpf/IOStackCollector.h" +#include "bpf/io.h" double IOStackCollector::count_value(void *data) { diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/MemoryStackCollector.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/mem.cpp similarity index 65% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/MemoryStackCollector.cpp rename to eBPF_Supermarket/Stack_Analyser/src/bpf/mem.cpp index 93e204365..90adc7461 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/MemoryStackCollector.cpp +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/mem.cpp @@ -1,4 +1,22 @@ -#include "bpf/MemoryStackCollector.h" +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// mem ebpf程序的包装类,实现接口和一些自定义方法 + +#include "bpf/mem.h" double MemoryStackCollector::count_value(void *d) { diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/mem_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/src/bpf/mem_count.bpf.c deleted file mode 100644 index 40c96a743..000000000 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/mem_count.bpf.c +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: luiyanbing@foxmail.com -// -// 内核态ebpf的内存模块代码 - -#include "vmlinux.h" -#include -#include -#include - -#include "sa_ebpf.h" -#include "task.h" - -DeclareCommonMaps(u64); -DeclareCommonVar(); - -/// @brief 内存信息的键,唯一标识一块被分配的内存 -/// @note o为可初始化的填充对齐成员,贴合bpf verifier要求 -typedef struct -{ - __u64 addr; - __u32 pid, o; -} piddr; - -/// @brief 内存分配信息,可溯源的一次内存分配 -/// @note o为可初始化的填充对齐成员,贴合bpf verifier要求 -typedef struct -{ - __u64 size; - __u32 usid, o; -} mem_info; - -BPF_HASH(pid_size, u32, u64); // 记录了对应进程使用malloc,calloc等函数申请内存的大小 -BPF_HASH(piddr_meminfo, piddr, mem_info); // 记录了每次申请的内存空间的起始地址等信息 - -const char LICENSE[] SEC("license") = "GPL"; - -int gen_alloc_enter(size_t size) -{ - if (size <= min || size > max) - return 0; - struct task_struct *curr = (struct task_struct *)bpf_get_current_task(); - ignoreKthread(curr); - // update group - u32 pid = get_task_ns_pid(curr); - if (pid == self_pid) - return 0; - u32 tgid = get_task_ns_tgid(curr); - bpf_map_update_elem(&pid_tgid, &pid, &tgid, BPF_ANY); - // update comm - if (!bpf_map_lookup_elem(&pid_comm, &pid)) - { - comm name; - bpf_get_current_comm(&name, COMM_LEN); - bpf_map_update_elem(&pid_comm, &pid, &name, BPF_NOEXIST); - } - // record size - return bpf_map_update_elem(&pid_size, &pid, &size, BPF_ANY); -} - -SEC("uprobe/malloc") -int BPF_KPROBE(malloc_enter, size_t size) -{ - return gen_alloc_enter(size); -} - -SEC("uprobe/calloc") -int BPF_KPROBE(calloc_enter, size_t nmemb, size_t size) -{ - return gen_alloc_enter(nmemb * size); -} - -SEC("uprobe/mmap") -int BPF_KPROBE(mmap_enter) -{ - size_t size = PT_REGS_PARM2(ctx); - return gen_alloc_enter(size); -} - -int gen_alloc_exit(struct pt_regs *ctx) -{ - void *addr = (void *)PT_REGS_RC(ctx); - if (!addr) - { - return 0; - } - u32 pid = get_task_ns_pid((struct task_struct *)bpf_get_current_task()); - u64 *size = bpf_map_lookup_elem(&pid_size, &pid); - if (!size) - { - return 0; - } - // record counts - psid apsid = { - .pid = pid, - .usid = u ? USER_STACK : -1, - .ksid = k ? KERNEL_STACK : -1, - }; - u64 *count = bpf_map_lookup_elem(&psid_count, &apsid); - if (!count) - { - bpf_map_update_elem(&psid_count, &apsid, size, BPF_NOEXIST); - } - else - { - __sync_fetch_and_add(count, *size); - } - // record pid_addr-info - piddr a = { - .addr = (u64)addr, - .pid = pid, - .o = 0, - }; - mem_info info = { - .size = *size, - .usid = apsid.usid, - .o = 0, - }; - return bpf_map_update_elem(&piddr_meminfo, &a, &info, BPF_NOEXIST); -} - -SEC("uretprobe/malloc") -int BPF_KRETPROBE(malloc_exit) -{ - return gen_alloc_exit(ctx); -} - -SEC("uretprobe/calloc") -int BPF_KRETPROBE(calloc_exit) -{ - return gen_alloc_exit(ctx); -} - -SEC("uretprobe/realloc") -int BPF_KRETPROBE(realloc_exit) -{ - return gen_alloc_exit(ctx); -} - -SEC("uretprobe/mmap") -int BPF_KRETPROBE(mmap_exit) -{ - return gen_alloc_exit(ctx); -} - -int gen_free_enter(u64 addr, size_t unsize) -{ - struct task_struct *curr = (struct task_struct *)bpf_get_current_task(); - u32 pid = get_task_ns_pid(curr); - piddr a = {.addr = addr, .pid = pid, .o = 0}; - mem_info *info = bpf_map_lookup_elem(&piddr_meminfo, &a); - if (!info) - return -1; - - // get allocated size - psid apsid = { - .ksid = -1, - .pid = pid, - .usid = info->usid, - }; - - u64 *size = bpf_map_lookup_elem(&psid_count, &apsid); - if (!size) - return -1; - - // sub the freeing size - if (unsize) - { - if (unsize >= *size) - { - *size = 0; - } - else - { - __sync_fetch_and_sub(size, unsize); - } - } - else - { - __sync_fetch_and_sub(size, info->size); - } - - if (*size == 0) - { - bpf_map_delete_elem(&psid_count, &apsid); - } - - // del freeing addr info - return bpf_map_delete_elem(&piddr_meminfo, &a); -} - -SEC("uprobe/free") -int BPF_KPROBE(free_enter, void *addr) -{ - return gen_free_enter((u64)addr, 0); -} - -SEC("uprobe/realloc") -int BPF_KPROBE(realloc_enter, void *ptr, size_t size) -{ - gen_free_enter((u64)ptr, 0); - return gen_alloc_enter(size); -} - -SEC("uprobe/munmap") -int BPF_KPROBE(munmap_enter, void *addr, size_t unsize) -{ - return gen_free_enter((u64)addr, unsize); -} diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/off_cpu.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/off_cpu.cpp new file mode 100644 index 000000000..7e392d9a1 --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/off_cpu.cpp @@ -0,0 +1,58 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// off cpu ebpf程序的包装类,实现接口和一些自定义方法 + +#include "bpf/off_cpu.h" +#include "dt_symbol.h" + +OffCPUStackCollector::OffCPUStackCollector() +{ + scale.Period = 1 << 20; + scale.Type = "OffCPUTime"; + scale.Unit = "milliseconds"; +}; + +double OffCPUStackCollector::count_value(void *data) +{ + return *(uint32_t *)data; +}; + +int OffCPUStackCollector::load(void) +{ + StackProgLoadOpen(skel->bss->apid = pid;); + return 0; +} + +int OffCPUStackCollector::attach(void) +{ + symbol sym; + sym.name = "finish_task_switch"; + if(!g_symbol_parser.complete_kernel_symbol(sym)) + { + return -1; + } + skel->links.do_stack = bpf_program__attach_kprobe(skel->progs.do_stack, false, sym.name.c_str()); + return 0; +} + +void OffCPUStackCollector::detach(void) { + defaultDetach; +} + +void OffCPUStackCollector::unload(void) { + defaultUnload; +} \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/OnCPUStackCollector.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/on_cpu.cpp similarity index 81% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/OnCPUStackCollector.cpp rename to eBPF_Supermarket/Stack_Analyser/src/bpf/on_cpu.cpp index a883a7045..1dd3d2db9 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/OnCPUStackCollector.cpp +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/on_cpu.cpp @@ -1,4 +1,22 @@ -#include "bpf/OnCPUStackCollector.h" +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// on cpu ebpf程序的包装类,实现接口和一些自定义方法 + +#include "bpf/on_cpu.h" #include #include diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/ProbeStackCollector.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/probe.cpp similarity index 75% rename from eBPF_Supermarket/Stack_Analyser/src/bpf/ProbeStackCollector.cpp rename to eBPF_Supermarket/Stack_Analyser/src/bpf/probe.cpp index a61189f68..b6ffa99a2 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/ProbeStackCollector.cpp +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/probe.cpp @@ -1,4 +1,22 @@ -#include "bpf/ProbeStackCollector.h" +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: GaoYixiang +// +// probe ebpf 程序的包装类,实现接口和一些自定义方法 + +#include "bpf/probe.h" #include "uprobe_helpers.h" double StackCountStackCollector::count_value(void *data) @@ -55,7 +73,7 @@ int StackCountStackCollector::attach(void) func = strList[2]; skel->links.handle = bpf_program__attach_kprobe(skel->progs.handle, false, func.c_str()); - CHECK_ERR(!skel->links.handle, "Fail to attach kprobe111"); + CHECK_ERR(!skel->links.handle, "Fail to attach kprobe"); return 0; } else if (strList.size() == 3 && strList[0] == "t") diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/readahead.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/readahead.cpp new file mode 100644 index 000000000..ee1b58ed8 --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/readahead.cpp @@ -0,0 +1,57 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// readahead ebpf程序的包装类,实现接口和一些自定义方法 + +#include "bpf/readahead.h" + +double ReadaheadStackCollector::count_value(void *data) +{ + ra_tuple *p = (ra_tuple *)data; + return p->expect - p->truth; +}; + +ReadaheadStackCollector::ReadaheadStackCollector() +{ + showDelta = false; + scale = { + .Type = "UnusedReadaheadPages", + .Unit = "pages", + .Period = 1, + }; +}; + +int ReadaheadStackCollector::load(void) +{ + StackProgLoadOpen(); + return 0; +} + +int ReadaheadStackCollector::attach(void) +{ + defaultAttach; + return 0; +} + +void ReadaheadStackCollector::detach(void) +{ + defaultDetach; +} + +void ReadaheadStackCollector::unload(void) +{ + defaultUnload; +} \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/stack_count.bpf.c b/eBPF_Supermarket/Stack_Analyser/src/bpf/stack_count.bpf.c deleted file mode 100644 index a5304610a..000000000 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/stack_count.bpf.c +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2023 The LMP Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// author: GaoYixiang -// -// 内核态eBPF的通用的调用栈计数代码 - -#include "vmlinux.h" -#include -#include -#include - -#include "../include/sa_ebpf.h" -#include "../include/task.h" - - -DeclareCommonMaps(u32); -DeclareCommonVar(); - -// 传进来的参数 -int apid = 0; - -const char LICENSE[] SEC("license") = "GPL"; - -static int handle_func(void *ctx) -{ - struct task_struct *curr = (struct task_struct *)bpf_get_current_task(); // 利用bpf_get_current_task()获得当前的进程tsk - ignoreKthread(curr); - - u32 pid = get_task_ns_pid(curr); // 利用帮助函数获得当前进程的pid - if ((apid >= 0 && pid != apid) || !pid || pid == self_pid) - return 0; - - u32 tgid = get_task_ns_tgid(curr); // 利用帮助函数获取进程的tgid - bpf_map_update_elem(&pid_tgid, &pid, &tgid, BPF_ANY); // 将pid_tgid表中的pid选项更新为tgid,若没有该表项,则创建 - - if (!bpf_map_lookup_elem(&pid_comm, &pid)) - { - comm name; - bpf_get_current_comm(&name, COMM_LEN); - bpf_map_update_elem(&pid_comm, &pid, &name, BPF_NOEXIST); - } - - psid apsid = { - .pid = pid, - .usid = u ? USER_STACK : -1, - .ksid = k ? KERNEL_STACK : -1, - }; - - u32 *cnt = bpf_map_lookup_elem(&psid_count, &apsid); - if (!cnt) - { - u32 ONE = 1; - bpf_map_update_elem(&psid_count, &apsid, &ONE, BPF_NOEXIST); - } - else - { - (*cnt)++; - } - - return 0; -} - -SEC("kprobe/dummy_kprobe") -int BPF_KPROBE(handle) -{ - handle_func(ctx); - return 0; -} -SEC("tp/sched/dummy_tp") -int handle_tp(void *ctx) -{ - handle_func(ctx); - return 0; -} \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/template.bpf.c b/eBPF_Supermarket/Stack_Analyser/src/bpf/template.bpf.c deleted file mode 100644 index 6e6b93a9d..000000000 --- a/eBPF_Supermarket/Stack_Analyser/src/bpf/template.bpf.c +++ /dev/null @@ -1,14 +0,0 @@ - -#include "vmlinux.h" -#include -#include -#include - -#include "sa_ebpf.h" -#include "bpf/TemplateClass.h" -#include "task.h" - -DeclareCommonMaps(__u32); -DeclareCommonVar(); - -const char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/bpf/template.cpp b/eBPF_Supermarket/Stack_Analyser/src/bpf/template.cpp new file mode 100644 index 000000000..33acf521e --- /dev/null +++ b/eBPF_Supermarket/Stack_Analyser/src/bpf/template.cpp @@ -0,0 +1,44 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// ebpf程序包装类的模板,实现接口和一些自定义方法 + +#include "bpf/template.h" + +// ========== implement virtual func ========== + +double TemplateClass::count_value(void *data) +{ + return *(uint32_t*)data; +}; + +int TemplateClass::load(void) +{ + return 0; +}; + +int TemplateClass::attach(void) +{ + return 0; +}; + +void TemplateClass::detach(void){}; + +void TemplateClass::unload(void){}; + +// ========== other implementations ========== + +TemplateClass::TemplateClass(){}; \ No newline at end of file diff --git a/eBPF_Supermarket/Stack_Analyser/src/elf.cc b/eBPF_Supermarket/Stack_Analyser/src/dt_elf.cpp similarity index 99% rename from eBPF_Supermarket/Stack_Analyser/src/elf.cc rename to eBPF_Supermarket/Stack_Analyser/src/dt_elf.cpp index 40831786d..d040e8936 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/elf.cc +++ b/eBPF_Supermarket/Stack_Analyser/src/dt_elf.cpp @@ -6,6 +6,8 @@ * License terms: GNU General Public License (GPL) version 3 * */ +#include "dt_elf.h" + #include #include #include @@ -16,8 +18,6 @@ #include #include -#include "elf.hpp" - #define NOTE_ALIGN(n) (((n) + 3) & -4U) struct sym_section_ctx diff --git a/eBPF_Supermarket/Stack_Analyser/src/symbol.cc b/eBPF_Supermarket/Stack_Analyser/src/dt_symbol.cpp similarity index 95% rename from eBPF_Supermarket/Stack_Analyser/src/symbol.cc rename to eBPF_Supermarket/Stack_Analyser/src/dt_symbol.cpp index 72b353dc8..92feb0d7b 100644 --- a/eBPF_Supermarket/Stack_Analyser/src/symbol.cc +++ b/eBPF_Supermarket/Stack_Analyser/src/dt_symbol.cpp @@ -12,8 +12,8 @@ #include #include -#include "symbol.h" -#include "elf.hpp" +#include "dt_symbol.h" +#include "dt_elf.h" void restore_global_env(); int attach_ns_env(int pid); @@ -274,6 +274,25 @@ bool symbol_parser::find_kernel_symbol(symbol &sym) return false; } +bool symbol_parser::complete_kernel_symbol(symbol &sym) +{ + load_kernel(); + sym.end = sym.start = 0; + for (auto it = kernel_symbols.begin(); it != kernel_symbols.end(); ++it) { + auto size = sym.name.size(), tsize = it->name.size(); + if(size > tsize || it->name.substr(tsize-5, 5) == ".cold") { + continue; + } + if(it->name.substr(0, size) == sym.name) { + sym.end = it->end; + sym.start = it->start; + sym.name = it->name; + return true; + } + } + return false; +} + bool symbol_parser::find_symbol_in_cache(int tgid, unsigned long addr, std::string &symbol) { std::map >::const_iterator it_pid = diff --git a/eBPF_Supermarket/Stack_Analyser/main.cpp b/eBPF_Supermarket/Stack_Analyser/src/main.cpp similarity index 88% rename from eBPF_Supermarket/Stack_Analyser/main.cpp rename to eBPF_Supermarket/Stack_Analyser/src/main.cpp index e529c5459..67f0df23d 100644 --- a/eBPF_Supermarket/Stack_Analyser/main.cpp +++ b/eBPF_Supermarket/Stack_Analyser/src/main.cpp @@ -1,12 +1,30 @@ +// Copyright 2024 The LMP Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/linuxkerneltravel/lmp/blob/develop/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: luiyanbing@foxmail.com +// +// 主函数,负责参数解析,管理被监控命令对应的进程,数据输出 + #include #include -#include "bpf/OnCPUStackCollector.h" -#include "bpf/OffCPUStackCollector.h" -#include "bpf/MemoryStackCollector.h" -#include "bpf/IOStackCollector.h" -#include "bpf/ReadaheadStackCollector.h" -#include "bpf/ProbeStackCollector.h" +#include "bpf/on_cpu.h" +#include "bpf/off_cpu.h" +#include "bpf/mem.h" +#include "bpf/io.h" +#include "bpf/readahead.h" +#include "bpf/probe.h" #include "sa_user.h" #include "clipp.h" @@ -64,7 +82,7 @@ int main(int argc, char *argv[]) { StackCollectorList.back()->min = IntTmp; })) % "set the min threshold of sampled value"); - auto OnCpuOption = (clipp::option("on-cpu").call([] + auto OnCpuOption = (clipp::option("on_cpu").call([] { StackCollectorList.push_back(new OnCPUStackCollector()); }) % "sample the call stacks of on-cpu processes") & (clipp::option("-F", "--frequency") & clipp::value("sampling frequency", IntTmp).call([] @@ -72,7 +90,7 @@ int main(int argc, char *argv[]) "sampling at a set frequency", SubOption); - auto OffCpuOption = clipp::option("off-cpu").call([] + auto OffCpuOption = clipp::option("off_cpu").call([] { StackCollectorList.push_back(new OffCPUStackCollector()); }) % "sample the call stacks of off-cpu processes" & SubOption; @@ -97,11 +115,11 @@ int main(int argc, char *argv[]) "set the statistic mod", SubOption); - auto ReadaheadOption = clipp::option("ra").call([] + auto ReadaheadOption = clipp::option("readahead").call([] { StackCollectorList.push_back(new ReadaheadStackCollector()); }) % "sample the readahead hit rate of call stacks" & SubOption; - auto StackCountOption = clipp::option("stackcount").call([] + auto StackCountOption = clipp::option("probe").call([] { StackCollectorList.push_back(new StackCountStackCollector()); }) % "sample the counts of calling stacks" & (clipp::option("-S", "--String") & clipp::value("probe String", StrTmp).call([]