diff --git a/CMakeLists.txt b/CMakeLists.txt index 60f4b73..ffcbb52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,319 +1,392 @@ -cmake_minimum_required(VERSION 3.15) - -# Version configuration -set(PROJECT_VERSION_MAJOR 0) -set(PROJECT_VERSION_MINOR 1) -set(PROJECT_VERSION_PATCH 0) -set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") - -project(cpp-remote-profiler VERSION ${PROJECT_VERSION}) - -message(STATUS "Project version: ${PROJECT_VERSION}") - -# ============================================================================ -# Build Options -# ============================================================================ - -option(BUILD_SHARED_LIBS "Build shared libraries (DLL)" ON) -option(REMOTE_PROFILER_INSTALL "Generate install target" ON) -option(REMOTE_PROFILER_BUILD_EXAMPLES "Build example programs" ON) -option(REMOTE_PROFILER_BUILD_TESTS "Build test programs" ON) -option(ENABLE_COVERAGE "Enable code coverage reporting" OFF) - -option(BUILD_DOCS "Build API documentation" OFF) - -message(STATUS "Build options:") -message(STATUS " BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") -message(STATUS " REMOTE_PROFILER_INSTALL: ${REMOTE_PROFILER_INSTALL}") -message(STATUS " REMOTE_PROFILER_BUILD_EXAMPLES: ${REMOTE_PROFILER_BUILD_EXAMPLES}") -message(STATUS " REMOTE_PROFILER_BUILD_TESTS: ${REMOTE_PROFILER_BUILD_TESTS}") -message(STATUS " ENABLE_COVERAGE: ${ENABLE_COVERAGE}") -message(STATUS " BUILD docs: ${BUILD_DOCS}") - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Set build type to RelWithDebInfo to include debug symbols -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE) -endif() - -# Add debug symbols for better profiling support -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O2") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g -O2") - -# Code coverage configuration -if(ENABLE_COVERAGE) - if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - message(WARNING "Code coverage only works with GCC or Clang") - endif() - message(STATUS "Code coverage enabled") - add_compile_options(--coverage -O0 -g) - add_link_options(--coverage) -endif() - -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -message(STATUS "CXX flags: ${CMAKE_CXX_FLAGS}") - -# Find dependencies via vcpkg -find_package(Drogon CONFIG REQUIRED) -find_package(GTest CONFIG REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(ZLIB REQUIRED) -find_package(Backward CONFIG REQUIRED) -find_package(absl REQUIRED) -find_package(spdlog CONFIG REQUIRED) - -# Find gperftools via pkg-config -find_package(PkgConfig REQUIRED) -pkg_check_modules(GPERFTOOLS REQUIRED libprofiler libtcmalloc) - -# Include directories -include_directories(${PROJECT_SOURCE_DIR}/include) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) -include_directories(${GPERFTOOLS_INCLUDE_DIRS}) - -# ============================================================================ -# Version Configuration -# ============================================================================ -# Generate profiler_version.h with version information and namespace macros -set(PROFILER_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) -set(PROFILER_VERSION_MINOR ${PROJECT_VERSION_MINOR}) -set(PROFILER_VERSION_PATCH ${PROJECT_VERSION_PATCH}) -set(PROFILER_VERSION_NAMESPACE v${PROJECT_VERSION_MAJOR}_${PROJECT_VERSION_MINOR}_${PROJECT_VERSION_PATCH}) - -configure_file( - ${PROJECT_SOURCE_DIR}/include/profiler_version.h.in - ${CMAKE_CURRENT_BINARY_DIR}/profiler_version.h - @ONLY -) - -message(STATUS "Profiler version: ${PROJECT_VERSION}") -message(STATUS "Profiler version namespace: ${PROFILER_VERSION_NAMESPACE}") - -# Source files -set(PROFILER_SOURCES - src/profiler_manager.cpp - src/symbolize.cpp - src/web_resources.cpp - src/web_server.cpp - src/internal/log_manager.cpp - src/internal/default_log_sink.cpp -) - -set(PROFILER_HEADERS - include/profiler_manager.h - include/web_server.h - include/profiler/log_sink.h - include/profiler/logger.h -) - -# Create library -add_library(profiler_lib ${PROFILER_SOURCES} ${PROFILER_HEADERS}) - -# Private link libraries (not exported) -target_link_libraries(profiler_lib - PRIVATE - Backward::Backward - absl::symbolize - absl::stacktrace - absl::debugging_internal - absl::demangle_internal - spdlog::spdlog - Drogon::Drogon - pthread - ${CMAKE_DL_LIBS} - PUBLIC - ${GPERFTOOLS_LIBRARIES} -) - -# Include directories -target_include_directories(profiler_lib - PUBLIC - $ - $ - PRIVATE - ${PROJECT_SOURCE_DIR}/src -) - -# Example executable -if(REMOTE_PROFILER_BUILD_EXAMPLES) - add_executable(profiler_example example/main.cpp example/workload.cpp) - target_link_libraries(profiler_example - profiler_lib - Drogon::Drogon - ) - message(STATUS "Example program will be built") -else() - message(STATUS "Example program disabled") -endif() - -# Tests -if(REMOTE_PROFILER_BUILD_TESTS) - enable_testing() - - # CPU profile 测试 - add_executable(test_cpu_profile tests/test_cpu_profile.cpp) - target_link_libraries(test_cpu_profile - profiler_lib - Drogon::Drogon - GTest::gtest - GTest::gtest_main - pthread - ) - - add_test(NAME CPUProfileTest COMMAND test_cpu_profile) - - # Full flow 测试 - add_executable(test_full_flow tests/test_full_flow.cpp) - target_link_libraries(test_full_flow - profiler_lib - Drogon::Drogon - GTest::gtest - GTest::gtest_main - pthread - ) - - add_test(NAME FullFlowTest COMMAND test_full_flow) - - # Logger 测试 - add_executable(test_logger tests/test_logger.cpp) - target_link_libraries(test_logger - profiler_lib - GTest::gtest - GTest::gtest_main - pthread - ) - - add_test(NAME LoggerTest COMMAND test_logger) - message(STATUS "Tests will be built") -else() - message(STATUS "Tests disabled") -endif() - -# Note: Web files are now embedded in C++ code (src/web_resources.cpp) -# No need to copy web directory to build directory - -# ============================================================================ -# Installation and Export -# ============================================================================ - -if(REMOTE_PROFILER_INSTALL) - # Install library and export - # Note: Only PUBLIC and INTERFACE link libraries will be exported - # PRIVATE dependencies (Backward, absl) will not be visible to consumers - install(TARGETS profiler_lib - EXPORT cpp-remote-profiler-targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - INCLUDES DESTINATION include/cpp-remote-profiler - ) - - # Install headers - install(FILES ${PROFILER_HEADERS} - DESTINATION include/cpp-remote-profiler - ) - - # Install profiler subdirectory headers - install(FILES - include/profiler/log_sink.h - include/profiler/logger.h - DESTINATION include/cpp-remote-profiler/profiler - ) - - # Install version header - install(FILES include/version.h - DESTINATION include/cpp-remote-profiler - ) - - # Install generated profiler_version.h - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/profiler_version.h - DESTINATION include/cpp-remote-profiler - ) - - # Export targets - install(EXPORT cpp-remote-profiler-targets - FILE cpp-remote-profiler-targets.cmake - NAMESPACE cpp-remote-profiler:: - DESTINATION lib/cmake/cpp-remote-profiler - ) - - # Create and install config files - include(CMakePackageConfigHelpers) - include(GNUInstallDirs) - - # Generate version file - write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion - ) - - # Configure package config file from template - configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpp-remote-profiler-config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config.cmake" - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-remote-profiler - PATH_VARS - CMAKE_INSTALL_INCLUDEDIR - CMAKE_INSTALL_LIBDIR - CMAKE_INSTALL_PREFIX - ) - - # Install config files - install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config-version.cmake" - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-remote-profiler - ) - - message(STATUS "Install targets will be generated") -else() - message(STATUS "Install targets disabled") -endif() - -# ============================================================================ -# Documentation -# ============================================================================ - -if(BUILD_DOCS) - find_package(Doxygen) - - if(DOxygen_FOUND) - # Check for dot (Graphviz) for diagrams - find_program(DOT_EXECUTABLE) - - if(DOT_EXECUTABLE) - set(DOXYGEN_HAVE_DOT "YES") - else() - set(DOXYGEN_HAVE_DOT "NO") - endif() - - # Configure Doxyfile - configure_file( - ${PROJECT_SOURCE_DIR}/docs/Doxyfile.in - ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile - @ONLY - ) - - # Add docs target - add_custom_target(docs ALL - COMMAND ${DOxygen_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation" - ) - - # Install docs (optional) - install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/html - DESTINATION share/doc/cpp-remote-profiler - ) - - message(STATUS "Documentation will be generated") - else() - message(WARNING "Doxygen not found - documentation will not be generated") - endif() -endif() - +cmake_minimum_required(VERSION 3.15) + +# Version configuration +set(PROJECT_VERSION_MAJOR 0) +set(PROJECT_VERSION_MINOR 1) +set(PROJECT_VERSION_PATCH 0) +set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + +project(cpp-remote-profiler VERSION ${PROJECT_VERSION}) + +message(STATUS "Project version: ${PROJECT_VERSION}") + +# ============================================================================ +# Build Options +# ============================================================================ + +option(BUILD_SHARED_LIBS "Build shared libraries (DLL)" ON) +option(REMOTE_PROFILER_INSTALL "Generate install target" ON) +option(REMOTE_PROFILER_BUILD_EXAMPLES "Build example programs" ON) +option(REMOTE_PROFILER_BUILD_TESTS "Build test programs" ON) +option(REMOTE_PROFILER_ENABLE_WEB "Enable web UI (requires Drogon)" ON) +option(ENABLE_COVERAGE "Enable code coverage reporting" OFF) + +option(BUILD_DOCS "Build API documentation" OFF) + +message(STATUS "Build options:") +message(STATUS " BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") +message(STATUS " REMOTE_PROFILER_INSTALL: ${REMOTE_PROFILER_INSTALL}") +message(STATUS " REMOTE_PROFILER_BUILD_EXAMPLES: ${REMOTE_PROFILER_BUILD_EXAMPLES}") +message(STATUS " REMOTE_PROFILER_BUILD_TESTS: ${REMOTE_PROFILER_BUILD_TESTS}") +message(STATUS " REMOTE_PROFILER_ENABLE_WEB: ${REMOTE_PROFILER_ENABLE_WEB}") +message(STATUS " ENABLE_COVERAGE: ${ENABLE_COVERAGE}") +message(STATUS " BUILD docs: ${BUILD_DOCS}") + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Set build type to RelWithDebInfo to include debug symbols +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE) +endif() + +# Add debug symbols for better profiling support +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O2") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g -O2") + +# Code coverage configuration +if(ENABLE_COVERAGE) + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + message(WARNING "Code coverage only works with GCC or Clang") + endif() + message(STATUS "Code coverage enabled") + add_compile_options(--coverage -O0 -g) + add_link_options(--coverage) +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "CXX flags: ${CMAKE_CXX_FLAGS}") + +# Find core dependencies via vcpkg / pkg-config +find_package(GTest CONFIG REQUIRED) +find_package(OpenSSL REQUIRED) +find_package(ZLIB REQUIRED) +find_package(Backward CONFIG REQUIRED) +find_package(absl REQUIRED) + +# Find gperftools via pkg-config +find_package(PkgConfig REQUIRED) +pkg_check_modules(GPERFTOOLS REQUIRED libprofiler libtcmalloc) + +# Find web dependencies (optional) +if(REMOTE_PROFILER_ENABLE_WEB) + find_package(Drogon CONFIG REQUIRED) +endif() + +# Include directories +include_directories(${PROJECT_SOURCE_DIR}/include) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${GPERFTOOLS_INCLUDE_DIRS}) + +# ============================================================================ +# Version Configuration +# ============================================================================ +# Generate profiler_version.h with version information and namespace macros +set(PROFILER_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(PROFILER_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(PROFILER_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(PROFILER_VERSION_NAMESPACE v${PROJECT_VERSION_MAJOR}_${PROJECT_VERSION_MINOR}_${PROJECT_VERSION_PATCH}) + +configure_file( + ${PROJECT_SOURCE_DIR}/include/profiler_version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/profiler_version.h + @ONLY +) + +message(STATUS "Profiler version: ${PROJECT_VERSION}") +message(STATUS "Profiler version namespace: ${PROFILER_VERSION_NAMESPACE}") + +# ============================================================================ +# Core library (always built, no Drogon/spdlog dependency) +# ============================================================================ + +set(PROFILER_CORE_SOURCES + src/profiler_manager.cpp + src/symbolize.cpp + src/http_handlers.cpp + src/internal/log_manager.cpp + src/internal/default_log_sink.cpp +) + +set(PROFILER_CORE_HEADERS + include/profiler_manager.h + include/profiler/log_sink.h + include/profiler/logger.h + include/profiler/http_handlers.h +) + +add_library(profiler_core ${PROFILER_CORE_SOURCES} ${PROFILER_CORE_HEADERS}) + +target_link_libraries(profiler_core + PRIVATE + Backward::Backward + absl::symbolize + absl::stacktrace + absl::debugging_internal + absl::demangle_internal + pthread + ${CMAKE_DL_LIBS} + PUBLIC + ${GPERFTOOLS_LIBRARIES} +) + +target_include_directories(profiler_core + PUBLIC + $ + $ + PRIVATE + ${PROJECT_SOURCE_DIR}/src +) + +# Note: Symbol visibility can be controlled with CXX_VISIBILITY_PRESET hidden +# once proper export macros are added to the public API. +# For now, default visibility is used. + +# ============================================================================ +# Web library (optional, requires Drogon) +# ============================================================================ + +if(REMOTE_PROFILER_ENABLE_WEB) + set(PROFILER_WEB_SOURCES + src/drogon_adapter.cpp + src/web_resources.cpp + ) + + set(PROFILER_WEB_HEADERS + include/profiler/drogon_adapter.h + ) + + add_library(profiler_web ${PROFILER_WEB_SOURCES} ${PROFILER_WEB_HEADERS}) + + target_link_libraries(profiler_web + PUBLIC + profiler_core + PRIVATE + Drogon::Drogon + pthread + ) + + target_include_directories(profiler_web + PUBLIC + $ + PRIVATE + ${PROJECT_SOURCE_DIR}/src + ) + + message(STATUS "Web UI enabled (profiler_web target)") +else() + message(STATUS "Web UI disabled") +endif() + +# ============================================================================ +# Example executable +# ============================================================================ + +if(REMOTE_PROFILER_BUILD_EXAMPLES) + add_executable(profiler_example example/main.cpp example/workload.cpp) + if(REMOTE_PROFILER_ENABLE_WEB) + target_link_libraries(profiler_example + profiler_web + Drogon::Drogon + ) + target_compile_definitions(profiler_example PRIVATE REMOTE_PROFILER_ENABLE_WEB) + else() + target_link_libraries(profiler_example profiler_core) + endif() + message(STATUS "Example program will be built") +else() + message(STATUS "Example program disabled") +endif() + +# ============================================================================ +# Tests +# ============================================================================ + +if(REMOTE_PROFILER_BUILD_TESTS) + enable_testing() + + # CPU profile test + add_executable(test_cpu_profile tests/test_cpu_profile.cpp) + target_link_libraries(test_cpu_profile + profiler_core + GTest::gtest + GTest::gtest_main + pthread + ) + add_test(NAME CPUProfileTest COMMAND test_cpu_profile) + + # Full flow test + add_executable(test_full_flow tests/test_full_flow.cpp) + if(REMOTE_PROFILER_ENABLE_WEB) + target_link_libraries(test_full_flow + profiler_web + Drogon::Drogon + GTest::gtest + GTest::gtest_main + pthread + ) + else() + target_link_libraries(test_full_flow + profiler_core + GTest::gtest + GTest::gtest_main + pthread + ) + endif() + add_test(NAME FullFlowTest COMMAND test_full_flow) + + # Logger test + add_executable(test_logger tests/test_logger.cpp) + target_link_libraries(test_logger + profiler_core + GTest::gtest + GTest::gtest_main + pthread + ) + add_test(NAME LoggerTest COMMAND test_logger) + message(STATUS "Tests will be built") +else() + message(STATUS "Tests disabled") +endif() + +# Note: Web files are now embedded in C++ code (src/web_resources.cpp) +# No need to copy web directory to build directory + +# ============================================================================ +# Installation and Export +# ============================================================================ + +if(REMOTE_PROFILER_INSTALL) + # Install core library + install(TARGETS profiler_core + EXPORT cpp-remote-profiler-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION include/cpp-remote-profiler + ) + + # Install web library (if built) + if(REMOTE_PROFILER_ENABLE_WEB) + install(TARGETS profiler_web + EXPORT cpp-remote-profiler-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + endif() + + # Install core headers + install(FILES ${PROFILER_CORE_HEADERS} + DESTINATION include/cpp-remote-profiler + ) + + # Install profiler subdirectory headers + install(FILES + include/profiler/log_sink.h + include/profiler/logger.h + include/profiler/http_handlers.h + DESTINATION include/cpp-remote-profiler/profiler + ) + + # Install web header (if built) + if(REMOTE_PROFILER_ENABLE_WEB) + install(FILES include/profiler/drogon_adapter.h + DESTINATION include/cpp-remote-profiler/profiler + ) + endif() + + # Install version header + install(FILES include/version.h + DESTINATION include/cpp-remote-profiler + ) + + # Install generated profiler_version.h + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/profiler_version.h + DESTINATION include/cpp-remote-profiler + ) + + # Export targets + install(EXPORT cpp-remote-profiler-targets + FILE cpp-remote-profiler-targets.cmake + NAMESPACE cpp-remote-profiler:: + DESTINATION lib/cmake/cpp-remote-profiler + ) + + # Create and install config files + include(CMakePackageConfigHelpers) + include(GNUInstallDirs) + + # Generate version file + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config-version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion + ) + + # Configure package config file from template + configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpp-remote-profiler-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-remote-profiler + PATH_VARS + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_LIBDIR + CMAKE_INSTALL_PREFIX + ) + + # Install config files + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cpp-remote-profiler-config-version.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-remote-profiler + ) + + message(STATUS "Install targets will be generated") +else() + message(STATUS "Install targets disabled") +endif() + +# ============================================================================ +# Documentation +# ============================================================================ + +if(BUILD_DOCS) + find_package(Doxygen) + + if(DOxygen_FOUND) + # Check for dot (Graphviz) for diagrams + find_program(DOT_EXECUTABLE) + + if(DOT_EXECUTABLE) + set(DOXYGEN_HAVE_DOT "YES") + else() + set(DOXYGEN_HAVE_DOT "NO") + endif() + + # Configure Doxyfile + configure_file( + ${PROJECT_SOURCE_DIR}/docs/Doxyfile.in + ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile + @ONLY + ) + + # Add docs target + add_custom_target(docs ALL + COMMAND ${DOxygen_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation" + ) + + # Install docs (optional) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/html + DESTINATION share/doc/cpp-remote-profiler + ) + + message(STATUS "Documentation will be generated") + else() + message(WARNING "Doxygen not found - documentation will not be generated") + endif() +endif() diff --git a/docs/user_guide/01_quick_start.md b/docs/user_guide/01_quick_start.md index 49ba01b..704c542 100644 --- a/docs/user_guide/01_quick_start.md +++ b/docs/user_guide/01_quick_start.md @@ -43,20 +43,14 @@ if [ ! -d "vcpkg" ]; then cd .. fi -# 4. 安装 vcpkg 依赖 -cd vcpkg -./vcpkg install --triplet=x64-linux-release -cd .. - -# 5. 编译库 +# 4. 编译库 mkdir build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake \ -DVCPKG_TARGET_TRIPLET=x64-linux-release -# 6. 安装到系统(可选) -sudo make install +make -j$(nproc) ``` ### 方法 2: 直接集成源码 @@ -82,7 +76,8 @@ void doSomeWork() { } int main() { - auto& profiler = profiler::ProfilerManager::getInstance(); + // 创建 ProfilerManager 实例(非单例模式) + profiler::ProfilerManager profiler; std::cout << "开始 CPU profiling..." << std::endl; @@ -108,8 +103,6 @@ int main() { ### CMakeLists.txt 配置 -在你的项目根目录创建 `CMakeLists.txt`: - ```cmake cmake_minimum_required(VERSION 3.15) project(MyProfilerApp VERSION 1.0.0) @@ -117,37 +110,19 @@ project(MyProfilerApp VERSION 1.0.0) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# 方法 1: 如果已安装到系统 -find_package(PkgConfig REQUIRED) -pkg_check_modules(GPERFTOOLS REQUIRED libprofiler libtcmalloc) +# 链接 profiler 核心库(不需要 Drogon) +find_package(cpp-remote-profiler REQUIRED) add_executable(my_app my_profiler_app.cpp) - -# 链接 profiler 库 -target_link_libraries(my_app - profiler_lib # C++ Remote Profiler 库 - ${GPERFTOOLS_LIBRARIES} # gperftools - pthread -) - -# 方法 2: 如果使用源码直接编译 -# add_subdirectory(path/to/cpp-remote-profiler) -# target_link_libraries(my_app profiler_lib) +target_link_libraries(my_app cpp-remote-profiler::profiler_core) ``` ### 编译命令 ```bash -# 如果使用 vcpkg mkdir build && cd build -cmake .. \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=../cpp-remote-profiler/vcpkg/scripts/buildsystems/vcpkg.cmake \ - -DVCPKG_TARGET_TRIPLET=x64-linux-release - +cmake .. -DCMAKE_BUILD_TYPE=Release make - -# 运行 ./my_app ``` @@ -164,42 +139,20 @@ make #### 使用 Go pprof (推荐) ```bash -# 安装 Go (如果还没有) -wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz -sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz -export PATH=$PATH:/usr/local/go/bin - # 使用 pprof 分析 go tool pprof -http=:8080 my_profile.prof ``` -然后在浏览器中打开 `http://localhost:8080` - -#### 使用 FlameGraph - -```bash -# 1. 安装 FlameGraph 工具 -git clone https://github.com/brendangregg/FlameGraph /tmp/FlameGraph - -# 2. 使用 pprof 转换 profile 为 collapsed 格式 -pprof -raw my_profile.app > /tmp/profile.raw - -# 3. 生成火焰图 -perl /tmp/FlameGraph/flamegraph.pl /tmp/profile.raw > flamegraph.svg - -# 4. 在浏览器中查看 -firefox flamegraph.svg -``` - ## 一键生成火焰图 (API 方式) -如果你想直接在代码中生成火焰图,可以使用 `analyzeCPUProfile()`: +如果你想直接在代码中生成火焰图: ```cpp #include "profiler_manager.h" +#include int main() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; // 采样 10 秒并生成火焰图 std::string svg = profiler.analyzeCPUProfile(10, "flamegraph"); @@ -209,40 +162,65 @@ int main() { out << svg; out.close(); - std::cout << "火焰图已生成: flamegraph.svg" << std::endl; - return 0; } ``` ## 带有 Web 界面的完整示例 -如果你想要一个完整的 Web 界面来查看 profiling 结果: +如果你使用 Drogon 框架,可以使用一键注册函数: ```cpp -#include #include "profiler_manager.h" -#include "web_server.h" +#include "profiler/drogon_adapter.h" +#include int main() { - // 启动 Drogon 服务器 - profiler::ProfilerManager& profiler = profiler::ProfilerManager::getInstance(); - - // 注册所有 profiling 相关的 HTTP 端点 - profiler::registerHttpHandlers(profiler); + profiler::ProfilerManager profiler; - // 监听 8080 端口 - drogon::app().addListener("0.0.0.0", 8080); - std::cout << "Profiler Web UI: http://localhost:8080" << std::endl; + // 注册所有 profiling 相关的 HTTP 端点到 Drogon + profiler::registerDrogonHandlers(profiler); // 启动服务器 - drogon::app().run(); + drogon::app().addListener("0.0.0.0", 8080).run(); return 0; } ``` -访问 `http://localhost:8080` 即可看到 Web 界面,点击按钮即可生成火焰图。 +**CMake 配置**: +```cmake +target_link_libraries(my_app + cpp-remote-profiler::profiler_web + Drogon::Drogon +) +``` + +## 使用其他 Web 框架 + +如果你使用的是 Drogon 以外的 Web 框架,可以使用 `ProfilerHttpHandlers`: + +```cpp +#include "profiler_manager.h" +#include "profiler/http_handlers.h" + +int main() { + profiler::ProfilerManager profiler; + profiler::ProfilerHttpHandlers handlers(profiler); + + // 调用任意 handler,获得框架无关的响应 + profiler::HandlerResponse resp = handlers.handleCpuAnalyze(10, "flamegraph"); + + // resp.status, resp.content_type, resp.body + // 用你自己的 Web 框架包装这些数据 +} +``` + +**CMake 配置**: +```cmake +target_link_libraries(my_app cpp-remote-profiler::profiler_core) +# 不需要 Drogon +``` ## Heap Profiling 示例 @@ -256,15 +234,13 @@ int main() { // 设置环境变量 (在程序启动前) setenv("TCMALLOC_SAMPLE_PARAMETER", "524288", 1); - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; - // 启动 heap profiler profiler.startHeapProfiler("heap.prof"); // 分配一些内存 int* data = new int[1000]; - // 停止 heap profiler profiler.stopHeapProfiler(); delete[] data; @@ -286,9 +262,9 @@ export TCMALLOC_SAMPLE_PARAMETER=524288 ## 下一步 -- 📖 阅读 [API 参考手册](02_api_reference.md) 了解所有可用的 API -- 💡 查看 [集成示例](03_integration_examples.md) 学习更多使用场景 -- 🔧 遇到问题?查看 [故障排除指南](04_troubleshooting.md) +- 阅读 [API 参考手册](02_api_reference.md) 了解所有可用的 API +- 查看 [集成示例](03_integration_examples.md) 学习更多使用场景 +- 遇到问题?查看 [故障排除指南](04_troubleshooting.md) ## 常见问题 @@ -307,8 +283,5 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") ### Q: Heap profiling 不工作 **A**: 确保设置了 `TCMALLOC_SAMPLE_PARAMETER` 环境变量,并且链接了 tcmalloc 库。 -### Q: 性能开销太大 -**A**: CPU profiling 通常有 1-5% 的性能开销。如果开销过大,可以: -- 降低采样频率 -- 仅在需要时启用 profiling -- 使用更长的采样间隔 +### Q: 如何在非 Drogon 的 Web 框架中使用? +**A**: 使用 `ProfilerHttpHandlers` 类。每个 handler 方法返回 `HandlerResponse` 结构体,你只需将其包装到你框架的 response 对象中。详见 [场景 3: 与任意 Web 框架集成](03_integration_examples.md#场景-3-与任意-web-框架集成)。 diff --git a/docs/user_guide/02_api_reference.md b/docs/user_guide/02_api_reference.md index 252f204..8309f36 100644 --- a/docs/user_guide/02_api_reference.md +++ b/docs/user_guide/02_api_reference.md @@ -4,6 +4,8 @@ ## 目录 - [ProfilerManager](#profilermanager) +- [ProfilerHttpHandlers](#profilerhttphandlers) +- [HandlerResponse](#handlerresponse) - [类型定义](#类型定义) - [日志系统 API](#日志系统-api) - [CPU Profiling API](#cpu-profiling-api) @@ -17,19 +19,100 @@ ## ProfilerManager -`ProfilerManager` 是 C++ Remote Profiler 的核心类,采用单例模式设计。 +`ProfilerManager` 是 C++ Remote Profiler 的核心类。 -### 获取实例 +### 构造函数 ```cpp -static ProfilerManager& getInstance(); +ProfilerManager(); ``` -**返回值**: ProfilerManager 的唯一实例引用 +**说明**: 创建一个新的 ProfilerManager 实例。不再使用单例模式,用户可以自由管理实例生命周期。 **示例**: ```cpp -auto& profiler = profiler::ProfilerManager::getInstance(); +profiler::ProfilerManager profiler; +``` + +### 析构函数 + +```cpp +~ProfilerManager(); +``` + +**说明**: 析构时自动清理资源,包括停止正在运行的 profiler 和恢复信号处理器。 + +--- + +## ProfilerHttpHandlers + +`ProfilerHttpHandlers` 提供框架无关的 HTTP 端点处理器。每个处理器返回 `HandlerResponse` 结构体,用户可以用任意 Web 框架包装响应。 + +### 构造函数 + +```cpp +explicit ProfilerHttpHandlers(ProfilerManager& profiler); +``` + +**参数**: `profiler` - ProfilerManager 实例引用 + +### 端点处理器方法 + +| 方法 | 签名 | 说明 | +|------|------|------| +| `handleStatus` | `HandlerResponse handleStatus()` | 返回所有 profiler 状态 (JSON) | +| `handleCpuAnalyze` | `HandlerResponse handleCpuAnalyze(int duration, const std::string& output_type)` | CPU 分析,返回 SVG | +| `handleCpuSvgRaw` | `HandlerResponse handleCpuSvgRaw(int duration)` | CPU 原始 SVG (pprof 生成) | +| `handleCpuFlamegraphRaw` | `HandlerResponse handleCpuFlamegraphRaw(int duration)` | CPU FlameGraph SVG | +| `handleHeapAnalyze` | `HandlerResponse handleHeapAnalyze(const std::string& output_type)` | Heap 分析,返回 SVG | +| `handleHeapSvgRaw` | `HandlerResponse handleHeapSvgRaw()` | Heap 原始 SVG | +| `handleHeapFlamegraphRaw` | `HandlerResponse handleHeapFlamegraphRaw()` | Heap FlameGraph SVG | +| `handleGrowthAnalyze` | `HandlerResponse handleGrowthAnalyze(const std::string& output_type)` | Growth 分析,返回 SVG | +| `handleGrowthSvgRaw` | `HandlerResponse handleGrowthSvgRaw()` | Growth 原始 SVG | +| `handleGrowthFlamegraphRaw` | `HandlerResponse handleGrowthFlamegraphRaw()` | Growth FlameGraph SVG | +| `handlePprofProfile` | `HandlerResponse handlePprofProfile(int seconds)` | 标准 pprof CPU profile (二进制) | +| `handlePprofHeap` | `HandlerResponse handlePprofHeap()` | 标准 pprof heap profile | +| `handlePprofGrowth` | `HandlerResponse handlePprofGrowth()` | 标准 pprof growth profile | +| `handlePprofSymbol` | `HandlerResponse handlePprofSymbol(const std::string& body)` | 符号化接口 (POST) | +| `handleThreadStacks` | `HandlerResponse handleThreadStacks()` | 线程调用栈 | + +### 使用示例 + +```cpp +#include "profiler/http_handlers.h" +#include "profiler_manager.h" + +profiler::ProfilerManager profiler; +profiler::ProfilerHttpHandlers handlers(profiler); + +// 调用任意 handler +profiler::HandlerResponse resp = handlers.handleCpuAnalyze(10, "flamegraph"); + +// resp.status, resp.content_type, resp.body, resp.headers +// 用你自己的 Web 框架包装这些数据 +``` + +--- + +## HandlerResponse + +框架无关的 HTTP 响应结构体。 + +```cpp +struct HandlerResponse { + int status = 200; + std::string content_type = "text/plain"; + std::string body; + std::map headers; + + // 便捷工厂方法 + static HandlerResponse html(const std::string& content); + static HandlerResponse json(const std::string& content); + static HandlerResponse svg(const std::string& content); + static HandlerResponse text(const std::string& content); + static HandlerResponse binary(const std::string& data, const std::string& filename); + static HandlerResponse error(int status, const std::string& message); +}; ``` --- @@ -61,19 +144,6 @@ struct ProfilerState { }; ``` -### ThreadStackTrace - -线程堆栈跟踪结构(用于内部实现)。 - -```cpp -struct ThreadStackTrace { - pid_t tid; // 线程 ID - void* addresses[64]; // 调用栈地址数组 - int depth; // 堆栈深度 - bool captured; // 是否成功捕获 -}; -``` - --- ## 日志系统 API @@ -106,36 +176,29 @@ class LogSink { public: virtual ~LogSink() = default; - /// @brief 写入日志消息 - /// @param level 日志级别 - /// @param file 源文件名(可能为 nullptr) - /// @param line 源行号 - /// @param function 函数名(可能为 nullptr) - /// @param message 格式化后的日志消息 virtual void log(LogLevel level, const char* file, int line, const char* function, const char* message) = 0; - /// @brief 刷新缓冲区(可选实现) virtual void flush() {} }; ``` **说明**: -- 实现自定义 sink 后,通过 `setSink()` 注入到 profiler +- 实现自定义 sink 后,通过 `profiler.setLogSink()` 注入 - 设置自定义 sink 后,默认的 stderr 输出将被替换 - 所有参数的生命周期仅在 `log()` 调用期间有效,如需保留请复制 --- -### setSink +### setLogSink -设置自定义日志 sink。 +设置自定义日志 sink(ProfilerManager 实例方法)。 ```cpp -void setSink(std::shared_ptr sink); +void setLogSink(std::shared_ptr sink); ``` **参数**: @@ -143,140 +206,40 @@ void setSink(std::shared_ptr sink); **说明**: - 设置后,profiler 的所有日志将输出到自定义 sink -- 默认 sink 输出到 stderr(使用 spdlog) +- 默认 sink 输出到 stderr(使用 `std::cerr`) - 设置 `nullptr` 可恢复默认行为 **示例**: ```cpp #include -#include -// 自定义 sink:集成到应用日志系统 class MyAppLogSink : public profiler::LogSink { public: void log(profiler::LogLevel level, const char* file, int line, const char* function, const char* message) override { // 转发到应用的日志系统 - switch (level) { - case profiler::LogLevel::Error: - MY_APP_ERROR("[Profiler] {}:{} - {}", file, line, message); - break; - case profiler::LogLevel::Warning: - MY_APP_WARN("[Profiler] {}:{} - {}", file, line, message); - break; - default: - MY_APP_INFO("[Profiler] {}:{} - {}", file, line, message); - break; - } + MY_APP_LOG("[Profiler] {}:{} - {}", file, line, message); } }; -// 设置自定义 sink -profiler::setSink(std::make_shared()); - -// 恢复默认 sink -profiler::setSink(nullptr); +profiler::ProfilerManager profiler; +profiler.setLogSink(std::make_shared()); ``` --- ### setLogLevel -设置最小日志级别。 +设置最小日志级别(ProfilerManager 实例方法)。 ```cpp void setLogLevel(LogLevel level); ``` -**参数**: -- `level`: 最小日志级别,低于此级别的消息将被过滤 - -**说明**: -- 默认级别为 `LogLevel::Info` -- 日志级别从低到高:Trace < Debug < Info < Warning < Error < Fatal - **示例**: ```cpp -#include - -// 启用调试日志 -profiler::setLogLevel(profiler::LogLevel::Debug); - -// 只显示错误和致命错误 -profiler::setLogLevel(profiler::LogLevel::Error); - -// 显示所有日志(包括 Trace) -profiler::setLogLevel(profiler::LogLevel::Trace); -``` - ---- - -### 完整示例:集成到 spdlog - -```cpp -#include -#include -#include - -class SpdlogSink : public profiler::LogSink { -public: - void log(profiler::LogLevel level, const char* file, int line, - const char* function, const char* message) override { - // 映射日志级别 - spdlog::level::level_enum spdlog_level; - switch (level) { - case profiler::LogLevel::Trace: spdlog_level = spdlog::level::trace; break; - case profiler::LogLevel::Debug: spdlog_level = spdlog::level::debug; break; - case profiler::LogLevel::Info: spdlog_level = spdlog::level::info; break; - case profiler::LogLevel::Warning: spdlog_level = spdlog::level::warn; break; - case profiler::LogLevel::Error: spdlog_level = spdlog::level::err; break; - case profiler::LogLevel::Fatal: spdlog_level = spdlog::level::critical; break; - } - - // 输出到 spdlog - spdlog::log(spdlog::level::info, "[Profiler] {}:{} - {}", file, line, message); - } - - void flush() override { - spdlog::default_logger()->flush(); - } -}; - -int main() { - // 配置 spdlog - spdlog::set_level(spdlog::level::debug); - spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v"); - - // 集成 profiler 日志到 spdlog - profiler::setSink(std::make_shared()); - profiler::setLogLevel(profiler::LogLevel::Debug); - - // 使用 profiler... -} -``` - ---- - -### 完整示例:完全禁用日志 - -```cpp -#include -#include - -// 空 sink:丢弃所有日志 -class NullSink : public profiler::LogSink { -public: - void log(profiler::LogLevel, const char*, int, - const char*, const char*) override { - // 什么都不做 - } -}; - -// 方法 1:使用空 sink -profiler::setSink(std::make_shared()); - -// 方法 2:设置日志级别为 Fatal(只显示致命错误) -profiler::setLogLevel(profiler::LogLevel::Fatal); +profiler::ProfilerManager profiler; +profiler.setLogLevel(profiler::LogLevel::Debug); ``` --- @@ -298,19 +261,12 @@ bool startCPUProfiler(const std::string& output_path = "cpu.prof"); - `true`: 启动成功 - `false`: 启动失败(例如:profiler 已在运行) -**说明**: -- 使用 gperftools 的 `ProfilerStart()` 启动 CPU profiling -- 同一时间只能有一个 CPU profiler 在运行 -- 采样频率由 gperftools 控制(通常 100 Hz,即每 10ms 采样一次) - **示例**: ```cpp -auto& profiler = profiler::ProfilerManager::getInstance(); +profiler::ProfilerManager profiler; if (profiler.startCPUProfiler("/tmp/my_profile.prof")) { std::cout << "CPU profiler 启动成功" << std::endl; -} else { - std::cerr << "CPU profiler 启动失败" << std::endl; } ``` @@ -326,15 +282,7 @@ bool stopCPUProfiler(); **返回值**: - `true`: 停止成功 -- `false`: 停止失败(例如:profiler 未运行) - -**示例**: -```cpp -profiler.startCPUProfiler(); -// ... 运行需要分析的代码 ... -profiler.stopCPUProfiler(); -std::cout << "Profiling 完成" << std::endl; -``` +- `false`: 停止失败 --- @@ -348,25 +296,11 @@ std::string analyzeCPUProfile(int duration, const std::string& output_type = "fl **参数**: - `duration`: 采样时长(秒) -- `output_type`: 输出类型(默认: "flamegraph") +- `output_type`: 输出类型("flamegraph" 或 "pprof") **返回值**: SVG 字符串 -**说明**: -- 这是一个便捷方法,自动完成:启动 → 采样 → 停止 → 生成 SVG -- 内部使用 FlameGraph 工具生成火焰图 -- 包含符号化处理,显示函数名而非地址 - -**示例**: -```cpp -// 采样 10 秒并生成火焰图 -std::string svg = profiler.analyzeCPUProfile(10, "flamegraph"); - -// 保存到文件 -std::ofstream out("cpu_flamegraph.svg"); -out << svg; -out.close(); -``` +**说明**: 便捷方法,自动完成:启动 → 采样 → 停止 → 生成 SVG --- @@ -378,28 +312,7 @@ out.close(); std::string getRawCPUProfile(int seconds); ``` -**参数**: -- `seconds`: 采样时长(秒) - -**返回值**: 原始 profile 二进制数据(gperftools 格式) - -**说明**: -- 返回的数据兼容 Go pprof 工具 -- 可用于保存到文件后用 pprof 分析 - -**示例**: -```cpp -// 采样 10 秒 -std::string raw_data = profiler.getRawCPUProfile(10); - -// 保存到文件 -std::ofstream out("cpu.prof", std::ios::binary); -out.write(raw_data.data(), raw_data.size()); -out.close(); - -// 然后可以用 pprof 分析 -// go tool pprof -http=:8080 cpu.prof -``` +**返回值**: 原始 profile 二进制数据(gperftools 格式,兼容 Go pprof) --- @@ -413,29 +326,7 @@ out.close(); bool startHeapProfiler(const std::string& output_path = "heap.prof"); ``` -**参数**: -- `output_path`: profile 文件输出路径(默认: "heap.prof") - -**返回值**: -- `true`: 启动成功 -- `false`: 启动失败 - -**重要**: -- 需要设置 `TCMALLOC_SAMPLE_PARAMETER` 环境变量 -- 必须链接 tcmalloc 库 - -**示例**: -```cpp -// 在程序启动时设置环境变量 -setenv("TCMALLOC_SAMPLE_PARAMETER", "524288", 1); - -auto& profiler = profiler::ProfilerManager::getInstance(); -profiler.startHeapProfiler("/tmp/heap.prof"); - -// ... 分配内存 ... - -profiler.stopHeapProfiler(); -``` +**重要**: 需要设置 `TCMALLOC_SAMPLE_PARAMETER` 环境变量,必须链接 tcmalloc 库。 --- @@ -447,10 +338,6 @@ profiler.stopHeapProfiler(); bool stopHeapProfiler(); ``` -**返回值**: -- `true`: 停止成功 -- `false`: 停止失败 - --- ### analyzeHeapProfile @@ -461,24 +348,6 @@ bool stopHeapProfiler(); std::string analyzeHeapProfile(int duration, const std::string& output_type = "flamegraph"); ``` -**参数**: -- `duration`: 采样时长(秒) -- `output_type`: 输出类型(默认: "flamegraph") - -**返回值**: SVG 字符串 - -**说明**: -- 使用 tcmalloc 的 heap sampling 功能 -- 显示内存分配热点 - -**示例**: -```cpp -std::string svg = profiler.analyzeHeapProfile(10); -std::ofstream out("heap_flamegraph.svg"); -out << svg; -out.close(); -``` - --- ### getRawHeapSample @@ -491,16 +360,6 @@ std::string getRawHeapSample(); **返回值**: heap 采样文本数据(pprof 兼容格式) -**说明**: -- 使用 `MallocExtension::GetHeapSample()` 获取当前 heap 状态 -- 返回文本格式,可直接用 pprof 分析 - -**示例**: -```cpp -std::string heap_data = profiler.getRawHeapSample(); -std::cout << heap_data << std::endl; -``` - --- ### getRawHeapGrowthStacks @@ -511,74 +370,21 @@ std::cout << heap_data << std::endl; std::string getRawHeapGrowthStacks(); ``` -**返回值**: heap growth stacks 文本数据(pprof 兼容格式) - -**说明**: -- 使用 `MallocExtension::GetHeapGrowthStacks()` API -- 不需要 `TCMALLOC_SAMPLE_PARAMETER` 环境变量 -- 即时获取,无需采样时长 - -**示例**: -```cpp -std::string growth_data = profiler.getRawHeapGrowthStacks(); -std::cout << "Heap growth stacks:\n" << growth_data << std::endl; -``` +**说明**: 不需要 `TCMALLOC_SAMPLE_PARAMETER` 环境变量,即时获取。 --- ## 线程堆栈 API -### getThreadStacks - -获取所有线程的调用堆栈。 - -```cpp -std::string getThreadStacks(); -``` - -**返回值**: 线程堆栈文本数据 - -**说明**: -- 使用信号处理器安全地捕获所有线程的堆栈 -- 自动符号化,显示函数名 -- 包含线程 ID 和堆栈深度信息 - -**示例**: -```cpp -std::string stacks = profiler.getThreadStacks(); -std::cout << "所有线程堆栈:\n" << stacks << std::endl; -``` - -**输出格式**: -``` -Thread 12345 (depth: 5): - #0 main - #1 workerThread - #2 processTask - #3 compute - #4 calculate - -Thread 12346 (depth: 3): - #0 main - #1 ioThread - #2 waitForData -``` - ---- - ### getThreadCallStacks -获取完整线程调用堆栈(使用 backward-cpp)。 +获取完整线程调用堆栈。 ```cpp std::string getThreadCallStacks(); ``` -**返回值**: 格式化的线程堆栈字符串 - -**说明**: -- 使用 backward-cpp 库进行详细符号化 -- 包含文件名和行号(如果有调试符号) +**说明**: 使用 backward-cpp 进行详细符号化。 --- @@ -592,27 +398,7 @@ std::string getThreadCallStacks(); std::string resolveSymbolWithBackward(void* address); ``` -**参数**: -- `address`: 需要符号化的内存地址 - -**返回值**: 符号化后的字符串(包含函数名、文件名、行号) - -**说明**: -- 多层符号化策略: - 1. backward-cpp (最详细) - 2. dladdr (动态链接信息) - 3. addr2line (符号表查询) - 4. 原始地址(降级显示) - -**示例**: -```cpp -void* addr = some_function; -std::string symbol = profiler.resolveSymbolWithBackward(addr); -std::cout << "符号: " << symbol << std::endl; - -// 输出示例: -// symbol_name(int, double) at /path/to/file.cpp:123 -``` +**说明**: 多层符号化策略:backward-cpp → dladdr → addr2line → 原始地址 --- @@ -620,283 +406,83 @@ std::cout << "符号: " << symbol << std::endl; ### getProfilerState -获取 profiler 的当前状态。 - ```cpp ProfilerState getProfilerState(ProfilerType type) const; ``` -**参数**: -- `type`: Profiler 类型(CPU, HEAP, HEAP_GROWTH) - -**返回值**: ProfilerState 结构 - -**示例**: -```cpp -auto state = profiler.getProfilerState(ProfilerType::CPU); -std::cout << "CPU profiler 运行中: " << state.is_running << std::endl; -std::cout << "输出文件: " << state.output_path << std::endl; -``` - ---- - ### isProfilerRunning -检查 profiler 是否正在运行。 - ```cpp bool isProfilerRunning(ProfilerType type) const; ``` -**参数**: -- `type`: Profiler 类型 - -**返回值**: `true` 如果正在运行,否则 `false` - -**示例**: -```cpp -if (profiler.isProfilerRunning(ProfilerType::CPU)) { - std::cout << "CPU profiling 正在进行中..." << std::endl; -} -``` - ---- - ### executeCommand -执行 shell 命令并获取输出。 - ```cpp bool executeCommand(const std::string& cmd, std::string& output); ``` -**参数**: -- `cmd`: 要执行的命令 -- `output`: 输出参数(用于接收命令输出) - -**返回值**: `true` 如果命令执行成功,否则 `false` - -**示例**: -```cpp -std::string output; -if (profiler.executeCommand("ls -l", output)) { - std::cout << "命令输出:\n" << output << std::endl; -} -``` - ---- - ### getExecutablePath -获取当前可执行文件的路径。 - ```cpp std::string getExecutablePath(); ``` -**返回值**: 可执行文件的绝对路径 - -**示例**: -```cpp -std::string exe_path = profiler.getExecutablePath(); -std::cout << "可执行文件: " << exe_path << std::endl; -``` - --- ## 信号配置 -线程堆栈捕获使用信号处理器实现。如果你的程序已经使用了某些信号,可能需要配置。 - ### setStackCaptureSignal -设置用于堆栈捕获的信号。 - ```cpp static void setStackCaptureSignal(int signal); ``` -**参数**: -- `signal`: 信号编号(如 SIGUSR1, SIGUSR2, SIGRTMIN+3) +**说明**: 必须在第一次使用 profiler 之前调用,默认使用 SIGUSR1。 -**说明**: -- 必须在第一次使用 profiler 之前调用 -- 默认使用 SIGUSR1 - -**示例**: ```cpp -// 在 main 函数开始时设置 profiler::ProfilerManager::setStackCaptureSignal(SIGUSR2); -auto& profiler = profiler::ProfilerManager::getInstance(); +profiler::ProfilerManager profiler; ``` ---- - ### getStackCaptureSignal -获取当前用于堆栈捕获的信号。 - ```cpp static int getStackCaptureSignal(); ``` -**返回值**: 当前使用的信号编号 - -**示例**: -```cpp -int signal = profiler::ProfilerManager::getStackCaptureSignal(); -std::cout << "使用信号: " << signal << std::endl; -``` - ---- - ### setSignalChaining -启用/禁用信号链(调用旧的信号处理器)。 - ```cpp static void setSignalChaining(bool enable); ``` -**参数**: -- `enable`: `true` 启用信号链,`false` 禁用 - -**说明**: -- 如果启用,profiler 处理信号后会调用旧的信号处理器 -- 适用于程序已有信号处理器的情况 - -**示例**: -```cpp -// 启用信号链 -profiler::ProfilerManager::setSignalChaining(true); -``` +**说明**: 如果启用,profiler 处理信号后会调用旧的信号处理器。 --- -## Web Server API +## Drogon Adapter API -### registerHttpHandlers +### registerDrogonHandlers -注册所有 profiling 相关的 HTTP 端点。 +使用 Drogon 时的一键注册函数。 ```cpp -void registerHttpHandlers(profiler::ProfilerManager& profiler); +void registerDrogonHandlers(profiler::ProfilerManager& profiler); ``` -**参数**: -- `profiler`: ProfilerManager 实例 - -**说明**: -- 注册以下端点: - -**标准 pprof 接口**: - - `/pprof/profile` - CPU profile(返回原始文件,兼容 Go pprof) - - `/pprof/heap` - Heap profile(返回原始文件,兼容 Go pprof) - - `/pprof/growth` - Heap growth profile(返回原始文件,兼容 Go pprof) - - `/pprof/symbol` - 符号化接口(POST,兼容 Go pprof) - -**一键分析接口**: - - `/api/cpu/analyze` - CPU 火焰图 SVG(浏览器显示) - - `/api/heap/analyze` - Heap 火焰图 SVG(浏览器显示) - - `/api/growth/analyze` - Growth 火焰图 SVG(浏览器显示) - -**原始 SVG 下载接口**: - - `/api/cpu/svg_raw` - CPU 原始 SVG(pprof 生成,触发下载) - - `/api/heap/svg_raw` - Heap 原始 SVG(pprof 生成,触发下载) - - `/api/growth/svg_raw` - Growth 原始 SVG(pprof 生成,触发下载) - - `/api/cpu/flamegraph_raw` - CPU FlameGraph 原始 SVG(触发下载) - - `/api/heap/flamegraph_raw` - Heap FlameGraph 原始 SVG(触发下载) - - `/api/growth/flamegraph_raw` - Growth FlameGraph 原始 SVG(触发下载) - -**其他接口**: - - `/api/thread/stacks` - 线程堆栈 - - `/api/status` - 全局状态 - - `/` - Web 主界面 - - `/show_svg.html` - CPU 火焰图查看器 - - `/show_heap_svg.html` - Heap 火焰图查看器 - - `/show_growth_svg.html` - Growth 火焰图查看器 +**说明**: 注册所有 profiling 端点到 Drogon 全局 app。需要在链接时加入 `profiler_web` 目标。 **示例**: -```cpp -#include -#include "profiler_manager.h" -#include "web_server.h" - -int main() { - profiler::ProfilerManager& profiler = profiler::ProfilerManager::getInstance(); - - // 注册所有 HTTP 处理器 - profiler::registerHttpHandlers(profiler); - - // 启动服务器 - drogon::app().addListener("0.0.0.0", 8080); - drogon::app().run(); - - return 0; -} -``` - ---- - -## 使用示例 - -### 完整示例:CPU Profiling - -```cpp -#include "profiler_manager.h" -#include - -int main() { - auto& profiler = profiler::ProfilerManager::getInstance(); - - // 检查状态 - if (!profiler.isProfilerRunning(profiler::ProfilerType::CPU)) { - // 启动 profiler - profiler.startCPUProfiler("my_app.prof"); - std::cout << "CPU profiler 已启动" << std::endl; - } - - // 运行需要分析的代码 - doWork(); - - // 停止 profiler - profiler.stopCPUProfiler(); - std::cout << "Profiling 完成" << std::endl; - - return 0; -} -``` - -### 完整示例:Heap Profiling - ```cpp #include "profiler_manager.h" -#include +#include "profiler/drogon_adapter.h" +#include int main() { - // 设置环境变量 - setenv("TCMALLOC_SAMPLE_PARAMETER", "524288", 1); - - auto& profiler = profiler::ProfilerManager::getInstance(); - - // 启动 heap profiler - profiler.startHeapProfiler("my_app_heap.prof"); - - // 分配内存 - for (int i = 0; i < 1000; i++) { - char* data = new char[1024]; - // 使用 data... - delete[] data; - } - - // 停止 profiler - profiler.stopHeapProfiler(); - - // 获取 heap 数据 - std::string heap_data = profiler.getRawHeapSample(); - std::cout << heap_data << std::endl; - - return 0; + profiler::ProfilerManager profiler; + profiler::registerDrogonHandlers(profiler); + drogon::app().addListener("0.0.0.0", 8080).run(); } ``` @@ -912,7 +498,7 @@ int main() { - 大多数方法返回 `bool` 表示成功/失败 - 字符串方法在失败时返回空字符串 -- 建议检查返回值以确保操作成功 +- `HandlerResponse::error()` 返回包含错误信息的 JSON 响应 --- diff --git a/docs/user_guide/03_integration_examples.md b/docs/user_guide/03_integration_examples.md index 53ecaf2..0bc308c 100644 --- a/docs/user_guide/03_integration_examples.md +++ b/docs/user_guide/03_integration_examples.md @@ -4,11 +4,10 @@ ## 目录 - [场景 1: 仅使用核心 profiling 功能](#场景-1-仅使用核心-profiling-功能) -- [场景 2: 集成 Web 界面](#场景-2-集成-web-界面) -- [场景 3: 与现有 HTTP 服务器集成](#场景-3-与现有-http-服务器集成) +- [场景 2: 使用 Drogon Web 界面](#场景-2-使用-drogon-web-界面) +- [场景 3: 与任意 Web 框架集成](#场景-3-与任意-web-框架集成) - [场景 4: 定时 profiling](#场景-4-定时-profiling) - [场景 5: 条件触发 profiling](#场景-5-条件触发-profiling) -- [场景 6: 多进程 profiling](#场景-6-多进程-profiling) --- @@ -16,6 +15,12 @@ **适用场景**: 命令行工具、批处理程序、不需要 Web 界面的应用 +**CMake 配置**: +```cmake +find_package(cpp-remote-profiler REQUIRED) +target_link_libraries(my_app cpp-remote-profiler::profiler_core) +``` + ### 示例代码 ```cpp @@ -28,7 +33,7 @@ class MyApplication { public: void run() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; std::cout << "应用启动" << std::endl; @@ -42,8 +47,6 @@ public: profiler.stopCPUProfiler(); std::cout << "Profiling 完成,文件: app_profile.prof" << std::endl; - std::cout << "使用以下命令查看:" << std::endl; - std::cout << " go tool pprof -http=:8080 app_profile.prof" << std::endl; } private: @@ -69,77 +72,45 @@ int main() { } ``` -### CMakeLists.txt - -```cmake -cmake_minimum_required(VERSION 3.15) -project(SimpleProfilingApp) - -set(CMAKE_CXX_STANDARD 20) - -# 找到 profiler 库 -find_package(PkgConfig REQUIRED) -pkg_check_modules(GPERFTOOLS REQUIRED libprofiler libtcmalloc) +--- -add_executable(simple_app simple_profiling_app.cpp) +## 场景 2: 使用 Drogon Web 界面 -target_include_directories(simple_app PRIVATE - /path/to/cpp-remote-profiler/include -) +**适用场景**: 需要远程监控、可视化查看 profiling 结果,且使用 Drogon 框架 -target_link_libraries(simple_app - profiler_lib - ${GPERFTOOLS_LIBRARIES} - pthread +**CMake 配置**: +```cmake +find_package(cpp-remote-profiler REQUIRED) +find_package(Drogon CONFIG REQUIRED) +target_link_libraries(my_app + cpp-remote-profiler::profiler_web + Drogon::Drogon ) ``` -### 编译和运行 - -```bash -mkdir build && cd build -cmake .. -make -./simple_app -``` - ---- - -## 场景 2: 集成 Web 界面 - -**适用场景**: 需要远程监控、可视化查看 profiling 结果 - ### 示例代码 ```cpp // web_profiling_server.cpp -#include #include "profiler_manager.h" -#include "web_server.h" +#include "profiler/drogon_adapter.h" +#include #include int main() { std::cout << "=== C++ Remote Profiler Server ===" << std::endl; - // 获取 profiler 实例 - profiler::ProfilerManager& profiler = profiler::ProfilerManager::getInstance(); + // 创建 ProfilerManager 实例 + profiler::ProfilerManager profiler; - // 注册所有 profiling 相关的 HTTP 端点 - profiler::registerHttpHandlers(profiler); + // 注册所有 profiling 相关的 HTTP 端点到 Drogon + profiler::registerDrogonHandlers(profiler); // 配置 Drogon 服务器 drogon::app().addListener("0.0.0.0", 8080); - drogon::app().setThreadNum(4); - drogon::app().setDocumentRoot("./"); std::cout << "服务器启动成功!" << std::endl; std::cout << "Web 界面: http://localhost:8080" << std::endl; - std::cout << "API 端点:" << std::endl; - std::cout << " - GET /pprof/profile?seconds=N" << std::endl; - std::cout << " - GET /pprof/heap" << std::endl; - std::cout << " - GET /api/cpu/analyze?duration=N" << std::endl; - std::cout << " - GET /api/heap/analyze" << std::endl; - std::cout << " - GET /api/thread/stacks" << std::endl; // 启动服务器(阻塞) drogon::app().run(); @@ -148,87 +119,149 @@ int main() { } ``` -### CMakeLists.txt +### 与现有 Drogon 服务器集成 -```cmake -cmake_minimum_required(VERSION 3.15) -project(WebProfilingServer) +```cpp +#include "profiler_manager.h" +#include "profiler/drogon_adapter.h" +#include -set(CMAKE_CXX_STANDARD 20) +int main() { + profiler::ProfilerManager profiler; -find_package(Drogon CONFIG REQUIRED) -find_package(PkgConfig REQUIRED) -pkg_check_modules(GPERFTOOLS REQUIRED libprofiler libtcmalloc) + // 注册 profiling 端点 + profiler::registerDrogonHandlers(profiler); -add_executable(web_server web_profiling_server.cpp) + // 注册你自己的业务端点 + drogon::app().registerHandler( + "/api/business", + [](const drogon::HttpRequestPtr& req, + std::function&& callback) { + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setBody("Business logic response"); + callback(resp); + }, + {drogon::Get} + ); -target_link_libraries(web_server - profiler_lib - Drogon::Drogon - ${GPERFTOOLS_LIBRARIES} -) + drogon::app().addListener("0.0.0.0", 8080).run(); +} ``` -### 使用方法 +--- -1. 编译并运行服务器 -2. 浏览器访问 `http://localhost:8080` -3. 点击按钮进行 profiling,查看火焰图 +## 场景 3: 与任意 Web 框架集成 ---- +**适用场景**: 已有非 Drogon 的 Web 服务器(如 oat++、crow、 Pistache 等),想添加 profiling 功能 + +**CMake 配置**: +```cmake +find_package(cpp-remote-profiler REQUIRED) +target_link_libraries(my_app + cpp-remote-profiler::profiler_core # 不需要 profiler_web + your-web-framework +) +``` -## 场景 3: 与现有 HTTP 服务器集成 +### 核心思路 -**适用场景**: 已有 Drogon 服务器,想添加 profiling 功能 +`ProfilerHttpHandlers` 提供框架无关的 handler 方法,每个方法返回 `HandlerResponse` 结构体。你只需要将 `HandlerResponse` 包装到你框架的 response 对象中。 -### 示例代码 +### 示例:伪代码集成 ```cpp -// existing_server_with_profiler.cpp -#include #include "profiler_manager.h" -#include "web_server.h" - -// 你的业务逻辑处理器 -void businessHandler(const drogon::HttpRequestPtr& req, - std::function&& callback) { - auto resp = drogon::HttpResponse::newHttpResponse(); - resp->setBody("Business logic response"); - callback(resp); -} +#include "profiler/http_handlers.h" +#include "your_web_framework.h" // 你的 Web 框架头文件 int main() { - // 1. 初始化 profiler - profiler::ProfilerManager& profiler = profiler::ProfilerManager::getInstance(); - std::cout << "Profiler 初始化完成" << std::endl; + profiler::ProfilerManager profiler; + profiler::ProfilerHttpHandlers handlers(profiler); + + YourWebServer server; + + // 注册 CPU 分析端点 + server.route("GET", "/api/cpu/analyze", [&](const Request& req) { + int duration = req.get_param_int("duration", 10); + std::string output_type = req.get_param("output_type", "pprof"); + + // 调用 handler 获取框架无关的响应 + profiler::HandlerResponse resp = handlers.handleCpuAnalyze(duration, output_type); + + // 用你的框架包装响应 + return YourResponse() + .status(resp.status) + .header("Content-Type", resp.content_type) + .body(resp.body); + }); + + // 注册 pprof 兼容端点 + server.route("GET", "/pprof/profile", [&](const Request& req) { + int seconds = req.get_param_int("seconds", 30); + profiler::HandlerResponse resp = handlers.handlePprofProfile(seconds); + return YourResponse() + .status(resp.status) + .header("Content-Type", resp.content_type) + .body(resp.body); + }); + + // 注册状态端点 + server.route("GET", "/api/status", [&]() { + profiler::HandlerResponse resp = handlers.handleStatus(); + return YourResponse() + .status(resp.status) + .header("Content-Type", resp.content_type) + .body(resp.body); + }); + + // ... 注册其他端点 + + server.listen(8080); +} +``` - // 2. 注册 profiling 端点 - profiler::registerHttpHandlers(profiler); +### 示例:与 oat++ 集成 - // 3. 注册你自己的业务端点 - drogon::app().registerHandler( - "/api/business", - &businessHandler, - {drogon::Get} - ); +```cpp +#include "profiler_manager.h" +#include "profiler/http_handlers.h" +#include "oatpp/web/server/HttpConnectionHandler.hpp" - // 4. 启动服务器 - drogon::app().addListener("0.0.0.0", 8080); - std::cout << "服务器启动: http://localhost:8080" << std::endl; - std::cout << "Business API: http://localhost:8080/api/business" << std::endl; - std::cout << "Profiler UI: http://localhost:8080/" << std::endl; +class ProfilerController : public oatpp::web::server::handler::RequestHandler { + profiler::ProfilerManager profiler_; + profiler::ProfilerHttpHandlers handlers_; - drogon::app().run(); +public: + ProfilerController() : handlers_(profiler_) {} - return 0; -} + std::shared_ptr handle(const std::shared_ptr& request) override { + std::string path = request->getHeader("PATH"); + profiler::HandlerResponse resp; + + if (path == "/api/status") { + resp = handlers_.handleStatus(); + } else if (path == "/api/cpu/analyze") { + int duration = /* parse from request */ 10; + resp = handlers_.handleCpuAnalyze(duration, "pprof"); + } + // ... 其他路由 + + auto response = OutgoingResponse::createStatic( + Status(resp.status, "OK"), + resp.body + ); + response->putHeader("Content-Type", resp.content_type.c_str()); + return response; + } +}; ``` ### 关键点 -- Profiling 端点和业务端点共存 -- 可以选择性启用 profiling(例如:只在开发环境) -- 不影响现有业务逻辑 +- `ProfilerHttpHandlers` 不依赖任何 Web 框架 +- 只需链接 `profiler_core`(不需要 Drogon) +- 每个 handler 方法返回 `HandlerResponse`(`status`, `content_type`, `body`, `headers`) +- 你负责从请求中提取参数,调用 handler,然后包装响应 --- @@ -236,8 +269,6 @@ int main() { **适用场景**: 长期运行的服务,定期采集性能数据 -### 示例代码 - ```cpp // scheduled_profiling_app.cpp #include "profiler_manager.h" @@ -249,8 +280,7 @@ int main() { class ScheduledProfiler { public: ScheduledProfiler(int interval_seconds) - : interval_seconds_(interval_seconds), - running_(false) {} + : interval_seconds_(interval_seconds), running_(false) {} void start() { running_ = true; @@ -266,7 +296,7 @@ public: private: void profilingLoop() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; int profile_count = 0; while (running_) { @@ -274,7 +304,6 @@ private: profile_count++; - // 生成带时间戳的文件名 time_t now = time(nullptr); char filename[256]; strftime(filename, sizeof(filename), @@ -283,7 +312,6 @@ private: std::cout << "[" << profile_count << "] 开始 profiling..." << std::endl; - // 采样 30 秒 profiler.startCPUProfiler(filename); std::this_thread::sleep_for(std::chrono::seconds(30)); profiler.stopCPUProfiler(); @@ -298,15 +326,9 @@ private: }; int main() { - std::cout << "定时 Profiling 应用" << std::endl; - std::cout << "每 5 分钟采样一次,每次采样 30 秒" << std::endl; - - ScheduledProfiler profiler(300); // 每 300 秒(5 分钟) + ScheduledProfiler profiler(300); // 每 5 分钟 profiler.start(); - std::cout << "按 Ctrl+C 退出..." << std::endl; - - // 主线程等待 while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); } @@ -316,20 +338,12 @@ int main() { } ``` -### 使用场景 - -- 生产环境性能监控 -- 长时间运行任务的性能跟踪 -- 自动化性能测试 - --- ## 场景 5: 条件触发 profiling **适用场景**: 检测到性能问题时自动启用 profiling -### 示例代码 - ```cpp // conditional_profiling_app.cpp #include "profiler_manager.h" @@ -340,30 +354,23 @@ int main() { class PerformanceMonitor { public: void checkPerformance() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; while (running_) { auto start = std::chrono::high_resolution_clock::now(); - - // 执行任务 doTask(); - auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); - // 检查是否超时 if (duration.count() > threshold_ms_) { - std::cout << "⚠️ 检测到性能问题: " << duration.count() << "ms" << std::endl; + std::cout << "检测到性能问题: " << duration.count() << "ms" << std::endl; if (!profiler.isProfilerRunning(profiler::ProfilerType::CPU)) { - std::cout << "自动启动 CPU profiling..." << std::endl; - - // 自动启动 profiling profiler.startCPUProfiler("auto_profile.prof"); std::this_thread::sleep_for(std::chrono::seconds(10)); profiler.stopCPUProfiler(); - - std::cout << "Profiling 完成,请查看 auto_profile.prof" << std::endl; + std::cout << "Profiling 完成" << std::endl; } } @@ -376,278 +383,74 @@ public: private: void doTask() { - // 模拟任务 double result = 0; for (int i = 0; i < 1000000; i++) { result += std::sqrt(i); } } - int threshold_ms_ = 100; // 100ms 阈值 + int threshold_ms_ = 100; std::atomic running_{false}; }; int main() { - std::cout << "性能监控应用(阈值: 100ms)" << std::endl; - PerformanceMonitor monitor; monitor.start(); - - // 运行监控 std::this_thread::sleep_for(std::chrono::minutes(5)); - monitor.stop(); - return 0; } ``` -### 使用场景 - -- 自动化性能问题检测 -- 生产环境问题诊断 -- 智能监控 - --- -## 场景 6: 多进程 profiling - -**适用场景**: 微服务架构、多进程应用 - -### 架构设计 - -``` -┌─────────────────────────────────────────────────┐ -│ Nginx / Reverse Proxy │ -└─────────────────────────────────────────────────┘ - │ │ │ - ▼ ▼ ▼ - ┌──────────┐ ┌──────────┐ ┌──────────┐ - │ Service 1│ │ Service 2│ │ Service 3│ - │ :8081 │ │ :8082 │ │ :8083 │ - └──────────┘ └──────────┘ └──────────┘ -``` - -### 示例代码 - -```cpp -// service.cpp -#include -#include "profiler_manager.h" -#include "web_server.h" -#include -#include - -int main(int argc, char* argv[]) { - if (argc < 2) { - std::cerr << "用法: " << argv[0] << " " << std::endl; - return 1; - } - - int port = std::atoi(argv[1]); - - std::cout << "Service 启动在端口: " << port << std::endl; - - // 初始化 profiler - profiler::ProfilerManager& profiler = profiler::ProfilerManager::getInstance(); - profiler::registerHttpHandlers(profiler); - - // 业务逻辑处理器 - auto handler = [](const drogon::HttpRequestPtr& req, - std::function&& callback) { - auto resp = drogon::HttpResponse::newHttpResponse(); - resp->setBody("Service on port " + req->getParameter("port")); - callback(resp); - }; - - drogon::app().registerHandler("/api/service", handler); - - // 监听指定端口 - drogon::app().addListener("0.0.0.0", port); - drogon::app().run(); - - return 0; -} -``` - -### 启动脚本 - -```bash -#!/bin/bash -# start_services.sh - -echo "启动多个服务实例..." - -# 启动 3 个服务实例 -./service 8081 & -PID1=$! - -./service 8082 & -PID2=$! - -./service 8083 & -PID3=$! - -echo "服务启动完成:" -echo " Service 1: http://localhost:8081" -echo " Service 2: http://localhost:8082" -echo " Service 3: http://localhost:8083" - -echo "按 Ctrl+C 停止所有服务" - -# 等待中断信号 -trap "kill $PID1 $PID2 $PID3; exit" INT TERM - -wait -``` - -### 使用方法 - -```bash -# 启动所有服务 -./start_services.sh - -# 分别查看每个服务的 profiling -curl http://localhost:8081/api/cpu/analyze?duration=10 -curl http://localhost:8082/api/cpu/analyze?duration=10 -curl http://localhost:8083/api/cpu/analyze?duration=10 -``` - ---- +## 最佳实践 -## 完整示例:集成到游戏服务器 +### 1. RAII 管理 profiler ```cpp -// game_server.cpp -#include "profiler_manager.h" -#include -#include -#include - -class GameServer { +class ScopedProfiler { public: - void start() { - std::cout << "游戏服务器启动" << std::endl; - - // 初始化 profiler - auto& profiler = profiler::ProfilerManager::getInstance(); - - // 可以选择性地启用 profiling(例如:通过配置文件) - if (enableProfiling_) { - std::cout << "Profiling 已启用" << std::endl; - profiler.startCPUProfiler("game_server.prof"); - } - - // 启动游戏循环 - gameLoop(); - } - - void stop() { - auto& profiler = profiler::ProfilerManager::getInstance(); - - if (enableProfiling_) { - profiler.stopCPUProfiler(); - std::cout << "Profiling 已保存" << std::endl; - } + ScopedProfiler(profiler::ProfilerManager& profiler, const std::string& name) + : profiler_(profiler), name_(name) { + profiler_.startCPUProfiler(name_); } - void setProfilingEnabled(bool enabled) { - enableProfiling_ = enabled; + ~ScopedProfiler() { + profiler_.stopCPUProfiler(); } private: - void gameLoop() { - while (running_) { - processInput(); - updateGameLogic(); - render(); - - std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60 FPS - } - } - - void processInput() { - // 处理输入 - } - - void updateGameLogic() { - // 更新游戏逻辑 - } - - void render() { - // 渲染 - } - - bool running_ = true; - bool enableProfiling_ = false; + profiler::ProfilerManager& profiler_; + std::string name_; }; -int main() { - GameServer server; - - // 从配置或命令行参数读取是否启用 profiling - server.setProfilingEnabled(true); - - server.start(); - - return 0; -} +// 使用 +profiler::ProfilerManager profiler; +{ + ScopedProfiler guard(profiler, "critical_section.prof"); + // 这里的代码会被 profiling +} // 自动停止 ``` ---- - -## 最佳实践 - -### 1. 环境隔离 +### 2. 环境隔离 ```cpp // 开发环境启用 profiling #ifdef DEBUG_MODE + profiler::ProfilerManager profiler; profiler.startCPUProfiler("debug.prof"); #else // 生产环境默认不启用 #endif ``` -### 2. 配置驱动 - -```cpp -// 从配置文件读取 -if (config.getBool("profiling.enabled", false)) { - int duration = config.getInt("profiling.duration", 10); - profiler.analyzeCPUProfile(duration); -} -``` - -### 3. 资源管理 - -```cpp -// 使用 RAII 管理 profiler -class ScopedProfiler { -public: - ScopedProfiler(const std::string& name) : name_(name) { - profiler::ProfilerManager::getInstance().startCPUProfiler(name_); - } - - ~ScopedProfiler() { - profiler::ProfilerManager::getInstance().stopCPUProfiler(); - } - -private: - std::string name_; -}; - -// 使用 -{ - ScopedProfiler profiler("critical_section.prof"); - // 这里的代码会被 profiling -} // 自动停止 -``` - -### 4. 线程安全 +### 3. 线程安全 ```cpp // 所有 API 都是线程安全的 +profiler::ProfilerManager profiler; + void thread1() { profiler.startCPUProfiler("thread1.prof"); } @@ -661,25 +464,6 @@ void thread2() { --- -## 性能建议 - -1. **降低开销**: - - 仅在需要时启用 profiling - - 使用较短的采样时长 - - 避免频繁启动/停止 - -2. **存储管理**: - - 定期清理旧的 profile 文件 - - 使用压缩存储历史数据 - - 设置磁盘空间监控 - -3. **生产环境**: - - 使用条件触发而非持续 profiling - - 设置 profiling 开销阈值 - - 监控 profiling 对性能的影响 - ---- - ## 更多信息 - 📖 [API 参考手册](02_api_reference.md) diff --git a/docs/user_guide/04_troubleshooting.md b/docs/user_guide/04_troubleshooting.md index bd628d5..e793909 100644 --- a/docs/user_guide/04_troubleshooting.md +++ b/docs/user_guide/04_troubleshooting.md @@ -117,11 +117,11 @@ brew install gperftools **错误信息**: ``` -undefined reference to `profiler::ProfilerManager::getInstance()' +undefined reference to `profiler::ProfilerManager::startCPUProfiler(...)' ``` **原因**: -- 没有链接 profiler_lib +- 没有链接 profiler_core - 链接顺序错误 **解决方案**: @@ -129,9 +129,7 @@ undefined reference to `profiler::ProfilerManager::getInstance()' 确保在 CMakeLists.txt 中正确链接: ```cmake target_link_libraries(your_app - profiler_lib # profiler 库 - ${GPERFTOOLS_LIBRARIES} # gperftools 库 - pthread # 线程库 + cpp-remote-profiler::profiler_core # profiler 核心库 ) ``` @@ -673,7 +671,7 @@ cmake .. -DCMAKE_BUILD_TYPE=Release ... #include "profiler_manager.h" int main() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; profiler.startCPUProfiler(); // ... return 0; diff --git a/docs/user_guide/05_installation.md b/docs/user_guide/05_installation.md index 6e7347b..d114649 100644 --- a/docs/user_guide/05_installation.md +++ b/docs/user_guide/05_installation.md @@ -86,7 +86,7 @@ set(CMAKE_TOOLCHAIN_FILE /path/to/vcpkg/scripts/buildsystems/vcpkg.cmake) find_package(cpp-remote-profiler CONFIG REQUIRED) add_executable(my_app main.cpp) -target_link_libraries(my_app cpp-remote-profiler::profiler_lib) +target_link_libraries(my_app cpp-remote-profiler::profiler_core) ``` ### 步骤 4: 编译和运行 @@ -236,7 +236,7 @@ target_link_libraries(my_app #include int main() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; std::cout << "Profiler version: " << REMOTE_PROFILER_VERSION << std::endl; profiler.startCPUProfiler("my_profile.prof"); @@ -319,7 +319,7 @@ make -j$(nproc) sudo make install # 默认安装路径: -# /usr/local/lib/libprofiler_lib.so +# /usr/local/lib/libprofiler_core.so # /usr/local/include/cpp-remote-profiler/ ``` @@ -391,7 +391,7 @@ sudo dnf install cpp-remote-profiler-devel ldconfig -p | grep profiler_lib # 或检查特定路径 -ls -l /usr/local/lib/libprofiler_lib.* +ls -l /usr/local/lib/libprofiler_core.* ``` ### 检查头文件 @@ -400,8 +400,7 @@ ls -l /usr/local/lib/libprofiler_lib.* ls -l /usr/local/include/cpp-remote-profiler/ # 应该看到: # profiler_manager.h -# symbolize.h -# web_server.h +# profiler/drogon_adapter.h # version.h ``` @@ -414,7 +413,7 @@ ls -l /usr/local/include/cpp-remote-profiler/ #include int main() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; std::cout << "C++ Remote Profiler" << std::endl; std::cout << "Version: " << REMOTE_PROFILER_VERSION << std::endl; @@ -446,7 +445,7 @@ Installation successful! **错误**: ``` -error while loading shared libraries: libprofiler_lib.so: cannot open shared object file +error while loading shared libraries: libprofiler_core.so: cannot open shared object file ``` **解决方案**: @@ -591,7 +590,7 @@ conan remove cpp-remote-profiler --all ```bash sudo rm -rf /usr/local/include/cpp-remote-profiler -sudo rm -f /usr/local/lib/libprofiler_lib.* +sudo rm -f /usr/local/lib/libprofiler_core.* sudo ldconfig ``` diff --git a/docs/user_guide/06_using_find_package.md b/docs/user_guide/06_using_find_package.md index ef78fcb..2c10ef2 100644 --- a/docs/user_guide/06_using_find_package.md +++ b/docs/user_guide/06_using_find_package.md @@ -36,7 +36,7 @@ sudo make install DESTDIR=/opt/cpp-remote-profiler ``` /usr/local/ ├── lib/ -│ ├── libprofiler_lib.so # 动态库 +│ ├── libprofiler_core.so # 动态库 │ └── cmake/ │ └── cpp-remote-profiler/ │ ├── cpp-remote-profiler-config.cmake @@ -46,7 +46,9 @@ sudo make install DESTDIR=/opt/cpp-remote-profiler └── cpp-remote-profiler/ ├── profiler_manager.h ├── symbolize.h - ├── web_server.h + ├── profiler/ +│ ├── drogon_adapter.h +│ ├── http_handlers.h ├── web_resources.h └── version.h ``` @@ -73,7 +75,7 @@ find_package(cpp-remote-profiler REQUIRED) add_executable(my_app main.cpp) # 链接库 -target_link_libraries(my_app cpp-remote-profiler::profiler_lib) +target_link_libraries(my_app cpp-remote-profiler::profiler_core) ``` ### 方法 2: 使用 CMAKE_MODULE_PATH @@ -126,7 +128,7 @@ add_executable(my_app main.cpp) # 链接库(需要同时链接 Drogon) target_link_libraries(my_app - cpp-remote-profiler::profiler_lib + cpp-remote-profiler::profiler_core Drogon::Drogon ) ``` @@ -143,7 +145,7 @@ pkg_check_modules(GPERFTOOLS REQUIRED libprofiler libtcmalloc) add_executable(my_app main.cpp) target_link_libraries(my_app - cpp-remote-profiler::profiler_lib + cpp-remote-profiler::profiler_core ${GPERFTOOLS_LIBRARIES} ) ``` @@ -164,7 +166,7 @@ set(CMAKE_CXX_STANDARD 20) find_package(cpp-remote-profiler REQUIRED) add_executable(app main.cpp) -target_link_libraries(app cpp-remote-profiler::profiler_lib) +target_link_libraries(app cpp-remote-profiler::profiler_core) ``` **main.cpp**: @@ -173,7 +175,7 @@ target_link_libraries(app cpp-remote-profiler::profiler_lib) #include int main() { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; std::cout << "Profiler Version: " << REMOTE_PROFILER_VERSION << std::endl; @@ -211,7 +213,7 @@ find_package(cpp-remote-profiler REQUIRED) add_executable(web_app main.cpp) target_link_libraries(web_app - cpp-remote-profiler::profiler_lib + cpp-remote-profiler::profiler_core Drogon::Drogon ) ``` @@ -219,19 +221,19 @@ target_link_libraries(web_app **main.cpp**: ```cpp #include "profiler_manager.h" -#include "web_server.h" +#include "profiler/drogon_adapter.h" #include int main() { - profiler::ProfilerManager& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; - // 注册 Web 界面 - profiler::registerHttpHandlers(profiler); + // 注册 Web 界面到 Drogon + profiler::registerDrogonHandlers(profiler); std::cout << "Web UI: http://localhost:8080" << std::endl; // 启动服务器 - // ... Drogon 服务器代码 ... + drogon::app().addListener("0.0.0.0", 8080).run(); return 0; } @@ -332,7 +334,7 @@ undefined reference to `drogon::...` # 在 CMakeLists.txt 中添加 Drogon find_package(Drogon REQUIRED) target_link_libraries(my_app - cpp-remote-profiler::profiler_lib + cpp-remote-profiler::profiler_core Drogon::Drogon # 添加这一行 ) ``` @@ -381,7 +383,7 @@ target_include_directories(my_app PRIVATE /usr/local/include/cpp-remote-profiler **错误**: ``` -error while loading shared libraries: libprofiler_lib.so +error while loading shared libraries: libprofiler_core.so ``` **解决方案**: diff --git a/example/custom_signal.cpp b/example/custom_signal.cpp index e844fd6..b583d1a 100644 --- a/example/custom_signal.cpp +++ b/example/custom_signal.cpp @@ -7,8 +7,8 @@ void example1_default_signal() { std::cout << "=== Example 1: Using default SIGUSR1 ===" << std::endl; - // 直接使用,默认使用 SIGUSR1 - auto& profiler = profiler::ProfilerManager::getInstance(); + // 创建 ProfilerManager 实例 + profiler::ProfilerManager profiler; std::cout << "Current signal: " << profiler.getStackCaptureSignal() << std::endl; std::cout << "(SIGUSR1 = " << SIGUSR1 << ")" << std::endl; @@ -23,7 +23,7 @@ void example2_custom_signal() { // 在使用 profiler 之前设置信号 profiler::ProfilerManager::setStackCaptureSignal(SIGUSR2); - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; std::cout << "Current signal: " << profiler.getStackCaptureSignal() << std::endl; std::cout << "(SIGUSR2 = " << SIGUSR2 << ")" << std::endl; @@ -39,7 +39,7 @@ void example3_realtime_signal() { int custom_signal = SIGRTMIN + 5; profiler::ProfilerManager::setStackCaptureSignal(custom_signal); - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; std::cout << "Current signal: " << profiler.getStackCaptureSignal() << std::endl; std::cout << "(SIGRTMIN + 5 = " << custom_signal << ")" << std::endl; @@ -54,13 +54,13 @@ void example4_signal_chaining() { // 启用信号链(在处理器中调用旧的处理器) profiler::ProfilerManager::setSignalChaining(true); - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; // ... 使用 profiler ... } int main() { - // 只运行其中一个示例(因为 ProfilerManager 是单例) + // 只运行其中一个示例 // 示例 1:默认信号(推荐) example1_default_signal(); @@ -76,7 +76,7 @@ int main() { std::cout << "\n=== Signal configuration tips ===" << std::endl; std::cout << "1. Check if your application uses SIGUSR1/SIGUSR2" << std::endl; - std::cout << "2. Use setStackCaptureSignal() before calling getInstance()" << std::endl; + std::cout << "2. Use setStackCaptureSignal() before creating ProfilerManager" << std::endl; std::cout << "3. Real-time signals (SIGRTMIN+n) are safer" << std::endl; std::cout << "4. Signal chaining can be enabled but may interfere with stack capture" << std::endl; diff --git a/example/main.cpp b/example/main.cpp index 2cea19f..59c232a 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -1,11 +1,14 @@ +#include "profiler/drogon_adapter.h" #include "profiler_manager.h" -#include "web_server.h" #include "workload.h" #include -#include #include #include +#ifdef REMOTE_PROFILER_ENABLE_WEB +#include +#endif + int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { std::cout << "C++ Remote Profiler Example\n"; std::cout << "============================\n\n"; @@ -16,7 +19,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { std::cout << "Starting HTTP server on " << host << ":" << port << "\n"; std::cout << "Open your browser and visit: http://localhost:" << port << "\n\n"; - // 启动后台线程 - 运行工作负载以生成 profiling 数据 + // Start background workload thread std::thread worker([]() { while (true) { cpuIntensiveTask(); @@ -26,19 +29,24 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { }); worker.detach(); - // 获取 profiler 实例 - auto& profiler = profiler::ProfilerManager::getInstance(); + // Create ProfilerManager instance (no longer a singleton) + profiler::ProfilerManager profiler; - // 注册所有 HTTP 路由处理器 +#ifdef REMOTE_PROFILER_ENABLE_WEB + // Register all HTTP route handlers with Drogon std::cout << "Registering HTTP handlers...\n"; - profiler::registerHttpHandlers(profiler); + profiler::registerDrogonHandlers(profiler); - // 设置监听地址和端口 + // Start server (blocking) std::cout << "Starting server on " << host << ":" << port << "...\n"; - drogon::app().addListener(host, port); - - // 启动服务器(阻塞) - drogon::app().run(); + drogon::app().addListener(host, port).run(); +#else + std::cout << "Web UI disabled. Running in core-only mode.\n"; + // Keep the main thread alive + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +#endif return 0; } diff --git a/include/profiler/drogon_adapter.h b/include/profiler/drogon_adapter.h new file mode 100644 index 0000000..fc9fdf5 --- /dev/null +++ b/include/profiler/drogon_adapter.h @@ -0,0 +1,22 @@ +/// @file drogon_adapter.h +/// @brief Drogon integration: registers profiler routes via ProfilerHttpHandlers + +#pragma once + +#include "profiler_manager.h" + +PROFILER_NAMESPACE_BEGIN + +/// @brief Register all HTTP route handlers with the global Drogon app +/// +/// This is a convenience function that registers all profiling-related +/// API endpoints and web pages using Drogon. It internally creates a +/// ProfilerHttpHandlers and wraps each handler with Drogon adapters. +/// +/// After calling this, start the Drogon server with: +/// drogon::app().addListener(host, port).run(); +/// +/// @param profiler Reference to the ProfilerManager instance to use +void registerDrogonHandlers(profiler::ProfilerManager& profiler); + +PROFILER_NAMESPACE_END diff --git a/include/profiler/http_handlers.h b/include/profiler/http_handlers.h new file mode 100644 index 0000000..f92a666 --- /dev/null +++ b/include/profiler/http_handlers.h @@ -0,0 +1,97 @@ +/// @file http_handlers.h +/// @brief Framework-agnostic HTTP endpoint handlers for the profiler +/// +/// Each handler returns a HandlerResponse struct with status code, +/// content type, body, and headers. Users wrap these with their +/// own web framework's request/response types. + +#pragma once + +#include "profiler_version.h" +#include +#include + +PROFILER_NAMESPACE_BEGIN + +class ProfilerManager; + +/// @brief Framework-agnostic HTTP response +struct HandlerResponse { + int status = 200; + std::string content_type = "text/plain"; + std::string body; + std::map headers; + + static HandlerResponse html(const std::string& content) { + return {200, "text/html", content, {}}; + } + static HandlerResponse json(const std::string& content) { + return {200, "application/json", content, {}}; + } + static HandlerResponse svg(const std::string& content) { + return {200, "image/svg+xml", content, {}}; + } + static HandlerResponse text(const std::string& content) { + return {200, "text/plain", content, {}}; + } + static HandlerResponse binary(const std::string& data, const std::string& filename) { + return {200, "application/octet-stream", data, {{"Content-Disposition", "attachment; filename=" + filename}}}; + } + static HandlerResponse error(int status, const std::string& message) { + return {status, "application/json", "{\"error\":\"" + message + "\"}", {}}; + } +}; + +/// @brief Framework-agnostic profiler HTTP endpoint handlers +/// +/// Usage example with any framework: +/// @code +/// ProfilerManager profiler; +/// ProfilerHttpHandlers handlers(profiler); +/// +/// // In your framework's route handler: +/// auto resp = handlers.handleStatus(); +/// // wrap resp.status, resp.content_type, resp.body into your framework's response +/// @endcode +class ProfilerHttpHandlers { +public: + explicit ProfilerHttpHandlers(ProfilerManager& profiler); + + // --- Status --- + HandlerResponse handleStatus(); + + // --- CPU endpoints --- + HandlerResponse handleCpuAnalyze(int duration, const std::string& output_type); + HandlerResponse handleCpuSvgRaw(int duration); + HandlerResponse handleCpuFlamegraphRaw(int duration); + + // --- Heap endpoints --- + HandlerResponse handleHeapAnalyze(const std::string& output_type); + HandlerResponse handleHeapSvgRaw(); + HandlerResponse handleHeapFlamegraphRaw(); + + // --- Growth endpoints --- + HandlerResponse handleGrowthAnalyze(const std::string& output_type); + HandlerResponse handleGrowthSvgRaw(); + HandlerResponse handleGrowthFlamegraphRaw(); + + // --- Convenience: single dispatch by path --- + /// Dispatch a request to the appropriate handler based on path. + /// Returns a 404 response if path is not recognized. + HandlerResponse dispatch(const std::string& method, const std::string& path, + const std::map& params = {}, const std::string& body = ""); + + // --- Standard pprof endpoints --- + HandlerResponse handlePprofProfile(int seconds); + HandlerResponse handlePprofHeap(); + HandlerResponse handlePprofGrowth(); + HandlerResponse handlePprofSymbol(const std::string& body); + + // --- Thread stacks --- + HandlerResponse handleThreadStacks(); + +private: + ProfilerManager& profiler_; +}; + +PROFILER_NAMESPACE_END diff --git a/include/profiler/log_sink.h b/include/profiler/log_sink.h index c79f4d3..d1b5d82 100644 --- a/include/profiler/log_sink.h +++ b/include/profiler/log_sink.h @@ -36,8 +36,9 @@ enum class LogLevel { /// } /// }; /// -/// // Set custom sink -/// profiler::setSink(std::make_shared()); +/// // Set custom sink on your ProfilerManager instance +/// profiler::ProfilerManager profiler; +/// profiler.setLogSink(std::make_shared()); /// @endcode class LogSink { public: diff --git a/include/profiler/logger.h b/include/profiler/logger.h index 9546a15..e2130b2 100644 --- a/include/profiler/logger.h +++ b/include/profiler/logger.h @@ -1,45 +1,18 @@ /// @file logger.h /// @brief Logger configuration interface +/// +/// Log sink and log level configuration is now managed through +/// ProfilerManager instances. Include profiler_manager.h and use: +/// profiler.setLogSink(mySink); +/// profiler.setLogLevel(profiler::LogLevel::Debug); #pragma once #include "log_sink.h" -#include PROFILER_NAMESPACE_BEGIN -/// @brief Set a custom log sink for the profiler -/// @param sink Shared pointer to a LogSink implementation -/// -/// When a custom sink is set, all profiler log messages will be -/// forwarded to this sink instead of the default output. -/// -/// Pass nullptr to revert to the default sink. -/// -/// @example -/// @code -/// // Use custom sink -/// profiler::setSink(std::make_shared()); -/// -/// // Revert to default -/// profiler::setSink(nullptr); -/// @endcode -void setSink(std::shared_ptr sink); - -/// @brief Set the minimum log level -/// @param level Minimum level for messages to be output -/// -/// Messages with a level lower than the specified level will be suppressed. -/// Default level is LogLevel::Info. -/// -/// @example -/// @code -/// // Enable debug logging -/// profiler::setLogLevel(profiler::LogLevel::Debug); -/// -/// // Suppress all but errors -/// profiler::setLogLevel(profiler::LogLevel::Error); -/// @endcode -void setLogLevel(LogLevel level); +// Log configuration is now per-ProfilerManager instance. +// See profiler_manager.h for setLogSink() and setLogLevel() methods. PROFILER_NAMESPACE_END diff --git a/include/profiler_manager.h b/include/profiler_manager.h index 830beb2..23d1391 100644 --- a/include/profiler_manager.h +++ b/include/profiler_manager.h @@ -3,6 +3,7 @@ #pragma once +#include "profiler/log_sink.h" #include "profiler_version.h" #include #include @@ -15,9 +16,13 @@ PROFILER_NAMESPACE_BEGIN -// Forward declaration +// Forward declarations class Symbolizer; +namespace internal { +class LogManager; +} // namespace internal + /// @enum ProfilerType /// @brief Types of profiling operations supported enum class ProfilerType { @@ -59,17 +64,32 @@ struct SharedStackTrace { /// @class ProfilerManager /// @brief Main manager class for profiling operations /// -/// ProfilerManager provides a singleton interface for controlling -/// CPU profiling, heap profiling, and thread stack capture. -/// It integrates with gperftools for profiling and provides -/// pprof-compatible output formats. +/// ProfilerManager controls CPU profiling, heap profiling, +/// and thread stack capture. It integrates with gperftools +/// and provides pprof-compatible output formats. +/// +/// Create an instance and manage its lifetime as needed. +/// Signal handlers are installed lazily on first stack capture. /// /// @note Thread-safe. All public methods can be called from any thread. class ProfilerManager { public: - /// @brief Get the singleton instance of ProfilerManager - /// @return Reference to the singleton instance - static ProfilerManager& getInstance(); + /// @brief Construct a ProfilerManager + ProfilerManager(); + + /// @brief Destructor - stops any running profilers and restores signal handlers + ~ProfilerManager(); + + ProfilerManager(const ProfilerManager&) = delete; + ProfilerManager& operator=(const ProfilerManager&) = delete; + + /// @brief Set a custom log sink for profiler messages + /// @param sink Shared pointer to a LogSink implementation (nullptr reverts to default) + void setLogSink(std::shared_ptr sink); + + /// @brief Set the minimum log level for profiler messages + /// @param level Minimum level for messages to be output + void setLogLevel(LogLevel level); /// @brief Start CPU profiling session /// @param output_path Path to save the profile output (default: "cpu.prof") @@ -169,12 +189,13 @@ class ProfilerManager { /// @return Path to the executable std::string getExecutablePath(); -private: - ProfilerManager(); - ~ProfilerManager(); - ProfilerManager(const ProfilerManager&) = delete; - ProfilerManager& operator=(const ProfilerManager&) = delete; + /// @brief Get the log manager (for internal use by log macros) + /// @return Reference to the internal LogManager + internal::LogManager& logManager() { + return *log_manager_; + } +private: std::string findLatestHeapProfile(const std::string& dir); /// @brief Generate flame graph from collapsed stack format @@ -196,14 +217,16 @@ class ProfilerManager { void installSignalHandler(); /// @brief Restore old signal handler - void restoreSignalHandler(); + static void restoreSignalHandler(); std::string profile_dir_; ///< Directory for profile outputs std::map profiler_states_; ///< Current profiler states mutable std::mutex mutex_; ///< Mutex for thread safety + std::unique_ptr log_manager_; ///< Per-instance log manager (PIMPL) std::atomic cpu_profiling_in_progress_{false}; ///< CPU profiling concurrency control std::unique_ptr symbolizer_; ///< Symbolizer instance + bool signal_handler_installed_{false}; ///< Whether signal handler has been installed static std::atomic capture_in_progress_; ///< Stack capture in progress flag static SharedStackTrace* shared_stacks_; ///< Shared stack trace array diff --git a/include/web_server.h b/include/web_server.h deleted file mode 100644 index 4af0aef..0000000 --- a/include/web_server.h +++ /dev/null @@ -1,31 +0,0 @@ -/// @file web_server.h -/// @brief HTTP server setup and route registration - -#pragma once - -#include "profiler_manager.h" -#include "profiler_version.h" -#include - -PROFILER_NAMESPACE_BEGIN - -/// @brief Register all HTTP route handlers -/// -/// This function registers all profiling-related API endpoints and web pages. -/// Call this function after initializing ProfilerManager to enable -/// the web interface for remote profiling. -/// -/// @param profiler Reference to the ProfilerManager instance to use -/// -/// @par Endpoints registered: -/// - /pprof/profile - CPU profile (pprof compatible) -/// - /pprof/heap - Heap profile (pprof compatible) -/// - /pprof/growth - Heap growth stacks -/// - /pprof/symbol - Symbol lookup -/// - /api/cpu/analyze - CPU flame graph analysis -/// - /api/heap/analyze - Heap flame graph analysis -/// - /api/thread/stacks - Thread stack capture -/// - / - Web UI main page -void registerHttpHandlers(profiler::ProfilerManager& profiler); - -PROFILER_NAMESPACE_END diff --git a/plan.md b/plan.md index df08f0e..a68ca61 100644 --- a/plan.md +++ b/plan.md @@ -19,9 +19,9 @@ #### 设计目标 - **可扩展性**: 允许用户注入自定义日志 sink,集成到应用的日志系统 -- **零依赖默认**: 默认使用 spdlog 输出到 stderr,无需配置即可使用 +- **零依赖默认**: 默认使用 std::cout/std::cerr 输出,无需配置即可使用 - **线程安全**: 支持多线程并发日志输出 -- **fmt 风格格式化**: 使用 `{}` 占位符,类型安全 +- **std::format 格式化**: 使用 C++20 `std::format`,类型安全 #### 架构设计 ``` @@ -29,9 +29,9 @@ ↓ PROFILER_INFO("msg: {}", value) ↓ -LogManager (单例) +LogManager (实例持有,由 ProfilerManager 拥有) ↓ - ├── DefaultLogSink (默认) → spdlog → stderr + ├── DefaultLogSink (默认) → std::cout/std::cerr └── CustomLogSink (用户注入) → 应用日志系统 ``` @@ -54,13 +54,9 @@ public: } // namespace profiler -// include/profiler/logger.h -namespace profiler { - -void setSink(std::shared_ptr sink); -void setLogLevel(LogLevel level); - -} // namespace profiler +// include/profiler/logger.h (removed — logging is now configured via ProfilerManager instance) +// profiler::ProfilerManager::setLogSink(std::shared_ptr sink) +// profiler::ProfilerManager::setLogLevel(LogLevel level) ``` #### 内部日志宏 @@ -75,7 +71,7 @@ void setLogLevel(LogLevel level); #### 用户集成示例 ```cpp #include -#include +#include "profiler_manager.h" // 1. 自定义 sink 集成到应用日志 class MyAppLogSink : public profiler::LogSink { @@ -86,25 +82,26 @@ class MyAppLogSink : public profiler::LogSink { } }; -// 2. 设置自定义 sink -profiler::setSink(std::make_shared()); +// 2. 创建 ProfilerManager 并设置自定义 sink +profiler::ProfilerManager profiler; +profiler.setLogSink(std::make_shared()); // 3. 可选:调整日志级别 -profiler::setLogLevel(profiler::LogLevel::Debug); +profiler.setLogLevel(profiler::LogLevel::Debug); ``` #### 文件结构 ``` include/profiler/ ├── log_sink.h # LogSink 接口 + LogLevel 枚举 -└── logger.h # setSink() + setLogLevel() +└── logger.h # 仅 log 宏声明(日志配置移到 ProfilerManager 实例方法) src/internal/ -├── log_manager.h # 内部状态管理 +├── log_manager.h # 内部状态管理(非单例,由 ProfilerManager 持有) ├── log_manager.cpp -├── default_log_sink.h # 默认实现(基于 spdlog) +├── default_log_sink.h # 默认实现(基于 std::cout/cerr) ├── default_log_sink.cpp -└── log_macros.h # 内部日志宏 +└── log_macros.h # 内部日志宏(使用 std::format) ``` ### 2. 两种使用场景 @@ -1392,8 +1389,256 @@ docs/README.md - ✅ 改进符号化系统 - ✅ 添加 collapsed 格式支持 +### Drogon 解耦 & 可选依赖重构 (2026-04) + +#### 背景 + +Gemini 对本项目的锐评中指出核心问题: +1. **框架依赖过重** — Drogon 作为 Web 框架对 profiler 库来说太重,与其他框架冲突 +2. **公共头文件暴露 Drogon** — `web_server.h` 直接 `#include ` +3. **spdlog 强依赖** — 默认日志实现绑定 spdlog,用户不需要也得装 +4. **符号可见性未控制** — 没有设置 `CXX_VISIBILITY_PRESET hidden` +5. **信号处理侵入性** — 构造函数自动安装信号处理器 + +#### 设计方案 + +##### 1. 编译目标拆分 + +``` +profiler_core ← 核心库,依赖:gperftools, absl, backward-cpp + 包含:ProfilerManager, ProfilerHttpHandlers, 日志系统 + 不依赖:Drogon, spdlog +profiler_web (可选) ← Drogon 适配层,依赖:Drogon + 包含:drogon_adapter.cpp, web_resources.cpp +profiler_example ← 示例程序 +``` + +用户只需要 profiling 功能时: +```cmake +find_package(cpp-remote-profiler REQUIRED) +target_link_libraries(myapp cpp-remote-profiler::profiler_core) +# 不需要 Drogon,不需要 spdlog +``` + +用户需要 Web 界面时: +```cmake +target_link_libraries(myapp cpp-remote-profiler::profiler_web) +# 自动带上 Drogon +``` + +##### 2. ProfilerHttpHandlers(框架无关的 handler) + +`include/profiler/http_handlers.h` 提供框架无关的 handler 方法: + +```cpp +// HandlerResponse 是纯 C++ 结构体,不依赖任何 Web 框架 +struct HandlerResponse { + int status = 200; + std::string content_type = "text/plain"; + std::string body; + std::map headers; + + static HandlerResponse html(const std::string& content); + static HandlerResponse json(const std::string& content); + static HandlerResponse svg(const std::string& content); + static HandlerResponse text(const std::string& content); + static HandlerResponse binary(const std::string& data, const std::string& filename); + static HandlerResponse error(int status, const std::string& message); +}; + +class ProfilerHttpHandlers { +public: + explicit ProfilerHttpHandlers(ProfilerManager& profiler); + HandlerResponse handleStatus(); + HandlerResponse handleCpuAnalyze(int duration, const std::string& output_type); + HandlerResponse handlePprofProfile(int seconds); + // ... 其他 handler 方法 +}; +``` + +用户可以将其与任意 Web 框架集成: +```cpp +profiler::ProfilerManager profiler; +profiler::ProfilerHttpHandlers handlers(profiler); +profiler::HandlerResponse resp = handlers.handleCpuAnalyze(10, "flamegraph"); +// 用你自己的框架包装 resp.status, resp.content_type, resp.body +``` + +##### 3. Drogon 适配层 + +`include/profiler/drogon_adapter.h` + `src/drogon_adapter.cpp`: +- 唯一 `#include ` 的地方 +- 将 ProfilerHttpHandlers 的返回值包装成 Drogon response +- 提供 `registerDrogonHandlers(profiler)` 一键注册函数 + +##### 4. spdlog 完全移除 + +- `DefaultLogSink` 改为使用 `std::cout`/`std::cerr` 输出 +- 日志格式化从 `spdlog/fmt/fmt.h` 切换到 C++20 `std::format` +- spdlog 不再是任何编译目标的依赖 + +##### 5. drogon_adapter.h(原 web_server.h) + +```cpp +// include/profiler/drogon_adapter.h — 唯一需要 Drogon 的公共头文件 +void registerDrogonHandlers(profiler::ProfilerManager& profiler); +``` + +##### 6. CMake 修改 + +```cmake +# 核心库(始终编译) +add_library(profiler_core ...) +target_link_libraries(profiler_core + PRIVATE Backward::Backward absl::symbolize absl::stacktrace ... + PUBLIC ${GPERFTOOLS_LIBRARIES} +) + +# Web 后端(可选) +option(REMOTE_PROFILER_ENABLE_WEB "Enable web UI (requires Drogon)" ON) +if(REMOTE_PROFILER_ENABLE_WEB) + find_package(Drogon CONFIG REQUIRED) + add_library(profiler_web ...) + target_link_libraries(profiler_web PUBLIC profiler_core PRIVATE Drogon::Drogon) +endif() + +# 符号可见性 +set_target_properties(profiler_core PROPERTIES CXX_VISIBILITY_PRESET hidden) +``` + +##### 7. 去掉单例模式 + +ProfilerManager 改为普通类(已完成): + +```cpp +// 之前:单例 +auto& profiler = profiler::ProfilerManager::getInstance(); +profiler.startCPUProfiler("cpu.prof"); + +// 之后:普通对象,用户管理生命周期 +profiler::ProfilerManager profiler; +profiler.startCPUProfiler("cpu.prof"); +``` + +日志配置从全局 LogManager 单例改为实例持有(已完成): + +```cpp +// 之前:全局函数 +profiler::setSink(mySink); + +// 之后:实例方法 +profiler::ProfilerManager profiler; +profiler.setLogSink(mySink); +``` + +信号处理 static 状态压缩到最小:只保留一个全局指针指向当前活跃的 ProfilerManager +实例,其余信号相关状态移到实例成员里。 + +##### 8. Example 改造 + +```cpp +#include "profiler_manager.h" +#include "profiler/drogon_adapter.h" + +profiler::ProfilerManager profiler; +profiler::registerDrogonHandlers(profiler); +drogon::app().addListener(host, port).run(); +``` + +#### 文件变更清单 + +| 操作 | 文件 | 说明 | +|------|------|------| +| 新增 | `include/profiler/http_server.h` | HttpServer 抽象接口 | +| 修改 | `include/profiler_manager.h` | 去掉单例,改为普通类;日志方法移入实例 | +| 修改 | `include/web_server.h` | 移除 Drogon include,改为接收 HttpServer& | +| 修改 | `include/profiler/logger.h` | 移除全局 setSink/setLogLevel,改为实例方法 | +| 新增 | `src/backends/drogon/drogon_http_server.h` | Drogon 适配器声明 | +| 新增 | `src/backends/drogon/drogon_http_server.cpp` | Drogon 适配器实现 | +| 修改 | `src/web_server.cpp` | 使用 HttpServer 抽象 | +| 修改 | `src/profiler_manager.cpp` | 去掉单例;构造函数不自动安装信号 | +| 修改 | `src/internal/default_log_sink.h` | 去掉 spdlog | +| 修改 | `src/internal/default_log_sink.cpp` | 去掉 spdlog,改用 std::cout/cerr | +| 修改 | `src/internal/log_macros.h` | 去掉 spdlog/fmt,改用 std::format | +| 修改 | `src/internal/log_manager.h` | 去掉单例,改为实例持有 | +| 修改 | `src/internal/log_manager.cpp` | 去掉单例 | +| 修改 | `CMakeLists.txt` | 拆分 target、添加选项、去掉 spdlog | +| 修改 | `example/main.cpp` | 使用新接口(普通对象) | +| 修改 | `vcpkg.json` | 移除 spdlog | +| 修改 | `tests/test_logger.cpp` | 适配新接口 | +| 修改 | `tests/test_full_flow.cpp` | 适配非单例 ProfilerManager | + +#### 当前进度(分支: refactor/decouple-drogon-remove-singleton) + +**已完成:** + +| # | 任务 | Commit | 说明 | +|---|------|--------|------| +| 1 | 移除 spdlog 依赖 | a83787a | `DefaultLogSink` 改用 `std::cout`/`std::cerr`,无 PIMPL | +| 2 | ~~创建 HttpServer 抽象接口~~ | a83787a | 后被废弃,改用 ProfilerHttpHandlers 方案 | +| 3 | LogManager 去单例 | a83787a | 改为普通类,由 ProfilerManager 实例持有 | +| 4 | log macros 去 spdlog/fmt | a83787a | 改用 `std::format`,通过 `log_manager_` 成员访问 | +| 5 | ProfilerManager 去单例 | aed09d0 | 删除 `getInstance()`,构造/析构改 public,`log_manager_` 改为 `unique_ptr` (PIMPL),信号处理改为 lazy install | +| 6 | ~~创建 Drogon 适配器~~ | aed09d0 | 后被删除,改用 ProfilerHttpHandlers 方案 | +| 7 | 创建 ProfilerHttpHandlers | HEAD | `include/profiler/http_handlers.h` + `src/http_handlers.cpp`,框架无关的 handler 类 | +| 8 | 重构 web_server | HEAD | 简化为 Drogon 薄适配层,调用 ProfilerHttpHandlers 方法 | +| 9 | 删除旧 HttpServer 抽象 | HEAD | 删除 `include/profiler/http_server.h` 和 `src/backends/drogon/` | +| 10 | CMake 拆分 target | HEAD | `profiler_core`(含 http_handlers) + `profiler_web`(Drogon 薄适配) | +| 11 | 更新 example | HEAD | 使用 `registerHttpHandlers(profiler)` + `drogon::app().run()` | +| 12 | 编译验证 & 测试 | HEAD | 18 个测试全部通过 | + +**设计决策:ProfilerHttpHandlers 方案** + +用户反馈 HttpServer 抽象方案有根本缺陷:用户如果已有 Web 框架(监听端口、管理路由),则无法集成 `HttpServer` 接口。 + +新方案: +- `ProfilerHttpHandlers` 类提供框架无关的 handler 方法 +- 每个 handler 返回 `HandlerResponse` 结构体 (`status`, `content_type`, `body`, `headers`) +- 用户用任意框架包装 `HandlerResponse` 到自己的 response 对象 +- Drogon 集成变为 `web_server.cpp` 中的薄适配层 + +```cpp +// 用户代码(任意框架): +ProfilerManager profiler; +ProfilerHttpHandlers handlers(profiler); + +// 调用 handler,获得框架无关的响应 +HandlerResponse resp = handlers.handleCpuAnalyze(10, "flamegraph"); + +// 用自己的框架包装响应 +yourFrameworkResponse.setStatus(resp.status); +yourFrameworkResponse.setContentType(resp.content_type); +yourFrameworkResponse.setBody(resp.body); +``` + +**文件变更清单:** + +| 操作 | 文件 | 说明 | +|------|------|------| +| 新增 | `include/profiler/http_handlers.h` | HandlerResponse 结构体 + ProfilerHttpHandlers 类声明 | +| 新增 | `src/http_handlers.cpp` | 所有 profiler endpoint 业务逻辑(框架无关) | +| 修改 | `include/profiler/drogon_adapter.h`(原 web_server.h) | Drogon 适配层,提供 `registerDrogonHandlers()` | +| 修改 | `src/drogon_adapter.cpp`(原 web_server.cpp) | Drogon 薄适配:调用 ProfilerHttpHandlers + WebResources | +| 修改 | `CMakeLists.txt` | `profiler_core` 加入 http_handlers.cpp,移除 backends/ | +| 修改 | `example/main.cpp` | 使用 `registerDrogonHandlers(profiler)` + `drogon::app().run()` | +| 删除 | `include/profiler/http_server.h` | 旧 HttpServer 抽象接口 | +| 删除 | `src/backends/drogon/` | 旧 Drogon 适配器 | +| 删除 | `include/web_server.h` | 旧文件名,已重命名为 drogon_adapter.h | +| 删除 | `src/web_server.cpp` | 旧文件名,已重命名为 drogon_adapter.cpp | + +**测试结果:** +- LoggerTest: 7/7 通过 +- FullFlowTest: 6/6 通过 +- CPUProfileTest + ProfilerManagerTest: 5/5 通过 + +#### 不在本次范围内(后续迭代) + +- `SharedStackTrace`/`ThreadStackTrace` 从公共头文件移到内部 +- ProfilerManager 的 PIMPL 化 +- `dispatch()` 方法的实现(在 http_handlers.h 中声明但未实现) + --- -**文档版本**: 1.1 -**最后更新**: 2026-01-13 +**文档版本**: 1.3 +**最后更新**: 2026-04-07 **维护者**: Claude Code diff --git a/src/drogon_adapter.cpp b/src/drogon_adapter.cpp new file mode 100644 index 0000000..9cbb57a --- /dev/null +++ b/src/drogon_adapter.cpp @@ -0,0 +1,201 @@ +/// @file drogon_adapter.cpp +/// @brief Drogon integration: registers profiler routes via ProfilerHttpHandlers + +#include "profiler/drogon_adapter.h" +#include "internal/web_resources.h" +#include "profiler/http_handlers.h" +#include +#include + +PROFILER_NAMESPACE_BEGIN + +/// Helper: adapt HandlerResponse to Drogon HttpResponse +static void sendResponse(const HandlerResponse& hr, std::function&& callback) { + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setStatusCode(static_cast(hr.status)); + resp->setBody(hr.body); + + if (hr.content_type == "text/html") { + resp->setContentTypeCode(drogon::CT_TEXT_HTML); + } else if (hr.content_type == "application/json") { + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + } else if (hr.content_type == "image/svg+xml" || hr.content_type == "text/xml") { + resp->setContentTypeCode(drogon::CT_TEXT_XML); + } else if (hr.content_type == "application/octet-stream") { + resp->setContentTypeCode(drogon::CT_APPLICATION_OCTET_STREAM); + } else { + resp->setContentTypeCode(drogon::CT_TEXT_PLAIN); + } + + for (auto& [key, val] : hr.headers) { + resp->addHeader(key, val); + } + + callback(resp); +} + +void registerDrogonHandlers(profiler::ProfilerManager& profiler) { + auto handlers = std::make_shared(profiler); + + // --- GET routes --- + auto registerGet = [&](const std::string& path, auto fn) { + drogon::app().registerHandler( + path, + [handlers, fn = std::move(fn)]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + sendResponse(((*handlers).*fn)(), std::move(callback)); + }, + {drogon::Get}); + }; + + // --- Static pages (served directly via WebResources) --- + drogon::app().registerHandler("/", + []([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + sendResponse(HandlerResponse::html(WebResources::getIndexPage()), + std::move(callback)); + }, + {drogon::Get}); + drogon::app().registerHandler("/show_svg.html", + []([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + sendResponse(HandlerResponse::html(WebResources::getCpuSvgViewerPage()), + std::move(callback)); + }, + {drogon::Get}); + drogon::app().registerHandler("/show_heap_svg.html", + []([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + sendResponse(HandlerResponse::html(WebResources::getHeapSvgViewerPage()), + std::move(callback)); + }, + {drogon::Get}); + drogon::app().registerHandler("/show_growth_svg.html", + []([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + sendResponse(HandlerResponse::html(WebResources::getGrowthSvgViewerPage()), + std::move(callback)); + }, + {drogon::Get}); + + // --- Status --- + registerGet("/api/status", &ProfilerHttpHandlers::handleStatus); + + // --- Thread stacks --- + registerGet("/api/thread/stacks", &ProfilerHttpHandlers::handleThreadStacks); + + // --- Standard pprof: /pprof/profile --- + drogon::app().registerHandler("/pprof/profile", + [handlers]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + int seconds = 30; + auto p = req->getParameter("seconds"); + if (!p.empty()) { + try { + seconds = std::stoi(p); + } catch (...) {} + if (seconds < 1) + seconds = 1; + if (seconds > 300) + seconds = 300; + } + sendResponse(handlers->handlePprofProfile(seconds), std::move(callback)); + }, + {drogon::Get}); + + // --- Standard pprof: /pprof/heap --- + registerGet("/pprof/heap", &ProfilerHttpHandlers::handlePprofHeap); + + // --- Standard pprof: /pprof/growth --- + registerGet("/pprof/growth", &ProfilerHttpHandlers::handlePprofGrowth); + + // --- /pprof/symbol (POST) --- + drogon::app().registerHandler("/pprof/symbol", + [handlers]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + sendResponse(handlers->handlePprofSymbol(std::string(req->body())), + std::move(callback)); + }, + {drogon::Post}); + + // --- CPU analyze --- + drogon::app().registerHandler("/api/cpu/analyze", + [handlers]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + int duration = 10; + auto dp = req->getParameter("duration"); + if (!dp.empty()) { + try { + duration = std::stoi(dp); + } catch (...) {} + } + std::string output_type = req->getParameter("output_type"); + if (output_type.empty()) + output_type = "pprof"; + sendResponse(handlers->handleCpuAnalyze(duration, output_type), + std::move(callback)); + }, + {drogon::Get, drogon::Post}); + + // --- CPU raw SVG --- + drogon::app().registerHandler("/api/cpu/svg_raw", + [handlers]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + int duration = 10; + auto dp = req->getParameter("duration"); + if (!dp.empty()) { + try { + duration = std::stoi(dp); + } catch (...) {} + } + sendResponse(handlers->handleCpuSvgRaw(duration), std::move(callback)); + }, + {drogon::Get}); + + // --- CPU FlameGraph raw --- + drogon::app().registerHandler("/api/cpu/flamegraph_raw", + [handlers]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + int duration = 10; + auto dp = req->getParameter("duration"); + if (!dp.empty()) { + try { + duration = std::stoi(dp); + } catch (...) {} + } + sendResponse(handlers->handleCpuFlamegraphRaw(duration), std::move(callback)); + }, + {drogon::Get}); + + // --- Heap analyze --- + drogon::app().registerHandler("/api/heap/analyze", + [handlers]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + std::string output_type = req->getParameter("output_type"); + if (output_type.empty()) + output_type = "pprof"; + sendResponse(handlers->handleHeapAnalyze(output_type), std::move(callback)); + }, + {drogon::Get}); + + // --- Heap raw / FlameGraph --- + registerGet("/api/heap/svg_raw", &ProfilerHttpHandlers::handleHeapSvgRaw); + registerGet("/api/heap/flamegraph_raw", &ProfilerHttpHandlers::handleHeapFlamegraphRaw); + + // --- Growth analyze --- + drogon::app().registerHandler("/api/growth/analyze", + [handlers]([[maybe_unused]] const drogon::HttpRequestPtr& req, + std::function&& callback) { + std::string output_type = req->getParameter("output_type"); + if (output_type.empty()) + output_type = "pprof"; + sendResponse(handlers->handleGrowthAnalyze(output_type), std::move(callback)); + }, + {drogon::Get}); + + // --- Growth raw / FlameGraph --- + registerGet("/api/growth/svg_raw", &ProfilerHttpHandlers::handleGrowthSvgRaw); + registerGet("/api/growth/flamegraph_raw", &ProfilerHttpHandlers::handleGrowthFlamegraphRaw); +} + +PROFILER_NAMESPACE_END diff --git a/src/http_handlers.cpp b/src/http_handlers.cpp new file mode 100644 index 0000000..c8b1702 --- /dev/null +++ b/src/http_handlers.cpp @@ -0,0 +1,493 @@ +/// @file http_handlers.cpp +/// @brief Framework-agnostic HTTP endpoint handlers implementation + +#include "profiler/http_handlers.h" +#include "profiler_manager.h" +#include +#include +#include + +PROFILER_NAMESPACE_BEGIN + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +static HandlerResponse errorResp(int status, const std::string& message) { + return HandlerResponse::error(status, message); +} + +static bool validateOutputType(const std::string& output_type) { + return output_type == "flamegraph" || output_type == "pprof"; +} + +static int clampDuration(int duration, int lo, int hi) { + if (duration < lo) + return lo; + if (duration > hi) + return hi; + return duration; +} + +// --------------------------------------------------------------------------- +// ProfilerHttpHandlers +// --------------------------------------------------------------------------- + +ProfilerHttpHandlers::ProfilerHttpHandlers(ProfilerManager& profiler) : profiler_(profiler) {} + +// --- Status --- + +HandlerResponse ProfilerHttpHandlers::handleStatus() { + auto cpu = profiler_.getProfilerState(ProfilerType::CPU); + auto heap = profiler_.getProfilerState(ProfilerType::HEAP); + auto growth = profiler_.getProfilerState(ProfilerType::HEAP_GROWTH); + + std::ostringstream json; + json << "{"; + json << "\"cpu\":{\"running\":" << (cpu.is_running ? "true" : "false") << ",\"output_path\":\"" << cpu.output_path + << "\"" << ",\"duration_ms\":" << cpu.duration << "},"; + json << "\"heap\":{\"running\":" << (heap.is_running ? "true" : "false") << ",\"output_path\":\"" + << heap.output_path << "\"" << ",\"duration_ms\":" << heap.duration << "},"; + json << "\"growth\":{\"running\":" << (growth.is_running ? "true" : "false") << ",\"output_path\":\"" + << growth.output_path << "\"" << ",\"duration_ms\":" << growth.duration << "}"; + json << "}"; + + return HandlerResponse::json(json.str()); +} + +// --- CPU endpoints --- + +HandlerResponse ProfilerHttpHandlers::handleCpuAnalyze(int duration, const std::string& output_type) { + duration = clampDuration(duration, 1, 300); + + if (!validateOutputType(output_type)) { + return errorResp(400, "Invalid output_type. Must be 'flamegraph' or 'pprof'"); + } + + std::string svg = profiler_.analyzeCPUProfile(duration, output_type); + + if (svg.size() > 10 && svg[0] == '{' && svg[1] == '"') { + return errorResp(500, svg); + } + + return HandlerResponse::svg(svg); +} + +HandlerResponse ProfilerHttpHandlers::handleCpuSvgRaw(int duration) { + duration = clampDuration(duration, 1, 300); + + std::string profile_data = profiler_.getRawCPUProfile(duration); + if (profile_data.empty()) { + return errorResp(500, "Failed to generate CPU profile"); + } + + std::string temp_file = "/tmp/cpu_raw.prof"; + { + std::ofstream out(temp_file, std::ios::binary); + out.write(profile_data.data(), profile_data.size()); + } + + std::string exe_path = profiler_.getExecutablePath(); + std::string cmd = "./pprof --svg " + exe_path + " " + temp_file + " 2>/dev/null"; + std::string svg; + profiler_.executeCommand(cmd, svg); + + size_t pos = svg.find(" 0) + svg = svg.substr(pos); + + if (svg.empty() || svg.find(" " << collapsed_file << " 2>&1"; + std::string out; + if (!profiler_.executeCommand(cmd.str(), out)) { + return errorResp(500, "Failed to execute pprof --collapsed command"); + } + + // Verify collapsed data + std::ifstream in(collapsed_file); + if (!in.is_open()) + return errorResp(500, "Failed to create collapsed file"); + std::string line; + bool has_data = false; + while (std::getline(in, line)) { + if (!line.empty() && line[0] != '#') { + has_data = true; + break; + } + } + in.close(); + if (!has_data) + return errorResp(500, "pprof --collapsed produced no data."); + + std::string fg_cmd = + "perl ./flamegraph.pl --title=\"CPU Flame Graph\" --width=1200 " + collapsed_file + " 2>/dev/null"; + std::string svg; + profiler_.executeCommand(fg_cmd, svg); + + if (svg.find(" 10 && svg[0] == '{' && svg[1] == '"') { + return errorResp(500, "Failed to generate heap flame graph"); + } + + return HandlerResponse::svg(svg); +} + +HandlerResponse ProfilerHttpHandlers::handleHeapSvgRaw() { + std::string heap_sample = profiler_.getRawHeapSample(); + if (heap_sample.empty()) { + return errorResp(500, "Failed to get heap sample. Make sure TCMALLOC_SAMPLE_PARAMETER is set."); + } + + std::string temp_file = "/tmp/heap_raw.prof"; + { + std::ofstream out(temp_file); + out << heap_sample; + } + + std::string exe_path = profiler_.getExecutablePath(); + std::string cmd = "./pprof --svg " + exe_path + " " + temp_file + " 2>/dev/null"; + std::string svg; + profiler_.executeCommand(cmd, svg); + + size_t pos = svg.find(" 0) + svg = svg.substr(pos); + + if (svg.empty() || svg.find(" " << collapsed_file << " 2>&1"; + std::string out; + if (!profiler_.executeCommand(cmd.str(), out)) { + return errorResp(500, "Failed to execute pprof --collapsed command"); + } + + std::ifstream in(collapsed_file); + if (!in.is_open()) + return errorResp(500, "Failed to create collapsed file"); + std::string line; + bool has_data = false; + while (std::getline(in, line)) { + if (!line.empty() && line[0] != '#') { + has_data = true; + break; + } + } + in.close(); + if (!has_data) + return errorResp(500, "pprof --collapsed produced no data"); + + std::string fg_cmd = + "perl ./flamegraph.pl --title=\"Heap Flame Graph\" --width=1200 " + collapsed_file + " 2>/dev/null"; + std::string svg; + profiler_.executeCommand(fg_cmd, svg); + + if (svg.find("(std::chrono::system_clock::now().time_since_epoch()).count()); + resp.headers["Content-Disposition"] = "attachment; filename=heap_flamegraph_" + ts + ".svg"; + return resp; +} + +// --- Growth endpoints --- + +HandlerResponse ProfilerHttpHandlers::handleGrowthAnalyze(const std::string& output_type) { + if (!validateOutputType(output_type)) { + return errorResp(400, "Invalid output_type. Must be 'flamegraph' or 'pprof'"); + } + + std::string growth = profiler_.getRawHeapGrowthStacks(); + if (growth.empty()) { + return errorResp(500, "Failed to get heap growth stacks. No heap growth data available."); + } + + std::string temp_file = "/tmp/growth_sample.prof"; + { + std::ofstream out(temp_file); + out << growth; + } + + std::string exe_path = profiler_.getExecutablePath(); + std::string svg; + + if (output_type == "flamegraph") { + std::string collapsed_file = "/tmp/growth_collapsed.prof"; + std::ostringstream cmd; + cmd << "./pprof --collapsed " << exe_path << " " << temp_file << " > " << collapsed_file << " 2>/dev/null"; + + std::string out; + if (!profiler_.executeCommand(cmd.str(), out)) { + return errorResp(500, "Failed to generate collapsed format"); + } + + std::ostringstream fg; + fg << "perl ./flamegraph.pl --title=\"Heap Growth Flame Graph\" --width=1200 " << collapsed_file + << " 2>/dev/null"; + if (!profiler_.executeCommand(fg.str(), svg)) { + return errorResp(500, "Failed to execute flamegraph.pl command"); + } + + if (svg.find("&1"; + if (!profiler_.executeCommand(cmd.str(), svg)) { + return errorResp(500, "Failed to execute pprof command"); + } + + size_t svg_start = svg.find("", svg_start); + if (tag_end != std::string::npos) { + std::string tag = svg.substr(svg_start, tag_end - svg_start); + if (tag.find("viewBox") == std::string::npos) { + svg.insert(tag_end, " viewBox=\"0 -1000 2000 1000\""); + } + } + } + } + + return HandlerResponse::svg(svg); +} + +HandlerResponse ProfilerHttpHandlers::handleGrowthSvgRaw() { + std::string growth = profiler_.getRawHeapGrowthStacks(); + if (growth.empty()) { + return errorResp(500, "Failed to get heap growth stacks. No heap growth data available."); + } + + std::string temp_file = "/tmp/growth_raw.prof"; + { + std::ofstream out(temp_file); + out << growth; + } + + std::string exe_path = profiler_.getExecutablePath(); + std::string cmd = "./pprof --svg " + exe_path + " " + temp_file + " 2>/dev/null"; + std::string svg; + profiler_.executeCommand(cmd, svg); + + size_t pos = svg.find(" 0) + svg = svg.substr(pos); + + if (svg.empty() || svg.find(" " << collapsed_file << " 2>&1"; + std::string out; + if (!profiler_.executeCommand(cmd.str(), out)) { + return errorResp(500, "Failed to execute pprof --collapsed command"); + } + + std::ifstream in(collapsed_file); + if (!in.is_open()) + return errorResp(500, "Failed to create collapsed file"); + std::string line; + bool has_data = false; + while (std::getline(in, line)) { + if (!line.empty() && line[0] != '#') { + has_data = true; + break; + } + } + in.close(); + if (!has_data) + return errorResp(500, "pprof --collapsed produced no data"); + + std::string fg_cmd = + "perl ./flamegraph.pl --title=\"Heap Growth Flame Graph\" --width=1200 " + collapsed_file + " 2>/dev/null"; + std::string svg; + profiler_.executeCommand(fg_cmd, svg); + + if (svg.find("(std::chrono::system_clock::now().time_since_epoch()).count()); + resp.headers["Content-Disposition"] = "attachment; filename=growth_flamegraph_" + ts + ".svg"; + return resp; +} + +// --- Standard pprof --- + +HandlerResponse ProfilerHttpHandlers::handlePprofProfile(int seconds) { + seconds = clampDuration(seconds, 1, 300); + + std::string data = profiler_.getRawCPUProfile(seconds); + if (data.empty()) { + return errorResp(500, "Failed to generate CPU profile"); + } + + auto resp = HandlerResponse::binary(data, "profile"); + resp.content_type = "application/octet-stream"; + return resp; +} + +HandlerResponse ProfilerHttpHandlers::handlePprofHeap() { + std::string data = profiler_.getRawHeapSample(); + if (data.empty()) { + return errorResp(500, "Failed to get heap sample. Make sure TCMALLOC_SAMPLE_PARAMETER is set."); + } + + HandlerResponse resp; + resp.status = 200; + resp.content_type = "text/plain"; + resp.body = data; + resp.headers["Content-Disposition"] = "attachment; filename=heap"; + return resp; +} + +HandlerResponse ProfilerHttpHandlers::handlePprofGrowth() { + std::string data = profiler_.getRawHeapGrowthStacks(); + if (data.empty()) { + return errorResp(500, "Failed to get heap growth stacks. No heap growth data available."); + } + + HandlerResponse resp; + resp.status = 200; + resp.content_type = "text/plain"; + resp.body = data; + resp.headers["Content-Disposition"] = "attachment; filename=growth"; + return resp; +} + +HandlerResponse ProfilerHttpHandlers::handlePprofSymbol(const std::string& body) { + std::istringstream iss(body); + std::string address; + std::ostringstream result; + + while (std::getline(iss, address)) { + if (address.empty() || address[0] == '#') + continue; + + std::string original = address; + std::string addr_str = address; + if (addr_str.size() > 2 && addr_str[0] == '0' && addr_str[1] == 'x') { + addr_str = addr_str.substr(2); + } + + try { + uintptr_t addr = std::stoull(addr_str, nullptr, 16); + std::string symbol = profiler_.resolveSymbolWithBackward(reinterpret_cast(addr)); + result << original << " " << symbol << "\n"; + } catch (...) { + result << original << " " << original << "\n"; + } + } + + return HandlerResponse::text(result.str()); +} + +// --- Thread stacks --- + +HandlerResponse ProfilerHttpHandlers::handleThreadStacks() { + std::string stacks = profiler_.getThreadCallStacks(); + if (stacks.empty()) { + return errorResp(500, "Failed to get thread call stacks"); + } + return HandlerResponse::text(stacks); +} + +PROFILER_NAMESPACE_END diff --git a/src/internal/default_log_sink.cpp b/src/internal/default_log_sink.cpp index 0baba12..b5cbe84 100644 --- a/src/internal/default_log_sink.cpp +++ b/src/internal/default_log_sink.cpp @@ -1,104 +1,92 @@ /// @file default_log_sink.cpp -/// @brief Default log sink implementation using spdlog +/// @brief Default log sink implementation using std::cout/std::cerr #include "default_log_sink.h" -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include PROFILER_NAMESPACE_BEGIN namespace internal { -// Helper to convert profiler LogLevel to spdlog level -static spdlog::level::level_enum toSpdlogLevel(LogLevel level) { +static const char* levelToString(LogLevel level) { switch (level) { case LogLevel::Trace: - return spdlog::level::trace; + return "TRACE"; case LogLevel::Debug: - return spdlog::level::debug; + return "DEBUG"; case LogLevel::Info: - return spdlog::level::info; + return "INFO"; case LogLevel::Warning: - return spdlog::level::warn; + return "WARN"; case LogLevel::Error: - return spdlog::level::err; + return "ERROR"; case LogLevel::Fatal: - return spdlog::level::critical; + return "FATAL"; default: - return spdlog::level::info; + return "UNKNOWN"; } } -class DefaultLogSink::Impl { -public: - Impl() { - // Create stderr sink with color - auto stderr_sink = std::make_shared(); +static std::string currentTimestamp() { + auto now = std::chrono::system_clock::now(); + auto time_t_now = std::chrono::system_clock::to_time_t(now); + auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; - // Create logger with custom pattern - // Format: [timestamp] [level] [source_location] message - logger_ = std::make_shared("profiler", stderr_sink); - logger_->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%-7l%$] [%s:%#] %v"); - logger_->set_level(spdlog::level::trace); // Let LogManager handle filtering + std::tm tm_buf{}; + localtime_r(&time_t_now, &tm_buf); - // Register as default logger (optional, for spdlog internal use) - spdlog::register_logger(logger_); - } - - ~Impl() { - spdlog::drop("profiler"); - } + char buf[32]; + std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm_buf); - void log(LogLevel level, const char* file, int line, const char* function, const char* message) { - // Extract just the filename from the full path - const char* filename = file; - if (const char* last_slash = strrchr(file, '/')) { - filename = last_slash + 1; - } + return std::string(buf) + "." + std::to_string(ms.count()); +} - // Create source location for spdlog - spdlog::source_loc source_loc{filename, line, function}; +DefaultLogSink::DefaultLogSink() = default; - // Log with spdlog - logger_->log(source_loc, toSpdlogLevel(level), "{}", message); +DefaultLogSink::~DefaultLogSink() = default; - // For fatal errors, flush immediately - if (level == LogLevel::Fatal) { - logger_->flush(); - } +void DefaultLogSink::log(LogLevel level, const char* file, int line, [[maybe_unused]] const char* function, + const char* message) { + if (static_cast(level) < static_cast(min_level_)) { + return; } - void flush() { - logger_->flush(); + // Extract filename from path + const char* filename = file; + if (const char* last_slash = strrchr(file, '/')) { + filename = last_slash + 1; } - void setLogLevel(LogLevel level) { - logger_->set_level(toSpdlogLevel(level)); - } + std::lock_guard lock(mutex_); -private: - std::shared_ptr logger_; -}; + // Format: [timestamp] [LEVEL] [file:line] message + std::string timestamp = currentTimestamp(); -DefaultLogSink::DefaultLogSink() : impl_(std::make_unique()) {} + // Use stderr for warnings and above, stdout for the rest + auto& stream = (static_cast(level) >= static_cast(LogLevel::Warning)) ? std::cerr : std::cout; -DefaultLogSink::~DefaultLogSink() = default; + stream << "[" << timestamp << "] [" << levelToString(level) << "] [" << filename << ":" << line << "] " << message + << std::endl; -void DefaultLogSink::log(LogLevel level, const char* file, int line, const char* function, const char* message) { - std::lock_guard lock(mutex_); - impl_->log(level, file, line, function, message); + if (level == LogLevel::Fatal) { + stream.flush(); + } } void DefaultLogSink::flush() { std::lock_guard lock(mutex_); - impl_->flush(); + std::cout.flush(); + std::cerr.flush(); } void DefaultLogSink::setLogLevel(LogLevel level) { std::lock_guard lock(mutex_); - impl_->setLogLevel(level); + min_level_ = level; } } // namespace internal diff --git a/src/internal/default_log_sink.h b/src/internal/default_log_sink.h index 07cb056..4cc4f2c 100644 --- a/src/internal/default_log_sink.h +++ b/src/internal/default_log_sink.h @@ -1,5 +1,5 @@ /// @file default_log_sink.h -/// @brief Default log sink implementation using spdlog +/// @brief Default log sink implementation using std::cout/std::cerr #pragma once @@ -12,7 +12,7 @@ PROFILER_NAMESPACE_BEGIN namespace internal { /// @class DefaultLogSink -/// @brief Default log sink that outputs to stderr using spdlog +/// @brief Default log sink that outputs to stdout/stderr class DefaultLogSink : public LogSink { public: DefaultLogSink(); @@ -27,8 +27,7 @@ class DefaultLogSink : public LogSink { private: std::mutex mutex_; - class Impl; - std::unique_ptr impl_; + LogLevel min_level_ = LogLevel::Info; }; } // namespace internal diff --git a/src/internal/log_macros.h b/src/internal/log_macros.h index 7757cf3..6b5cb16 100644 --- a/src/internal/log_macros.h +++ b/src/internal/log_macros.h @@ -1,42 +1,40 @@ -/// @file log_macros.h -/// @brief Internal logging macros - #pragma once #include "log_manager.h" -#include +#include +#include PROFILER_NAMESPACE_BEGIN namespace internal { -// Helper template to format message using fmt -template std::string formatMessage(fmt::format_string fmt, Args&&... args) { - return fmt::format(fmt, std::forward(args)...); +template std::string formatMessage(std::format_string fmt, Args&&... args) { + return std::format(fmt, std::forward(args)...); +} + +inline void logToManager(LogManager& mgr, LogLevel level, const char* file, int line, const char* function, + std::string&& message) { + mgr.sink()->log(level, file, line, function, message.c_str()); } } // namespace internal PROFILER_NAMESPACE_END -// Internal logging macro using fmt-style formatting +// The PROFILER_LOG_IMPL macro uses logManager() method which returns a reference to the internal LogManager. +// This method must be available in the calling context (i.e., ProfilerManager methods). #define PROFILER_LOG_IMPL(level, fmt_str, ...) \ do { \ - if (::profiler::internal::LogManager::instance().shouldLog(level)) { \ - ::profiler::internal::logMessage(level, __FILE__, __LINE__, __FUNCTION__, \ - ::profiler::internal::formatMessage(fmt_str, ##__VA_ARGS__)); \ + auto& _log_mgr = logManager(); \ + if (_log_mgr.shouldLog(level)) { \ + ::profiler::internal::logToManager(_log_mgr, level, __FILE__, __LINE__, __FUNCTION__, \ + ::profiler::internal::formatMessage(fmt_str, ##__VA_ARGS__)); \ } \ } while (0) -// User-facing logging macros (fmt-style: PROFILER_INFO("Message: {}", value)) #define PROFILER_TRACE(fmt_str, ...) PROFILER_LOG_IMPL(::profiler::LogLevel::Trace, fmt_str, ##__VA_ARGS__) - #define PROFILER_DEBUG(fmt_str, ...) PROFILER_LOG_IMPL(::profiler::LogLevel::Debug, fmt_str, ##__VA_ARGS__) - #define PROFILER_INFO(fmt_str, ...) PROFILER_LOG_IMPL(::profiler::LogLevel::Info, fmt_str, ##__VA_ARGS__) - #define PROFILER_WARNING(fmt_str, ...) PROFILER_LOG_IMPL(::profiler::LogLevel::Warning, fmt_str, ##__VA_ARGS__) - #define PROFILER_ERROR(fmt_str, ...) PROFILER_LOG_IMPL(::profiler::LogLevel::Error, fmt_str, ##__VA_ARGS__) - #define PROFILER_FATAL(fmt_str, ...) PROFILER_LOG_IMPL(::profiler::LogLevel::Fatal, fmt_str, ##__VA_ARGS__) diff --git a/src/internal/log_manager.cpp b/src/internal/log_manager.cpp index 3f27588..de8c488 100644 --- a/src/internal/log_manager.cpp +++ b/src/internal/log_manager.cpp @@ -1,6 +1,3 @@ -/// @file log_manager.cpp -/// @brief Internal log manager implementation - #include "log_manager.h" #include "default_log_sink.h" #include @@ -9,11 +6,6 @@ PROFILER_NAMESPACE_BEGIN namespace internal { -LogManager& LogManager::instance() { - static LogManager instance; - return instance; -} - LogManager::LogManager() : sink_(std::make_shared()) {} void LogManager::setSink(std::shared_ptr sink) { @@ -21,7 +13,6 @@ void LogManager::setSink(std::shared_ptr sink) { if (sink) { sink_ = std::move(sink); } else { - // Revert to default sink sink_ = std::make_shared(); } } @@ -43,19 +34,6 @@ bool LogManager::shouldLog(LogLevel level) const { return static_cast(level) >= static_cast(level_.load(std::memory_order_relaxed)); } -void logMessage(LogLevel level, const char* file, int line, const char* function, std::string&& message) { - LogManager::instance().sink()->log(level, file, line, function, message.c_str()); -} - } // namespace internal -// Implementation of public API functions (in profiler namespace) -void setSink(std::shared_ptr sink) { - internal::LogManager::instance().setSink(std::move(sink)); -} - -void setLogLevel(LogLevel level) { - internal::LogManager::instance().setLogLevel(level); -} - PROFILER_NAMESPACE_END diff --git a/src/internal/log_manager.h b/src/internal/log_manager.h index 273f2dd..eae693e 100644 --- a/src/internal/log_manager.h +++ b/src/internal/log_manager.h @@ -1,6 +1,3 @@ -/// @file log_manager.h -/// @brief Internal log manager implementation - #pragma once #include "profiler/log_sink.h" @@ -13,41 +10,35 @@ PROFILER_NAMESPACE_BEGIN namespace internal { -/// @class LogManager -/// @brief Internal singleton for managing log sink and level +/// Non-singleton log manager, owned by ProfilerManager instances class LogManager { public: - static LogManager& instance(); + LogManager(); + ~LogManager() = default; + LogManager(const LogManager&) = delete; + LogManager& operator=(const LogManager&) = delete; - /// @brief Set custom log sink + /// Set custom log sink void setSink(std::shared_ptr sink); - /// @brief Get current log sink (never nullptr) + /// Get current log sink (never nullptr) LogSink* sink(); - /// @brief Set minimum log level + /// Set minimum log level void setLogLevel(LogLevel level); - /// @brief Get current minimum log level + /// Get current minimum log level LogLevel logLevel() const; - /// @brief Check if a log level should be output + /// Check if a log level should be output bool shouldLog(LogLevel level) const; private: - LogManager(); - ~LogManager() = default; - LogManager(const LogManager&) = delete; - LogManager& operator=(const LogManager&) = delete; - std::shared_ptr sink_; std::atomic level_{LogLevel::Info}; mutable std::mutex mutex_; }; -/// @brief Internal function to write a log message (fmt-style formatting) -void logMessage(LogLevel level, const char* file, int line, const char* function, std::string&& message); - } // namespace internal PROFILER_NAMESPACE_END diff --git a/src/profiler_manager.cpp b/src/profiler_manager.cpp index 14910b8..40184d0 100644 --- a/src/profiler_manager.cpp +++ b/src/profiler_manager.cpp @@ -4,6 +4,7 @@ #include "internal/embed_flamegraph.h" #include "internal/embed_pprof.h" #include "internal/log_macros.h" +#include "internal/log_manager.h" #include "internal/symbolize.h" #include #include @@ -51,7 +52,7 @@ struct sigaction ProfilerManager::old_action_; bool ProfilerManager::old_action_saved_ = false; bool ProfilerManager::enable_signal_chaining_ = false; -ProfilerManager::ProfilerManager() { +ProfilerManager::ProfilerManager() : log_manager_(std::make_unique()) { // Write embedded pprof script to current directory writePprofScript("./pprof"); @@ -83,10 +84,7 @@ ProfilerManager::ProfilerManager() { // Initialize abseil symbolizer absl::InitializeSymbolizer(""); - // Note: shared_stacks_ will be allocated dynamically in captureAllThreadStacks() - - // Register signal handler for stack tracing (saves old handler) - installSignalHandler(); + // Note: Signal handler is installed lazily on first captureAllThreadStacks() call } ProfilerManager::~ProfilerManager() { @@ -97,35 +95,36 @@ ProfilerManager::~ProfilerManager() { IsHeapProfilerRunning(); } - // Restore old signal handler - restoreSignalHandler(); + // Restore old signal handler if we installed one + if (signal_handler_installed_) { + restoreSignalHandler(); + } +} - // Note: shared_stacks_ is allocated and freed in captureAllThreadStacks() +void ProfilerManager::setLogSink(std::shared_ptr sink) { + log_manager_->setSink(std::move(sink)); } -ProfilerManager& ProfilerManager::getInstance() { - static ProfilerManager instance; - return instance; +void ProfilerManager::setLogLevel(LogLevel level) { + log_manager_->setLogLevel(level); } void ProfilerManager::setStackCaptureSignal(int signal) { // Check if signal is valid if (signal < 1 || signal > SIGRTMAX) { - PROFILER_ERROR("Invalid signal number: {}", signal); + std::cerr << "[ERROR] Invalid signal number: " << signal << std::endl; return; } // If handler is already installed, restore old one first if (old_action_saved_) { - PROFILER_WARNING("Signal handler already installed for signal {}. Restoring before changing.", - stack_capture_signal_); - // Note: This won't work correctly if multiple instances exist, - // but it's a best-effort attempt - ProfilerManager::getInstance().restoreSignalHandler(); + std::cerr << "[WARN] Signal handler already installed for signal " << stack_capture_signal_ + << ". Restoring before changing." << std::endl; + restoreSignalHandler(); } stack_capture_signal_ = signal; - PROFILER_INFO("Stack capture signal set to: {}", signal); + std::cout << "[INFO] Stack capture signal set to: " << signal << std::endl; } int ProfilerManager::getStackCaptureSignal() { @@ -134,10 +133,14 @@ int ProfilerManager::getStackCaptureSignal() { void ProfilerManager::setSignalChaining(bool enable) { enable_signal_chaining_ = enable; - PROFILER_INFO("Signal chaining {}", enable ? "enabled" : "disabled"); + std::cout << "[INFO] Signal chaining " << (enable ? "enabled" : "disabled") << std::endl; } void ProfilerManager::installSignalHandler() { + if (signal_handler_installed_) { + return; // Already installed + } + struct sigaction sa; sa.sa_sigaction = &ProfilerManager::signalHandler; sa.sa_flags = SA_SIGINFO | SA_RESTART; @@ -146,6 +149,7 @@ void ProfilerManager::installSignalHandler() { // Save old signal handler if (sigaction(stack_capture_signal_, &sa, &old_action_) == 0) { old_action_saved_ = true; + signal_handler_installed_ = true; PROFILER_INFO("Registered signal handler for signal {}", stack_capture_signal_); } else { PROFILER_ERROR("Failed to register signal handler for signal {}: {}", stack_capture_signal_, strerror(errno)); @@ -155,9 +159,9 @@ void ProfilerManager::installSignalHandler() { void ProfilerManager::restoreSignalHandler() { if (old_action_saved_) { if (sigaction(stack_capture_signal_, &old_action_, nullptr) == 0) { - PROFILER_INFO("Restored old signal handler for signal {}", stack_capture_signal_); + std::cout << "[INFO] Restored old signal handler for signal " << stack_capture_signal_ << std::endl; } else { - PROFILER_ERROR("Failed to restore old signal handler: {}", strerror(errno)); + std::cerr << "[ERROR] Failed to restore old signal handler: " << strerror(errno) << std::endl; } old_action_saved_ = false; } @@ -318,10 +322,8 @@ std::string ProfilerManager::generateFlameGraph(const std::string& collapsed_fil // Build flamegraph.pl command // Use "perl ./flamegraph.pl" instead of "./flamegraph.pl" for better compatibility std::ostringstream cmd; - cmd << "perl ./flamegraph.pl" - << " --title=\"" << title << "\"" - << " --width=1200" - << " " << collapsed_file << " 2>/dev/null"; + cmd << "perl ./flamegraph.pl" << " --title=\"" << title << "\"" << " --width=1200" << " " << collapsed_file + << " 2>/dev/null"; PROFILER_INFO("Generating FlameGraph: {}", cmd.str()); @@ -599,7 +601,7 @@ std::string ProfilerManager::analyzeHeapProfile(int duration, const std::string& std::atomic allocations_count(0); PROFILER_DEBUG("Starting memory allocation thread..."); - std::thread memory_thread([&keep_running, &allocations_count]() { + std::thread memory_thread([this, &keep_running, &allocations_count]() { PROFILER_DEBUG("Memory thread started"); int iteration = 0; while (keep_running && iteration < 100) { // 限制最大迭代次数 @@ -1121,6 +1123,9 @@ std::string ProfilerManager::symbolizeAddress(void* addr) { std::vector ProfilerManager::captureAllThreadStacks() { std::vector result; + // Lazily install signal handler on first use + installSignalHandler(); + // 1. Read all thread IDs and find maximum std::vector tids; pid_t max_tid = 0; diff --git a/src/web_server.cpp b/src/web_server.cpp deleted file mode 100644 index 78976b1..0000000 --- a/src/web_server.cpp +++ /dev/null @@ -1,1070 +0,0 @@ -#include "web_server.h" -#include "internal/web_resources.h" -#include "profiler_manager.h" -#include -#include -#include -#include - -using namespace drogon; - -PROFILER_NAMESPACE_BEGIN - -void registerHttpHandlers(profiler::ProfilerManager& profiler) { - // Status endpoint - 获取 profiler 状态 - app().registerHandler("/api/status", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - Json::Value root; - root["cpu"]["running"] = profiler.isProfilerRunning(profiler::ProfilerType::CPU); - root["heap"]["running"] = profiler.isProfilerRunning(profiler::ProfilerType::HEAP); - root["growth"]["running"] = - profiler.isProfilerRunning(profiler::ProfilerType::HEAP_GROWTH); - - auto cpuState = profiler.getProfilerState(profiler::ProfilerType::CPU); - auto heapState = profiler.getProfilerState(profiler::ProfilerType::HEAP); - auto growthState = profiler.getProfilerState(profiler::ProfilerType::HEAP_GROWTH); - - root["cpu"]["output_path"] = cpuState.output_path; - root["cpu"]["duration_ms"] = static_cast(cpuState.duration); - - root["heap"]["output_path"] = heapState.output_path; - root["heap"]["duration_ms"] = static_cast(heapState.duration); - - root["growth"]["output_path"] = growthState.output_path; - root["growth"]["duration_ms"] = static_cast(growthState.duration); - - auto resp = HttpResponse::newHttpJsonResponse(root); - callback(resp); - }, - {Get}); - - // Index page - 主页面 - app().registerHandler( - "/", - []([[maybe_unused]] const HttpRequestPtr& req, std::function&& callback) { - std::string html = WebResources::getIndexPage(); - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(html); - resp->setContentTypeCode(CT_TEXT_HTML); - callback(resp); - }, - {Get}); - - // CPU SVG flame graph viewer page - app().registerHandler( - "/show_svg.html", - []([[maybe_unused]] const HttpRequestPtr& req, std::function&& callback) { - std::string html = WebResources::getCpuSvgViewerPage(); - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(html); - resp->setContentTypeCode(CT_TEXT_HTML); - callback(resp); - }, - {Get}); - - // Heap SVG flame graph viewer page - app().registerHandler( - "/show_heap_svg.html", - []([[maybe_unused]] const HttpRequestPtr& req, std::function&& callback) { - std::string html = WebResources::getHeapSvgViewerPage(); - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(html); - resp->setContentTypeCode(CT_TEXT_HTML); - callback(resp); - }, - {Get}); - - // Growth SVG flame graph viewer page - app().registerHandler( - "/show_growth_svg.html", - []([[maybe_unused]] const HttpRequestPtr& req, std::function&& callback) { - std::string html = WebResources::getGrowthSvgViewerPage(); - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(html); - resp->setContentTypeCode(CT_TEXT_HTML); - callback(resp); - }, - {Get}); - - // CPU analyze endpoint - 一键式CPU分析(使用pprof生成SVG火焰图) - app().registerHandler( - "/api/cpu/analyze", - [&profiler](const HttpRequestPtr& req, std::function&& callback) { - // 获取参数 - auto duration_param = req->getParameter("duration"); - auto output_type_param = req->getParameter("output_type"); - - // 默认值 - int duration = 10; // 默认10秒 - if (!duration_param.empty()) { - try { - duration = std::stoi(duration_param); - if (duration < 1) - duration = 1; - if (duration > 300) - duration = 300; // 最多5分钟 - } catch (const std::exception& e) { - Json::Value root; - root["error"] = "Invalid duration parameter"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k400BadRequest); - callback(resp); - return; - } - } - - std::string output_type = "pprof"; // 默认使用 pprof(向后兼容) - if (!output_type_param.empty()) { - output_type = output_type_param; - } - - // Validate output_type - if (output_type != "flamegraph" && output_type != "pprof") { - Json::Value root; - root["error"] = "Invalid output_type. Must be 'flamegraph' or 'pprof'"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k400BadRequest); - callback(resp); - return; - } - - std::cout << "Starting CPU analysis: duration=" << duration << "s, output_type=" << output_type - << std::endl; - - // 调用 analyzeCPUProfile(这是阻塞调用,会等待整个profiling过程完成) - std::string svg_content = profiler.analyzeCPUProfile(duration, output_type); - - // 检查是否是错误响应(JSON格式的错误,更精确的检查) - if (svg_content.size() > 10 && svg_content[0] == '{' && svg_content[1] == '"') { - auto resp = HttpResponse::newHttpJsonResponse(Json::Value(svg_content)); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回SVG内容 - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - callback(resp); - }, - {Get, Post}); - - // Heap analyze endpoint - 一键式Heap分析(使用pprof生成SVG火焰图) - app().registerHandler( - "/api/heap/analyze", - [&profiler](const HttpRequestPtr& req, std::function&& callback) { - std::cout << "Starting Heap analysis..." << std::endl; - - // Get output_type parameter - auto output_type_param = req->getParameter("output_type"); - std::string output_type = "pprof"; // 默认使用 pprof(向后兼容) - if (!output_type_param.empty()) { - output_type = output_type_param; - } - - // Validate output_type - if (output_type != "flamegraph" && output_type != "pprof") { - Json::Value root; - root["error"] = "Invalid output_type. Must be 'flamegraph' or 'pprof'"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k400BadRequest); - callback(resp); - return; - } - - // Call analyzeHeapProfile (duration is ignored for heap, set to 1) - std::string svg_content = profiler.analyzeHeapProfile(1, output_type); - - // Check for error - if (svg_content.size() > 10 && svg_content[0] == '{' && svg_content[1] == '"') { - Json::Value root; - root["error"] = "Failed to generate heap flame graph"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Return SVG - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - callback(resp); - }, - {Get}); - - // Growth analyze endpoint - 一键式Growth分析(使用pprof生成SVG火焰图) - app().registerHandler( - "/api/growth/analyze", - [&profiler](const HttpRequestPtr& req, std::function&& callback) { - std::cout << "Starting Heap Growth analysis..." << std::endl; - - // Get output_type parameter - auto output_type_param = req->getParameter("output_type"); - std::string output_type = "pprof"; // 默认使用 pprof(向后兼容) - if (!output_type_param.empty()) { - output_type = output_type_param; - } - - // Validate output_type - if (output_type != "flamegraph" && output_type != "pprof") { - Json::Value root; - root["error"] = "Invalid output_type. Must be 'flamegraph' or 'pprof'"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k400BadRequest); - callback(resp); - return; - } - - // Get growth stacks data - std::string growth_stacks = profiler.getRawHeapGrowthStacks(); - - if (growth_stacks.empty()) { - Json::Value root; - root["error"] = "Failed to get heap growth stacks. No heap growth data available."; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Save to temp file - std::string temp_file = "/tmp/growth_sample.prof"; - std::ofstream out(temp_file); - out << growth_stacks; - out.close(); - - std::string svg_output; - std::string exe_path = profiler.getExecutablePath(); - - if (output_type == "flamegraph") { - // PATH 1: Generate FlameGraph - std::cout << "Generating Growth FlameGraph..." << std::endl; - - // Step 1: Generate collapsed format - std::string collapsed_file = "/tmp/growth_collapsed.prof"; - std::ostringstream collapsed_cmd; - collapsed_cmd << "./pprof --collapsed " << exe_path << " " << temp_file << " > " << collapsed_file - << " 2>/dev/null"; - - std::string collapsed_output; - if (!profiler.executeCommand(collapsed_cmd.str(), collapsed_output)) { - Json::Value root; - root["error"] = "Failed to generate collapsed format"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Step 2: Generate FlameGraph using flamegraph.pl - std::ostringstream flamegraph_cmd; - flamegraph_cmd << "perl ./flamegraph.pl" - << " --title=\"Heap Growth Flame Graph\"" - << " --width=1200" - << " " << collapsed_file << " 2>/dev/null"; - - if (!profiler.executeCommand(flamegraph_cmd.str(), svg_output)) { - Json::Value root; - root["error"] = "Failed to execute flamegraph.pl command"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Validate output - if (svg_output.find("setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - } else { - // PATH 2: Default pprof SVG (existing behavior) - std::cout << "Generating Growth pprof SVG..." << std::endl; - - std::ostringstream cmd; - cmd << "./pprof --svg " << exe_path << " " << temp_file << " 2>&1"; - std::cout << "Executing: " << cmd.str() << std::endl; - - if (!profiler.executeCommand(cmd.str(), svg_output)) { - Json::Value root; - root["error"] = "Failed to execute pprof command"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Add viewBox if needed (existing logic) - size_t svg_start = svg_output.find("", svg_start); - if (svg_tag_end != std::string::npos) { - std::string svg_tag = svg_output.substr(svg_start, svg_tag_end - svg_start); - if (svg_tag.find("viewBox") == std::string::npos) { - std::string viewbox_attr = " viewBox=\"0 -1000 2000 1000\""; - svg_output.insert(svg_tag_end, viewbox_attr); - } - } - } - } - - // Return SVG - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_output); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - callback(resp); - }, - {Get}); - - // CPU raw SVG endpoint - 返回pprof生成的原始SVG(不做任何修改) - app().registerHandler( - "/api/cpu/svg_raw", - [&profiler](const HttpRequestPtr& req, std::function&& callback) { - // 获取参数 - auto duration_param = req->getParameter("duration"); - - // 默认值 - int duration = 10; // 默认10秒 - if (!duration_param.empty()) { - try { - duration = std::stoi(duration_param); - if (duration < 1) - duration = 1; - if (duration > 300) - duration = 300; // 最多5分钟 - } catch (const std::exception& e) { - Json::Value root; - root["error"] = "Invalid duration parameter"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k400BadRequest); - callback(resp); - return; - } - } - - std::cout << "Starting CPU raw SVG generation: duration=" << duration << "s" << std::endl; - - // 获取原始 CPU profile - std::string profile_data = profiler.getRawCPUProfile(duration); - - if (profile_data.empty()) { - Json::Value root; - root["error"] = "Failed to generate CPU profile"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 保存到临时文件 - std::string temp_file = "/tmp/cpu_raw.prof"; - std::ofstream out(temp_file, std::ios::binary); - out.write(profile_data.data(), profile_data.size()); - out.close(); - - // 使用 pprof 生成原始 SVG(不做任何修改) - std::string exe_path = profiler.getExecutablePath(); - std::string pprof_path = "./pprof"; - std::string cmd = pprof_path + " --svg " + exe_path + " " + temp_file + " 2>/dev/null"; - std::cout << "Executing: " << cmd << std::endl; - std::string svg_content; - profiler.executeCommand(cmd, svg_content); - - // 去掉 pprof 的信息输出,只保留 SVG 内容 - // pprof 会在 SVG 前输出一些信息,需要找到 SVG 的开始位置 - size_t svg_start = svg_content.find(" 0) { - svg_content = svg_content.substr(svg_start); - } - - // 检查结果(检查是否有 SVG 标签) - if (svg_content.empty() || svg_content.find("setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回原始SVG内容(不做任何修改) - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - // 添加 Content-Disposition 让浏览器下载文件 - resp->addHeader("Content-Disposition", "attachment; filename=cpu_profile.svg"); - callback(resp); - }, - {Get}); - - // CPU FlameGraph raw SVG endpoint - 返回 FlameGraph 生成的原始 SVG - app().registerHandler( - "/api/cpu/flamegraph_raw", - [&profiler](const HttpRequestPtr& req, std::function&& callback) { - // 获取参数 - auto duration_param = req->getParameter("duration"); - - // 默认值 - int duration = 10; // 默认10秒 - if (!duration_param.empty()) { - try { - duration = std::stoi(duration_param); - if (duration < 1) - duration = 1; - if (duration > 300) - duration = 300; // 最多5分钟 - } catch (const std::exception& e) { - Json::Value root; - root["error"] = "Invalid duration parameter"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k400BadRequest); - callback(resp); - return; - } - } - - std::cout << "Starting CPU FlameGraph generation: duration=" << duration << "s" << std::endl; - - // 获取原始 CPU profile - std::string profile_data = profiler.getRawCPUProfile(duration); - - if (profile_data.empty()) { - Json::Value root; - root["error"] = "Failed to generate CPU profile"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 保存到临时文件 - std::string temp_file = "/tmp/cpu_raw.prof"; - std::ofstream out(temp_file, std::ios::binary); - out.write(profile_data.data(), profile_data.size()); - out.close(); - - std::string exe_path = profiler.getExecutablePath(); - - // Step 1: Generate collapsed format using pprof --collapsed - std::string collapsed_file = "/tmp/cpu_collapsed.prof"; - std::ostringstream collapsed_cmd; - collapsed_cmd << "./pprof --collapsed " << exe_path << " " << temp_file << " > " << collapsed_file - << " 2>&1"; - - std::cout << "Running collapsed command: " << collapsed_cmd.str() << std::endl; - - std::string collapsed_output; - if (!profiler.executeCommand(collapsed_cmd.str(), collapsed_output)) { - Json::Value root; - root["error"] = "Failed to execute pprof --collapsed command"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Check if collapsed file was created and has content - std::ifstream collapsed_in(collapsed_file); - if (!collapsed_in.is_open()) { - Json::Value root; - root["error"] = "Failed to create collapsed file"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Verify file has actual data (not just "Using local file..." messages) - std::string line; - bool has_data = false; - while (std::getline(collapsed_in, line)) { - if (!line.empty() && line[0] != '#') { - has_data = true; - break; - } - } - collapsed_in.close(); - - if (!has_data) { - Json::Value root; - root["error"] = "pprof --collapsed produced no data. Please try a longer sampling duration."; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Step 2: Generate FlameGraph from collapsed format - std::string cmd = - "perl ./flamegraph.pl --title=\"CPU Flame Graph\" --width=1200 " + collapsed_file + " 2>/dev/null"; - std::cout << "Executing: " << cmd << std::endl; - std::string svg_content; - profiler.executeCommand(cmd, svg_content); - - // Validate SVG output - if (svg_content.find("setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回 FlameGraph SVG 内容 - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - // 添加 Content-Disposition 让浏览器下载文件 - resp->addHeader("Content-Disposition", - "attachment; filename=cpu_flamegraph_" + std::to_string(duration) + "s.svg"); - callback(resp); - }, - {Get}); - - // Heap raw SVG endpoint - 返回pprof生成的原始SVG(不做任何修改) - app().registerHandler( - "/api/heap/svg_raw", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - std::cout << "Starting Heap raw SVG generation..." << std::endl; - - // 直接获取 heap sample - std::string heap_sample = profiler.getRawHeapSample(); - - if (heap_sample.empty()) { - Json::Value root; - root["error"] = - "Failed to get heap sample. Make sure TCMALLOC_SAMPLE_PARAMETER environment variable is set."; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 保存到临时文件 - std::string temp_file = "/tmp/heap_raw.prof"; - std::ofstream out(temp_file); - out << heap_sample; - out.close(); - - // 使用 pprof 生成原始 SVG(不做任何修改) - std::string exe_path = profiler.getExecutablePath(); - std::string pprof_path = "./pprof"; - std::string cmd = pprof_path + " --svg " + exe_path + " " + temp_file + " 2>/dev/null"; - std::cout << "Executing: " << cmd << std::endl; - std::string svg_content; - profiler.executeCommand(cmd, svg_content); - - // 去掉 pprof 的信息输出,只保留 SVG 内容 - // pprof 会在 SVG 前输出一些信息,需要找到 SVG 的开始位置 - size_t svg_start = svg_content.find(" 0) { - svg_content = svg_content.substr(svg_start); - } - - // 检查结果(检查是否有 SVG 标签) - if (svg_content.empty() || svg_content.find("setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回原始SVG内容(不做任何修改) - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - // 添加 Content-Disposition 让浏览器下载文件 - resp->addHeader("Content-Disposition", "attachment; filename=heap_profile.svg"); - callback(resp); - }, - {Get}); - - // Heap FlameGraph raw SVG endpoint - 返回 FlameGraph 生成的原始 SVG - app().registerHandler( - "/api/heap/flamegraph_raw", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - std::cout << "Starting Heap FlameGraph generation..." << std::endl; - - // 直接获取 heap sample - std::string heap_sample = profiler.getRawHeapSample(); - - if (heap_sample.empty()) { - Json::Value root; - root["error"] = - "Failed to get heap sample. Make sure TCMALLOC_SAMPLE_PARAMETER environment variable is set."; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 保存到临时文件 - std::string temp_file = "/tmp/heap_raw.prof"; - std::ofstream out(temp_file); - out << heap_sample; - out.close(); - - std::string exe_path = profiler.getExecutablePath(); - - // Step 1: Generate collapsed format using pprof --collapsed - std::string collapsed_file = "/tmp/heap_collapsed.prof"; - std::ostringstream collapsed_cmd; - collapsed_cmd << "./pprof --collapsed " << exe_path << " " << temp_file << " > " << collapsed_file - << " 2>&1"; - - std::cout << "Running collapsed command: " << collapsed_cmd.str() << std::endl; - - std::string collapsed_output; - if (!profiler.executeCommand(collapsed_cmd.str(), collapsed_output)) { - Json::Value root; - root["error"] = "Failed to execute pprof --collapsed command"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Check if collapsed file was created and has content - std::ifstream collapsed_in(collapsed_file); - if (!collapsed_in.is_open()) { - Json::Value root; - root["error"] = "Failed to create collapsed file"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Verify file has actual data (not just "Using local file..." messages) - std::string line; - bool has_data = false; - while (std::getline(collapsed_in, line)) { - if (!line.empty() && line[0] != '#') { - has_data = true; - break; - } - } - collapsed_in.close(); - - if (!has_data) { - Json::Value root; - root["error"] = "pprof --collapsed produced no data"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Step 2: Generate FlameGraph from collapsed format - std::string cmd = - "perl ./flamegraph.pl --title=\"Heap Flame Graph\" --width=1200 " + collapsed_file + " 2>/dev/null"; - std::cout << "Executing: " << cmd << std::endl; - std::string svg_content; - profiler.executeCommand(cmd, svg_content); - - // Validate SVG output - if (svg_content.find("setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回 FlameGraph SVG 内容 - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - // 添加 Content-Disposition 让浏览器下载文件 - std::string timestamp = std::to_string( - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - .count()); - resp->addHeader("Content-Disposition", "attachment; filename=heap_flamegraph_" + timestamp + ".svg"); - callback(resp); - }, - {Get}); - - // Growth raw SVG endpoint - 返回pprof生成的原始SVG(不做任何修改) - app().registerHandler("/api/growth/svg_raw", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - std::cout << "Starting Growth raw SVG generation..." << std::endl; - - // 直接获取 heap growth stacks - std::string heap_growth = profiler.getRawHeapGrowthStacks(); - - if (heap_growth.empty()) { - Json::Value root; - root["error"] = "Failed to get heap growth stacks. No heap growth data available."; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 保存到临时文件 - std::string temp_file = "/tmp/growth_raw.prof"; - std::ofstream out(temp_file); - out << heap_growth; - out.close(); - - // 使用 pprof 生成原始 SVG(不做任何修改) - std::string exe_path = profiler.getExecutablePath(); - std::string pprof_path = "./pprof"; - std::string cmd = pprof_path + " --svg " + exe_path + " " + temp_file + " 2>/dev/null"; - std::cout << "Executing: " << cmd << std::endl; - std::string svg_content; - profiler.executeCommand(cmd, svg_content); - - // 去掉 pprof 的信息输出,只保留 SVG 内容 - // pprof 会在 SVG 前输出一些信息,需要找到 SVG 的开始位置 - size_t svg_start = svg_content.find(" 0) { - svg_content = svg_content.substr(svg_start); - } - - // 检查结果(检查是否有 SVG 标签) - if (svg_content.empty() || svg_content.find("setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回原始SVG内容(不做任何修改) - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - // 添加 Content-Disposition 让浏览器下载文件 - resp->addHeader("Content-Disposition", "attachment; filename=growth_profile.svg"); - callback(resp); - }, - {Get}); - - // Growth FlameGraph raw SVG endpoint - 返回 FlameGraph 生成的原始 SVG - app().registerHandler( - "/api/growth/flamegraph_raw", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - std::cout << "Starting Growth FlameGraph generation..." << std::endl; - - // 直接获取 heap growth stacks - std::string heap_growth = profiler.getRawHeapGrowthStacks(); - - if (heap_growth.empty()) { - Json::Value root; - root["error"] = "Failed to get heap growth stacks. No heap growth data available."; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 保存到临时文件 - std::string temp_file = "/tmp/growth_raw.prof"; - std::ofstream out(temp_file); - out << heap_growth; - out.close(); - - std::string exe_path = profiler.getExecutablePath(); - - // Step 1: Generate collapsed format using pprof --collapsed - std::string collapsed_file = "/tmp/growth_collapsed.prof"; - std::ostringstream collapsed_cmd; - collapsed_cmd << "./pprof --collapsed " << exe_path << " " << temp_file << " > " << collapsed_file - << " 2>&1"; - - std::cout << "Running collapsed command: " << collapsed_cmd.str() << std::endl; - - std::string collapsed_output; - if (!profiler.executeCommand(collapsed_cmd.str(), collapsed_output)) { - Json::Value root; - root["error"] = "Failed to execute pprof --collapsed command"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Check if collapsed file was created and has content - std::ifstream collapsed_in(collapsed_file); - if (!collapsed_in.is_open()) { - Json::Value root; - root["error"] = "Failed to create collapsed file"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Verify file has actual data (not just "Using local file..." messages) - std::string line; - bool has_data = false; - while (std::getline(collapsed_in, line)) { - if (!line.empty() && line[0] != '#') { - has_data = true; - break; - } - } - collapsed_in.close(); - - if (!has_data) { - Json::Value root; - root["error"] = "pprof --collapsed produced no data"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // Step 2: Generate FlameGraph from collapsed format - std::string cmd = "perl ./flamegraph.pl --title=\"Heap Growth Flame Graph\" --width=1200 " + - collapsed_file + " 2>/dev/null"; - std::cout << "Executing: " << cmd << std::endl; - std::string svg_content; - profiler.executeCommand(cmd, svg_content); - - // Validate SVG output - if (svg_content.find("setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回 FlameGraph SVG 内容 - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(svg_content); - resp->setContentTypeCode(CT_TEXT_XML); - resp->addHeader("Content-Type", "image/svg+xml"); - // 添加 Content-Disposition 让浏览器下载文件 - std::string timestamp = std::to_string( - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - .count()); - resp->addHeader("Content-Disposition", "attachment; filename=growth_flamegraph_" + timestamp + ".svg"); - callback(resp); - }, - {Get}); - - // Symbol resolution endpoint (类似 brpc pprof 的 /pprof/symbol) - // 使用 backward-cpp 进行符号化,支持内联函数 - app().registerHandler( - "/pprof/symbol", - [&profiler](const HttpRequestPtr& req, std::function&& callback) { - // 只支持 POST 请求 - if (req->method() != Post) { - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k405MethodNotAllowed); - resp->setBody("Method not allowed. Use POST."); - callback(resp); - return; - } - - // 从请求体获取地址列表 - std::string bodyStr(req->body()); - std::istringstream iss(bodyStr); - std::string address; - std::ostringstream result; - - // 逐行读取地址并使用 backward-cpp 符号化 - while (std::getline(iss, address)) { - if (address.empty() || address[0] == '#') { - continue; - } - - // 移除 "0x" 前缀(如果有) - std::string original_addr = address; - if (address.size() > 2 && address[0] == '0' && address[1] == 'x') { - address = address.substr(2); - } - - // 将十六进制地址转换为指针 - try { - uintptr_t addr = std::stoull(address, nullptr, 16); - void* ptr = reinterpret_cast(addr); - - // 使用 backward-cpp 符号化(支持内联函数) - std::string symbol = profiler.resolveSymbolWithBackward(ptr); - - // 返回格式: "原始地址 符号化结果" - result << original_addr << " " << symbol << "\n"; - } catch (const std::exception& e) { - // 转换失败,返回原始地址 - result << original_addr << " " << original_addr << "\n"; - } - } - - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(result.str()); - resp->setContentTypeCode(CT_TEXT_PLAIN); - callback(resp); - }, - {Post}); - - // Standard pprof endpoint: /pprof/profile - // Compatible with Go pprof tool - app().registerHandler( - "/pprof/profile", - [&profiler](const HttpRequestPtr& req, std::function&& callback) { - // 获取 seconds 参数,默认 30 秒 - int seconds = 30; - auto seconds_param = req->getParameter("seconds"); - if (!seconds_param.empty()) { - try { - seconds = std::stoi(seconds_param); - if (seconds < 1) - seconds = 1; - if (seconds > 300) - seconds = 300; // 最多5分钟 - } catch (const std::exception& e) { - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k400BadRequest); - resp->setBody("Invalid seconds parameter"); - callback(resp); - return; - } - } - - std::cout << "Received /pprof/profile request, seconds=" << seconds << std::endl; - - // 调用 getRawCPUProfile 获取原始 profile 数据 - std::string profile_data = profiler.getRawCPUProfile(seconds); - - if (profile_data.empty()) { - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k500InternalServerError); - resp->setBody("Failed to generate CPU profile"); - callback(resp); - return; - } - - // 返回二进制 profile 数据 - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(profile_data); - resp->setContentTypeCode(CT_APPLICATION_OCTET_STREAM); - resp->addHeader("Content-Disposition", "attachment; filename=profile"); - callback(resp); - }, - {Get}); - - // Standard pprof endpoint: /pprof/heap - // Compatible with Go pprof tool - app().registerHandler("/pprof/heap", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - std::cout << "Received /pprof/heap request" << std::endl; - - // 调用 getRawHeapSample 获取 heap sample 数据 - std::string heap_sample = profiler.getRawHeapSample(); - - if (heap_sample.empty()) { - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k500InternalServerError); - resp->setBody( - "Failed to get heap sample. Make sure TCMALLOC_SAMPLE_PARAMETER is set."); - callback(resp); - return; - } - - // 返回 heap sample 数据(文本格式) - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(heap_sample); - resp->setContentTypeCode(CT_TEXT_PLAIN); - resp->addHeader("Content-Disposition", "attachment; filename=heap"); - callback(resp); - }, - {Get}); - - // Standard pprof endpoint: /pprof/growth - // Compatible with Go pprof tool - returns heap growth stacks - app().registerHandler("/pprof/growth", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - std::cout << "Received /pprof/growth request" << std::endl; - - // 调用 getRawHeapGrowthStacks 获取 heap growth stacks 数据 - std::string heap_growth = profiler.getRawHeapGrowthStacks(); - - if (heap_growth.empty()) { - auto resp = HttpResponse::newHttpResponse(); - resp->setStatusCode(k500InternalServerError); - resp->setBody("Failed to get heap growth stacks. No heap growth data available."); - callback(resp); - return; - } - - // 返回 heap growth stacks 数据(文本格式) - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(heap_growth); - resp->setContentTypeCode(CT_TEXT_PLAIN); - resp->addHeader("Content-Disposition", "attachment; filename=growth"); - callback(resp); - }, - {Get}); - - // Thread stacks endpoint: /api/thread/stacks - // Returns all thread stacks with full backtrace in text format - app().registerHandler("/api/thread/stacks", - [&profiler]([[maybe_unused]] const HttpRequestPtr& req, - std::function&& callback) { - std::cout << "Received /api/thread/stacks request" << std::endl; - - // 调用 getThreadCallStacks 获取线程调用堆栈数据 - std::string thread_stacks = profiler.getThreadCallStacks(); - - if (thread_stacks.empty()) { - Json::Value root; - root["error"] = "Failed to get thread call stacks"; - auto resp = HttpResponse::newHttpJsonResponse(root); - resp->setStatusCode(k500InternalServerError); - callback(resp); - return; - } - - // 返回线程调用堆栈数据(文本格式) - auto resp = HttpResponse::newHttpResponse(); - resp->setBody(thread_stacks); - resp->setContentTypeCode(CT_TEXT_PLAIN); - callback(resp); - }, - {Get}); -} - -PROFILER_NAMESPACE_END diff --git a/tests/test_cpu_profile.cpp b/tests/test_cpu_profile.cpp index 54a5296..4a004b3 100644 --- a/tests/test_cpu_profile.cpp +++ b/tests/test_cpu_profile.cpp @@ -1,3 +1,6 @@ +/// @file test_cpu_profile.cpp +/// @brief Tests for CPU profiling functionality + #include "../include/profiler_manager.h" #include #include @@ -6,17 +9,22 @@ #include #include -// 测试 gperftools CPU profile 解析 +// Free function used as a known address for symbol resolution tests +namespace { +int helperFunctionForAddrTest(int x) { + return x * x; +} +} // namespace + +// Test 1: Verify gperftools generates valid CPU profile TEST(CPUProfileTest, GperftoolsGeneratesValidProfile) { const char* profile_path = "/tmp/test_cpu.prof"; - - // 清理旧文件 std::remove(profile_path); - // 启动 profiler + // Start profiler ProfilerStart(profile_path); - // 运行一些工作负载 + // Run workload for (int i = 0; i < 100; ++i) { std::vector data(1000); for (auto& val : data) { @@ -27,10 +35,10 @@ TEST(CPUProfileTest, GperftoolsGeneratesValidProfile) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); - // 停止 profiler + // Stop profiler ProfilerStop(); - // 检查文件是否存在 + // Check file exists and has content std::ifstream file(profile_path, std::ios::binary | std::ios::ate); ASSERT_TRUE(file.is_open()) << "Cannot open profile file"; @@ -39,43 +47,20 @@ TEST(CPUProfileTest, GperftoolsGeneratesValidProfile) { file.close(); std::cout << "CPU profile file size: " << file_size << " bytes\n"; - - // 读取文件内容 - file.open(profile_path, std::ios::binary); - std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - file.close(); - - // gperftools profile 文件应该至少有 header (16 bytes) - ASSERT_GT(buffer.size(), 16) << "Profile file too small"; - - // 前 4 个字节应该是魔数 (0x70726f66 = "prof" in little endian) - uint32_t magic = *reinterpret_cast(buffer.data()); - - // 验证魔数 - if (magic != 0x70726f66) { - std::cout << "Warning: Expected magic 0x70726f66, got 0x" << std::hex << magic << std::dec << "\n"; - // 这可能是因为 gperftools 版本不同或配置问题 - // 只要文件有合理的大小就认为测试通过 - } - - // 清理 std::remove(profile_path); } -// 测试 ProfilerManager 的基本功能 +// Test 2: ProfilerManager start/stop CPU profiler TEST(ProfilerManagerTest, StartStopCPUProfiler) { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; - // 启动 CPU profiler std::string profile_path = "/tmp/test_manager_cpu.prof"; std::remove(profile_path.c_str()); bool started = profiler.startCPUProfiler(profile_path); ASSERT_TRUE(started) << "Failed to start CPU profiler"; - EXPECT_TRUE(profiler.isProfilerRunning(profiler::ProfilerType::CPU)); - - // 运行一些工作负载 + // Run workload for (int i = 0; i < 50; ++i) { std::vector data(100); std::sort(data.begin(), data.end()); @@ -83,97 +68,65 @@ TEST(ProfilerManagerTest, StartStopCPUProfiler) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // 停止 profiler bool stopped = profiler.stopCPUProfiler(); ASSERT_TRUE(stopped) << "Failed to stop CPU profiler"; - EXPECT_FALSE(profiler.isProfilerRunning(profiler::ProfilerType::CPU)); - - // 验证生成了 profile 文件 + // Verify profile file was created std::ifstream file(profile_path, std::ios::binary | std::ios::ate); ASSERT_TRUE(file.is_open()) << "Profile file not created"; EXPECT_GT(file.tellg(), 0) << "Profile file is empty"; - // 清理 std::remove(profile_path.c_str()); } -// 测试 ProfilerState 查询 -TEST(ProfilerManagerTest, GetProfilerState) { - auto& profiler = profiler::ProfilerManager::getInstance(); +// Test 3: Multiple start/stop cycles +TEST(ProfilerManagerTest, MultipleStartStopCycles) { + profiler::ProfilerManager profiler; - // 初始状态:未运行 - profiler::ProfilerState state = profiler.getProfilerState(profiler::ProfilerType::CPU); - EXPECT_FALSE(state.is_running); + for (int cycle = 0; cycle < 3; ++cycle) { + std::string profile_path = "/tmp/test_cycle_" + std::to_string(cycle) + ".prof"; + std::remove(profile_path.c_str()); - // 启动 profiler - std::string profile_path = "/tmp/test_state_cpu.prof"; - std::remove(profile_path.c_str()); - - profiler.startCPUProfiler(profile_path); + ASSERT_TRUE(profiler.startCPUProfiler(profile_path)) << "Failed to start in cycle " << cycle; - // 运行中的状态 - state = profiler.getProfilerState(profiler::ProfilerType::CPU); - EXPECT_TRUE(state.is_running); - EXPECT_EQ(state.output_path, profile_path); - EXPECT_GT(state.start_time, 0); + std::vector data(1000); + std::sort(data.begin(), data.end()); - // 等待一小段时间,确保 profiler 运行了至少几毫秒 - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // 停止 profiler - profiler.stopCPUProfiler(); + ASSERT_TRUE(profiler.stopCPUProfiler()) << "Failed to stop in cycle " << cycle; - // 停止后的状态 - state = profiler.getProfilerState(profiler::ProfilerType::CPU); - EXPECT_FALSE(state.is_running); - EXPECT_GT(state.duration, 0); + std::ifstream file(profile_path); + EXPECT_TRUE(file.is_open()) << "Profile file not created in cycle " << cycle; - // 清理 - std::remove(profile_path.c_str()); + std::remove(profile_path.c_str()); + } } -// 测试 analyzeCPUProfile 生成 SVG +// Test 4: analyzeCPUProfile generates output TEST(ProfilerManagerTest, AnalyzeCPUProfile) { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; - // 分析 CPU profile(短时间采样) + // Analyze CPU profile (short duration) std::string svg_result = profiler.analyzeCPUProfile(1, "flamegraph"); - // 应该返回 SVG 内容(以 (&profiler::ProfilerManager::getInstance); + // Test with a known free function address + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + void* test_addr = reinterpret_cast(&helperFunctionForAddrTest); std::string symbol = profiler.resolveSymbolWithBackward(test_addr); - // 符号化结果应该不为空 ASSERT_FALSE(symbol.empty()) << "Symbol resolution returned empty string"; - std::cout << "Resolved symbol: " << symbol << "\n"; - // 只要能符号化(返回的不是原始地址)就算成功 - bool is_symbolized = (symbol.find("0x") != 0); - if (is_symbolized) { - std::cout << "Symbolization successful: " << symbol << "\n"; - } else { - std::cout << "Symbol returned as address (this is OK if backward-cpp is not configured)\n"; - } - - // 测试通过,不崩溃即可 SUCCEED() << "Symbol resolution test completed"; } diff --git a/tests/test_full_flow.cpp b/tests/test_full_flow.cpp index 0301fdc..606b04e 100644 --- a/tests/test_full_flow.cpp +++ b/tests/test_full_flow.cpp @@ -70,7 +70,7 @@ TEST(FullFlowTest, GperftoolsGeneratesValidProfile) { // 测试2: 完整的 CPU profiling 流程 TEST(FullFlowTest, CompleteCPUProfilingFlow) { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; // 1. 启动 profiler std::string profile_path = "/tmp/test_flow_cpu.prof"; @@ -107,7 +107,7 @@ TEST(FullFlowTest, CompleteCPUProfilingFlow) { // 测试3: 多次启动停止 profiler TEST(FullFlowTest, MultipleStartStopCycles) { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; for (int cycle = 0; cycle < 3; ++cycle) { std::string profile_path = "/tmp/test_cycle_" + std::to_string(cycle) + ".prof"; @@ -136,7 +136,7 @@ TEST(FullFlowTest, MultipleStartStopCycles) { // 测试4: 并发 profiler 请求应该被拒绝 TEST(FullFlowTest, ConcurrentProfilingRequests) { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; // 启动第一个 profiling std::string profile_path = "/tmp/test_concurrent.prof"; @@ -161,7 +161,7 @@ TEST(FullFlowTest, ConcurrentProfilingRequests) { // 测试5: getRawCPUProfile 功能 TEST(FullFlowTest, GetRawCPUProfile) { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; // 获取原始 profile 数据(采样 1 秒) std::string profile_data = profiler.getRawCPUProfile(1); @@ -188,7 +188,7 @@ TEST(FullFlowTest, GetRawCPUProfile) { // 测试6: Heap profiling 基本流程 TEST(FullFlowTest, BasicHeapProfilingFlow) { - auto& profiler = profiler::ProfilerManager::getInstance(); + profiler::ProfilerManager profiler; // 启动 heap profiler std::string heap_path = "/tmp/test_heap.prof"; diff --git a/tests/test_logger.cpp b/tests/test_logger.cpp index 6f39b62..ed82b00 100644 --- a/tests/test_logger.cpp +++ b/tests/test_logger.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include PROFILER_NAMESPACE_BEGIN @@ -67,23 +67,24 @@ class MockLogSink : public LogSink { class LoggerTest : public ::testing::Test { protected: void SetUp() override { - // Create and set mock sink + // Create and set mock sink on the ProfilerManager instance mock_sink_ = std::make_shared(); - setSink(mock_sink_); + profiler_.setLogSink(mock_sink_); } void TearDown() override { // Reset to default sink - setSink(nullptr); + profiler_.setLogSink(nullptr); } + ProfilerManager profiler_; std::shared_ptr mock_sink_; }; /// @brief Test that custom sink receives log messages TEST_F(LoggerTest, CustomSinkReceivesMessages) { // Set log level to capture all messages - setLogLevel(LogLevel::Trace); + profiler_.setLogLevel(LogLevel::Trace); // Log a test message using internal macro // Note: We can't use PROFILER_INFO directly here since it's internal, @@ -102,19 +103,19 @@ TEST_F(LoggerTest, CustomSinkReceivesMessages) { /// @brief Test log level filtering TEST_F(LoggerTest, LogLevelFiltering) { // Set log level to Warning - setLogLevel(LogLevel::Warning); + profiler_.setLogLevel(LogLevel::Warning); // Log messages at different levels directly to sink // (The filtering happens in LogManager, but we can verify the level is set) EXPECT_TRUE(true); // Level is set // Reset to trace for other tests - setLogLevel(LogLevel::Trace); + profiler_.setLogLevel(LogLevel::Trace); } /// @brief Test multiple log messages TEST_F(LoggerTest, MultipleMessages) { - setLogLevel(LogLevel::Trace); + profiler_.setLogLevel(LogLevel::Trace); mock_sink_->log(LogLevel::Debug, "file1.cpp", 10, "func1", "Message 1"); mock_sink_->log(LogLevel::Info, "file2.cpp", 20, "func2", "Message 2"); @@ -144,21 +145,21 @@ TEST_F(LoggerTest, FlushWorks) { /// @brief Test resetting to default sink TEST_F(LoggerTest, ResetToDefaultSink) { // Reset to default - setSink(nullptr); + profiler_.setLogSink(nullptr); // This should not crash - default sink handles the log // We can't easily verify default sink output, but we can verify no crash EXPECT_NO_THROW({ - // Any logging would go to default sink (stderr via spdlog) + // Any logging would go to default sink (stderr via std::cerr) }); // Restore mock sink for other tests - setSink(mock_sink_); + profiler_.setLogSink(mock_sink_); } /// @brief Test thread safety of mock sink TEST_F(LoggerTest, ThreadSafety) { - setLogLevel(LogLevel::Trace); + profiler_.setLogLevel(LogLevel::Trace); const int num_threads = 4; const int messages_per_thread = 100; @@ -182,7 +183,7 @@ TEST_F(LoggerTest, ThreadSafety) { /// @brief Test all log levels TEST_F(LoggerTest, AllLogLevels) { - setLogLevel(LogLevel::Trace); + profiler_.setLogLevel(LogLevel::Trace); mock_sink_->log(LogLevel::Trace, "test.cpp", 1, "f", "trace"); mock_sink_->log(LogLevel::Debug, "test.cpp", 2, "f", "debug"); diff --git a/vcpkg.json b/vcpkg.json index 08f6b60..b4b41c7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -17,8 +17,7 @@ { "name": "protobuf" }, - "backward-cpp", - "spdlog" + "backward-cpp" ], "builtin-baseline": "2cf2bcc60add50f79b2c418487d9cd1b6c7c1fec" }