diff --git a/MagicEyes/CMakeLists.txt b/MagicEyes/CMakeLists.txt index f02b0d95d..682302408 100644 --- a/MagicEyes/CMakeLists.txt +++ b/MagicEyes/CMakeLists.txt @@ -137,4 +137,5 @@ if (NOT BpfObject_FOUND) endif () add_subdirectory(src/backend) -add_subdirectory(src/bridge) \ No newline at end of file +add_subdirectory(src/bridge) +add_subdirectory(src/magic_eyes_cli) diff --git a/MagicEyes/src/magic_eyes_cli/CMakeLists.txt b/MagicEyes/src/magic_eyes_cli/CMakeLists.txt new file mode 100644 index 000000000..523f3dff0 --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/CMakeLists.txt @@ -0,0 +1,17 @@ +set(MAGIC_EYES_CLI_INSTALL_DIR magic_eyes_cli) + +# 安装 MagicEyesCli 到 /sbin/ + +# 安装magic_eyes_cli文件夹到 /sbin/ + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION ${MAGIC_EYES_CLI_INSTALL_DIR} +) + +install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/magic_eyes_cli + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE + DESTINATION ${MAGIC_EYES_CLI_INSTALL_DIR}) + +install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/before_running.sh +PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE +DESTINATION ${MAGIC_EYES_CLI_INSTALL_DIR}) diff --git a/MagicEyes/src/magic_eyes_cli/README.md b/MagicEyes/src/magic_eyes_cli/README.md new file mode 100644 index 000000000..61b5a0f3a --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/README.md @@ -0,0 +1,72 @@ +## magic_eyes_cli 命令行前端 + +### 1. 简述 + +将所有的后端工具统一到一个命令行前端,并且具备自动补全功能。 + +Tips:**记得Tab** + +### 2. 使用之前 + +```bash +mkdir build && cd build +cmake .. && make && make install +cd ./install/magic_eyes_cli +# 运行前置条件脚本 +source ./before_running.sh +``` + +### 3. 使用 + +```bash +(venv) $ ./magic_eyes_cli -h +/home/fzy/Downloads/04_bcc_ebpf/MagicEyes +usage: magic_eyes_cli [-h] [-l | -c] {net,memory,system_diagnosis,process} ... + +magic_eyes_cli: command tools for Linux kernel diagnosis and optimization + +positional arguments: + {net,memory,system_diagnosis,process} + net tool for Linux net subsystem + memory tool for Linux memory subsystem + system_diagnosis tool for Linux system_diagnosis subsystem + process tool for Linux process subsystem + +optional arguments: + -h, --help show this help message and exit + +all of common options: + -l list all avaliable tools + -c check all tools dependency, and whether it can be run in current platform + +eg: magic_eyes_cli -l +``` + +**固定命令** + +magic_eyes_cli具有2个固定命令, 即 + +```bash +-l : 即list, 列出所有可用的后端命令 +-c : 即check, 检查所有运行依赖项(暂未实现) +``` + +**动态命令** + +{net,memory,system_diagnosis,process}为动态命令,会根据backend文件夹下的情况动态调整。 + +### 4. 例程 + +```bash +magic_eyes_cli process cpu_watcher -h +# <------------------ 自动补全 | 非自动补全 +``` + +### 5.其他 + +```bash +# 生成requirements.txt +pip3 freeze > requirements.txt +# 安装 +pip3 install -r requiredments.txt +``` diff --git a/MagicEyes/src/magic_eyes_cli/backend_tool_modules/README.md b/MagicEyes/src/magic_eyes_cli/backend_tool_modules/README.md new file mode 100644 index 000000000..5cc378d9a --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/backend_tool_modules/README.md @@ -0,0 +1,7 @@ +## 后端工具描述脚本存放文件夹 + +1. 每个后端工具均有一个后端工具描述脚本 + 1. 描述后端工具名称 + 2. 描述后端工具依赖项,如内核配置选项,某些需要下载的依赖等 + 3. 原始文件请存放在各自工具文件夹的scripts中,在此文件夹创建软链接 + diff --git a/MagicEyes/src/magic_eyes_cli/backend_tool_modules/cpu_watcher.py b/MagicEyes/src/magic_eyes_cli/backend_tool_modules/cpu_watcher.py new file mode 100644 index 000000000..354fe9a73 --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/backend_tool_modules/cpu_watcher.py @@ -0,0 +1,19 @@ +""" +方案2: 采用后端工具描述文件,后续读取该文件,尤其是其中的依赖项, + 并使用 magic_eyes_cli check 进行运行环境检查 +描述: + 1. 工具名,所属子系统 + 2. 工具的简要描述 + 3. 是否具有运行依赖项,依赖项是什么? + +""" + +class CpuWatcher(): + def __init__(self): + self.tool_name = "cpu_watcher" + self.belong_to_subsystem = "process" + self.description = "A tool for analyzing CPU running status" + self.dependencies = { + """ 工具运行的依赖项 """ + } + diff --git a/MagicEyes/src/magic_eyes_cli/backend_tool_modules/net_watcher.py b/MagicEyes/src/magic_eyes_cli/backend_tool_modules/net_watcher.py new file mode 100644 index 000000000..e69de29bb diff --git a/MagicEyes/src/magic_eyes_cli/backend_tool_modules/proc_image.py b/MagicEyes/src/magic_eyes_cli/backend_tool_modules/proc_image.py new file mode 100644 index 000000000..e69de29bb diff --git a/MagicEyes/src/magic_eyes_cli/before_running.sh b/MagicEyes/src/magic_eyes_cli/before_running.sh new file mode 100755 index 000000000..22bf499b1 --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/before_running.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +MAGIC_EYES_CLI_INSTALL_DIR=$(dirname $(realpath $0)) + +check_conditions() { + # 判断python3是否存在 + if /usr/bin/env python3 -V > /dev/null; then + echo "python 已安装" + else + echo "python 未安装" + return 1 + fi + # 进入python venv环境 + if . ./venv/bin/activate; then + echo "成功进入venv环境" + else + echo "进入venv环境失败" + return 1 + fi + # 使用pip列出所有已安装的包,并搜索argcomplete,判断是否argcomplete是否存在 + if pip list --verbose | grep -q "argcomplete"; then + echo "argcomplete 已经安装." + else + echo "argcomplete 未安装." + echo "尝试安装argcomplete" + pip3 install -r ./requirements.txt + if pip list --verbose | grep -q "argcomplete"; then + echo "argcomplete安装完成" + else + return 1 + fi + fi + # 注册 magic_eyes_cli + if eval "$(register-python-argcomplete ./magic_eyes_cli)"; then + echo "magic_eyes_cli注册成功" + else + echo "magic_eyes_cli注册失败" + return 1 + fi + return 0 +} + +# 调用函数并获取返回值 +check_conditions +exit_status=$? +# 根据返回值输出最终结果 +if [ $exit_status -eq 0 ]; then + echo "OK" +else + echo "条件不满足" +fi + + + + + diff --git a/MagicEyes/src/magic_eyes_cli/core/__init__.py b/MagicEyes/src/magic_eyes_cli/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/MagicEyes/src/magic_eyes_cli/core/cli_arg_parser.py b/MagicEyes/src/magic_eyes_cli/core/cli_arg_parser.py new file mode 100644 index 000000000..f6aa6a76c --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/core/cli_arg_parser.py @@ -0,0 +1,103 @@ +""" + for argc parse +""" +import argparse +import argcomplete +import os +import sys +import subprocess +import multiprocessing +from core.filesystem import get_all_tools +from core.filesystem import print_all_tools + +class CliArgParser: + """ + class of commandline argc praser + """ + def __init__(self) -> None: + """Initialize the parser """ + self._arg_parser = argparse.ArgumentParser( + description=''' magic_eyes_cli: command tools for Linux kernel diagnosis and optimization ''', + add_help=True, + epilog='''eg: magic_eyes_cli -l''') + self._parsed_args = None + self._setup_args() + argcomplete.autocomplete(self._arg_parser) + + def parse_args(self, args): + """ Parse the given arguments and return them """ + self._parsed_args = self._arg_parser.parse_args(args[:2]) # 只解析到 net net_watcher + if self._parsed_args.list: + print_all_tools() + elif self._parsed_args.check: + print("will to do in future") + elif hasattr(self._parsed_args, 'func'): + self._parsed_args.func(args) + else: + self._arg_parser.print_help() + + def handle_args(self, args): + backend_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), + '..', '..', 'backend') + tool_path = os.path.join(backend_path, args[0], args[1], 'bin', args[1]) + if not os.path.exists(tool_path): + print(f"Error: Tool {args[1]} not found in {args[0]}") + # [tool] 后面的参数 + tool_args = args[2:] + if (len(tool_args) == 1) and (tool_args[0] == '-h' or tool_args[0] == '--help'): + cmd = [tool_path] + tool_args # -h 不需要超级权限 + else: + cmd = ['sudo'] + [tool_path] + tool_args + try: + subprocess.run(cmd, check=True) + except KeyboardInterrupt: + print("Operation was cancelled by the user.") + except subprocess.CalledProcessError as e: + print(f"Error: {e}") + sys.exit(1) + + def _setup_args(self): + # 通用参数部分,组内选项是互斥的 + common_opts_group = self._arg_parser.add_argument_group("all of common options") + comm_opts = common_opts_group.add_mutually_exclusive_group() + comm_opts.add_argument( + "-l", action='store_true', dest='list', + help=" list all avaliable tools ") + comm_opts.add_argument( + "-c", action='store_true', dest='check', + help="check all tools dependency, and whether it can be run in current platform" + ) + subparser = self._arg_parser.add_subparsers(dest='command') + # 获取subsystem以及下属的工具清单 + tools_lists = get_all_tools() + for subsystem, tools in tools_lists: + subsystem_parser = "subsystem_" + str({subsystem}) + subsystem_parser = subparser.add_parser( + f'{subsystem}', + help=f"tool for Linux {subsystem} subsystem" + ) + subtool_parser = subsystem_parser.add_subparsers(dest='tools') + for tool in tools: + tool_parser = "tool_" + str({tool}) + tool_parser = subtool_parser.add_parser( + f'{tool}', + add_help=True, + help=f"tool within {subsystem}" + ) + #tool_parser.add_argument( + # 'tool_args', + # nargs='*', + # help="tool all args" + #) + tool_parser.set_defaults(func=self.handle_args) + + def exit(self, status=os.EX_OK, message=None): + self._arg_parser.exit(status=status, message=message) + + +def cpu_num(): + try: + num = multiprocessing.cpu_count() + except BaseException: + num = 1 + return int(num) \ No newline at end of file diff --git a/MagicEyes/src/magic_eyes_cli/core/filesystem.py b/MagicEyes/src/magic_eyes_cli/core/filesystem.py new file mode 100644 index 000000000..2be18a34b --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/core/filesystem.py @@ -0,0 +1,99 @@ +"""part of file system handle like files, directories and paths """ + +import logging +import os +import shutil + + +LOGGER = logging.getLogger(__name__) + + +def delete_file_or_directory(path, ignore_errors=False, log_function=LOGGER.debug): + """ + Used as a universal delete function. Also deletes non-empty directories recursively. + + Args: + path (str): Path to the file or directory to be deleted + + Keyword Args: + ignore_errors (bool): Catches and ignores the exceptions if True + log_function (Callable): Log messages will be passed to this callable + + Returns: + bool: True if file or directory is removed + False if an exception occurred during deletion or the file/directory does not exist + """ + logger = log_function or (lambda *x: None) + result = False + + try: + if os.path.isfile(path): + logger("Deleting file %s", path) + os.remove(path) + result = True + elif os.path.isdir(path): + logger("Deleting directory %s", path) + shutil.rmtree(path) + result = True + else: + logger("The path %s does not exist", path) + except BaseException: + if not ignore_errors: + logger("Error during deletion of %s", path) + raise + + return result + + +def write_to_file(filepath, file_content): + """ + Writes file_content to the file in filepath. Creates directories for filepath if they do not already exist. + Overwrites the file in filepath if it already exists. + + Args: + filepath (str): Path to the file + file_content (str | list): If it is of str type, it is directly written to the file, + if it is of list type, each element is converted to str and written to the file separeted by a newline + """ + file_directory = os.path.dirname(filepath) + if file_directory and not os.path.isdir(file_directory): + os.makedirs(file_directory) + + with open(filepath, 'w') as file_: + if isinstance(file_content, list): + file_.write("\n".join(file_content)) + else: + file_.write(str(file_content)) + + +# 列出所有子系统以及子系统下属的所有工具 +def get_all_tools(): + backend_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), + '..', '..' ,'backend') + if not os.path.isdir(backend_path): + print("invalid path") + return [] + tools_lists = [] + # 遍历backend目录下的所有文件和文件夹 + for item in os.listdir(backend_path): + item_path = os.path.join(backend_path, item) + if os.path.isdir(item_path): + # 添加子系统 + tools_lists.append([item, []]) + # 添加子系统下的工具 + for sub_item in os.listdir(item_path): + sub_item_path = os.path.join(item_path, sub_item) + if os.path.isdir(sub_item_path): + tools_lists[-1][1].append(sub_item) + return tools_lists + + +def print_all_tools(): + print("list all avaliable tools:") + tools_lists = get_all_tools() + for subsystem, tools in tools_lists: + print(f"{' '.ljust(2)}[{subsystem}]") + for tool in tools: + print(f"{' '.ljust(8)}{tool}") + print() + diff --git a/MagicEyes/src/magic_eyes_cli/core/tool_module_index.py b/MagicEyes/src/magic_eyes_cli/core/tool_module_index.py new file mode 100644 index 000000000..a1b67d5d6 --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/core/tool_module_index.py @@ -0,0 +1,4 @@ +""" +方案2: 采用后端工具描述文件,后续读取该文件,尤其是其中的依赖项, + 并使用 magic_eyes_cli check 进行运行环境检查 +""" diff --git a/MagicEyes/src/magic_eyes_cli/magic_eyes_cli b/MagicEyes/src/magic_eyes_cli/magic_eyes_cli new file mode 100755 index 000000000..81c70d255 --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/magic_eyes_cli @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +""" +copyright(c) 2024 by lmp/MagicEyes, all rights reserved. +brief: + Entry point for MagicEyes commandline tools +""" +__author__ = "MagicEyes developer" +__version__ = " 0.0.1 " + +import os +import sys +# argcomplete需要Python 3.7+ +if sys.version_info < (3,7): + sys.exit('This program requires Python version 3.7 or newer!') + + +from core.cli_arg_parser import CliArgParser + + +def main(): + cli_arg_parser = CliArgParser() + cli_arg_parser.parse_args(sys.argv[1:]) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/MagicEyes/src/magic_eyes_cli/requirements.txt b/MagicEyes/src/magic_eyes_cli/requirements.txt new file mode 100644 index 000000000..827ae25bb --- /dev/null +++ b/MagicEyes/src/magic_eyes_cli/requirements.txt @@ -0,0 +1 @@ +argcomplete==3.2.3