调试

Posted by     "LETTER" on Friday, November 21, 2025

GDB

GDB(GNU Debugger)是UNIX及UNIX-like下的强大调试工具,可以调试ada, c, c++, asm, minimal, d, fortran, objective-c, go, java,pascal等语言。

GDB常用命令

基本

命令描述
gdb打开调试器
file FILE装载指定可执行文件
r代表run,从头开始运行程序直到断点
q退出debug
bt列出调用堆栈

显示被调试文件信息

命令描述
info files显示被调试文件的详细信息
info func显示所有函数名称
info local显示当前函数中的局部变量信息
info prog显示被调试程序的执行状态
info var显示所有的全局和静态变量名称

查看/修改内存

命令描述
p x相当于“print x”。显示当前变量 x 的值
display x和print的区别是,x不是只显示一次就消失,而是一直显示,每次继续运行程序都会刷新。相当于VS的“watch”功能
undisplay x停止对变量x的display
x address查看指针所指位置的值
set x = 12 set x = y修改变量x的值
call function()调用某函数。这个函数可以是你程序里定义的函数,甚至是标准库函数,我的理解是只要在当前位置可访问到的函数都可以调用。

断点

命令描述
bb即break。在当前行设置断点。
b 45在某行设置断点。
b functionName在某函数开始处设置断点。常用:b main 在程序开始设置断点。
watch x == 3设置条件断点。这个比VS的条件断点更方便,因为它不需要设置在哪一行!时刻监控!
info break查看当前存在的所有断点。每个断点都有自己的编号。
delete N删除编号为N的那个断点。

调试运行

命令描述
n“next”,运行一行代码, 相当于VS的step over。
s“step”,运行一个指令,相当于VS的step in。n和s都可以一次运行多行,比如n 5
c“continue”,继续运行直到下一个断点。
f“finish”,运行完当前程序,相当于VS的 step out。

vscode

通过配置launch.json实现gdb调用

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/bin/eigen_test",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/usr/bin/gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

基本参数

  • program 要调试的程序名,${workspaceFolder}为当前工作目录,即.vscode同级目录
  • cwd 调试程序的路径
  • miDebuggerPath gdb 的路径

高级参数

  • "preLaunchTask": "build" 生成子 task,可在里面编辑 gcc 命令等。具体可参考 VSCode GDB 调试配置
  • "miDebuggerServerAddress" 配置服务器的地址和端口

ROS

run

rosrun --prefix 'gdb -ex run --args' [package_name] [node_name] 

launch

需安装xterm

通过在launch中设置launch-prefix启动调试,参考

  • launch-prefix="xterm -e gdb --args" : run your node in a gdb in a separate xterm window, manually type run to start it
  • launch-prefix="gdb -ex run --args" : run your node in gdb in the same xterm as your launch without having to type run to start it
  • launch-prefix="stterm -g 200x60 -e gdb -ex run --args" : run your node in gdb in a new stterm window without having to type run to start it
  • launch-prefix="valgrind" : run your node in valgrind valgrind工具可以用于检测内存泄露,并执行性能分析
  • launch-prefix="xterm -e" : run your node in a separate xterm window
  • launch-prefix="nice" : nice your process to lower its CPU usage
  • launch-prefix="screen -d -m gdb --args" : useful if the node is being run on another machine; you can then ssh to that machine and do screen -D -R to see the gdb session
  • launch-prefix="xterm -e python -m pdb" : run your python node a separate xterm window in pdb for debugging; manually type run to start it
  • launch-prefix="yappi -b -f pstat -o <filename>": run your rospy node in a multi-thread profiler such as yappi.
  • launch-prefix="/path/to/run_tmux": run your node in a new tmux window; you’ll need to create /path/to/run_tmux with the contents:

C++程序可增加launch-prefix="xterm -e gdb -ex run --args "

python程序可增加launch-prefix="xterm -e python -m pdb "

参考

GDB调试指南

GDB调试指南

ros项目调试:ROS项目使用GDB调试

信号处理

linux下sig类型,可以通过kill -${sig_num} ${PID}给对应进程发送信号。

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM # 发送给程序的终止请求
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
  • SIGTERM 发送给程序的终止请求
  • SIGSEGV 非法内存访问(分段错误)
  • SIGINT 外部中断,通常为用户所起始
  • SIGILL 非法程序映像,例如非法指令
  • SIGABRT 异常终止条件,例如为 std::abort() 所起始
  • SIGFPE 错误算术运算,例如除以零
  • SIGPIPE PIPE破裂

在代码运行过程中,可以设置对应的信号处理函数,记录程序异常时的堆栈信息以分析问题。

#include <signal.h>

void sig_handler(int sig) {
    LOGE("signal: {}", sig);
    void *array[10];
    // 获取堆栈内容,Debug模式下可用
    auto size = backtrace(array, 10);
    char **strings = backtrace_symbols(array, size);
    for (auto i = 0; i < size; i++) {
        LOGE("Backtrace: {}", strings[i]);  // 写堆栈日志
    }
    free(strings);
    // 重新发出信号以触发默认崩溃行为
    signal(sig, SIG_DFL);
    raise(sig);
}

int main(int argc, char **argv){
    signal(SIGTERM, sig_handler);  // 发送给程序的终止请求
    signal(SIGSEGV, sig_handler);  // 非法内存访问(分段错误)
    signal(SIGPIPE, sig_handler);  // PIPE破裂
    signal(SIGINT, sig_handler);   // 外部中断,通常为用户所起始
    signal(SIGILL, sig_handler);   // 非法程序映像,例如非法指令
    signal(SIGABRT, sig_handler);  // 异常终止条件,例如为 std::abort() 所起始
    signal(SIGFPE, sig_handler);   // 错误算术运算,例如除以零
}

代码检测

  • 静态分析是在代码编译前发现潜在问题,覆盖所有代码路径(无论是否被执行),但可能存在误报,且对复杂运行时问题(如动态内存管理和并发)的检测能力有限。

  • 动态分析(尤其是ASan和Valgrind)是在程序运行时发现实际发生的问题,准确率极高,尤其擅长检测内存错误和并发问题。但它们需要程序被执行,且会对性能造成一定影响。

特性CppcheckClang Static Analyzer (scan-build/clang-tidy)AddressSanitizer (ASan)Valgrind (Memcheck等)Flawfinder
分析类型静态分析静态分析动态分析 (运行时插桩)动态分析 (运行时模拟)静态分析
主要关注编译器不检测的缺陷,低误报率,可移植性深度缺陷(包括复杂逻辑/并发),代码风格,最佳实践运行时内存错误(越界、UAF、UAR、UAS),内存泄漏运行时内存错误,线程竞争,性能潜在安全漏洞(基于已知危险函数)
分析深度中等高(基于AST和数据流/路径分析)极高(运行时精确检测)极高(运行时行为,精确度高)浅(基于模式匹配)
使用方式独立工具,直接分析源代码拦截编译命令 (scan-build),或集成到构建系统 (clang-tidy)需要用特殊编译选项编译 (-fsanitize=address),然后运行程序运行时工具,需要编译并运行程序独立工具,直接分析源代码
误报率较低较低到中等(clang-tidy规则多可能报告更多“建议”)极低(几乎无误报)极低(运行时发现的问题通常是真实存在的)中高(基于模式匹配可能误报)
集成能力易于集成到CI/CD,IDE插件可用与LLVM/Clang生态系统紧密集成,易于集成到CI/CD,IDE支持与Clang/GCC编译器紧密集成,在CI/CD中常用作测试环节需要单独运行,结果报告通常为文本格式,可集成到CI/CD易于集成到CI/CD,结果通常为文本格式
性能开销否(分析时间)否(分析时间)是(运行时增加CPU和内存开销,约2x)较高(运行时模拟,约5-20x性能下降)否(分析时间)
典型缺陷空指针,内存泄漏,越界,未初始化变量,资源泄漏更复杂的内存/资源管理,并发问题,未定义行为,风格问题堆/栈/全局越界,Use-after-free/return/scope,Double-free,内存泄漏精确的内存泄漏,堆栈溢出,非法读写,竞争条件缓冲区溢出,格式字符串漏洞,TOCTOU竞争条件
内存泄漏检测能力有限(仅限于简单模式,可能漏报)有限(同Cppcheck,更复杂模式仍难捕捉)强(通过LSan模块,高准确率检测运行时泄漏)强(通过Memcheck,高准确率检测运行时泄漏)

静态检测

这里使用cppcheck

.
├── build           # 编译输出,静态检查报告输出
├── cmake           # 存放.cmake文件
├── CMakeLists.txt
├── src             # 代码
└── xslt            # 存放转换文件

可选参数

1. 检查范围和类型控制

  • --enable=<id>:启用额外的检查,一般使用all。
  • --suppress=<spec>:抑制匹配 <spec> 的警告
  • --inline-suppr:允许在代码中使用内联抑制。
  • --std=<id>:设置 C/C++ 标准。
  • --language=<language>:强制 Cppcheck 将所有文件作为给定语言检查,有效值为 cc++
  • --inconclusive:即使分析结果不确定,也允许报告缺陷。

2. 预处理器和包含路径

  • -I <dir>:指定包含文件的搜索路径。可以提供多个 -I 参数来指定多个路径。

3. 报告和输出

  • -j <jobs>:启动 <jobs> 个线程同时进行检查。
  • -q, --quiet:只在有错误时打印信息。
  • -rp, --relative-paths=<paths>:在输出中使用相对路径。当给定 <paths> 时,会将其用作基准路径。多个路径可以用 ; 分隔。
  • --xml:以 XML 格式输出结果。常用于与其他工具集成。
  • --xml-version=<version>:指定 XML 输出的版本。
  • --template=<format>:自定义报告的输出格式。

4. 配置和高级选项

  • --check-config:检查 Cppcheck 配置,使用此标志时,会禁用正常的代码分析。
  • --check-library:当库文件信息不完整时显示信息性消息。
  • -f, --force:强制检查包含大量配置的文件。如果找到此类文件,会打印错误,如果与 --max-configs= 一起使用,则最后一个选项有效。
  • --max-configs=<num>:限制每个符号的最大配置数。
  • --addon=<addon>:执行 Cppcheck 插件(Addon)。

构建CMAKE

filter_compile_commands.py

#!/usr/bin/env python3
"""
过滤compile_commands.json,只保留项目源代码的编译单元,排除第三方库
"""
import json
import sys
import os


def filter_compile_commands(input_file, output_file, source_dir):
    """Filter compile_commands.json to only include project source files"""
    with open(input_file, 'r', encoding='utf-8') as f:
        commands = json.load(f)
    
    # 只保留src目录和app目录下的文件
    # 排除thirdparty、build/_deps等第三方库目录
    filtered = []
    src_dir = os.path.join(source_dir, 'src')
    app_dir = os.path.join(source_dir, 'app')
    exclude_patterns = ['thirdparty', '_deps', 'external', 'third_party', 'build']
    
    for cmd in commands:
        file_path = cmd.get('file', '')
        # 规范化路径以进行比较
        file_path_normalized = os.path.normpath(file_path)
        
        # 检查是否在src或app目录中
        in_src = file_path_normalized.startswith(os.path.normpath(src_dir))
        in_app = file_path_normalized.startswith(os.path.normpath(app_dir))
        
        # 检查是否包含排除的模式
        exclude = any(pattern in file_path_normalized for pattern in exclude_patterns)
        
        if (in_src or in_app) and not exclude:
            filtered.append(cmd)
    
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(filtered, f, indent=2)
    
    print(f'Filtered {len(commands)} -> {len(filtered)} compilation units')
    return len(filtered)


if __name__ == '__main__':
    if len(sys.argv) != 4:
        print('Usage: filter_compile_commands.py <input> <output> <source_dir>')
        sys.exit(1)
    
    count = filter_compile_commands(sys.argv[1], sys.argv[2], sys.argv[3])
    sys.exit(0 if count > 0 else 1)

static_check.cmake

# ================================
# C++ 静态检查配置
# ================================
# 功能:对项目源代码进行静态分析,排除第三方库
# 使用:make run_cppcheck 或 make all_cppcheck_reports
# ================================

# 生成编译数据库
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# ============ 查找工具 ============
find_program(CPPCHECK_EXECUTABLE cppcheck)
if(NOT CPPCHECK_EXECUTABLE)
    message(FATAL_ERROR "Cppcheck executable not found. Please install Cppcheck or set its path.")
endif()

find_package(Python3 COMPONENTS Interpreter)
if(NOT Python3_FOUND)
    message(WARNING "Python3 not found. Will use unfiltered compile_commands.json")
endif()

find_program(XSLTPROC_EXECUTABLE xsltproc)
if(NOT XSLTPROC_EXECUTABLE)
    message(FATAL_ERROR "xsltproc executable not found. Please install xsltproc.")
endif()

# ============ 文件路径配置 ============
set(FILTER_SCRIPT ${CMAKE_SOURCE_DIR}/cmake/modules/filter_compile_commands.py)
set(FILTERED_COMPILE_COMMANDS ${CMAKE_BINARY_DIR}/compile_commands_filtered.json)
set(CPPCHECK_SUPPRESSIONS ${CMAKE_BINARY_DIR}/cppcheck_suppressions.txt)
set(CPPCHECK_XML_REPORT ${CMAKE_BINARY_DIR}/cppcheck_report.xml)
set(CPPCHECK_HTML_REPORT ${CMAKE_BINARY_DIR}/cppcheck_report.html)
set(CPPCHECK_XSLT_STYLESHEET ${CMAKE_SOURCE_DIR}/xslt/cppcheck_report.xslt)

# 确保 XSLT 样式表存在
if(NOT EXISTS ${CPPCHECK_XSLT_STYLESHEET})
    message(FATAL_ERROR "Cppcheck XSLT stylesheet not found: ${CPPCHECK_XSLT_STYLESHEET}")
endif()

# ============ 生成抑制规则文件 ============
# 排除常见第三方库的头文件检查(ROS、Eigen3、PCL、Boost等)
file(WRITE ${CPPCHECK_SUPPRESSIONS} "# Suppress all checks in third-party libraries
# ROS libraries
*:/opt/ros/*
*:*/ros/*
*:*/catkin/*
*:*/roscpp/*
*:*/rosbag/*
*:*/std_msgs/*
*:*/sensor_msgs/*
*:*/geometry_msgs/*
*:*/nav_msgs/*
*:*/tf/*
*:*/tf2/*

# Eigen library
*:*/eigen3/*
*:*/Eigen/*

# PCL library
*:*/pcl/*
*:*/pcl-*/include/*

# TBB library
*:*/tbb/*
*:*/oneapi/*

# Boost library
*:*/boost/*

# OpenCV library
*:*/opencv/*
*:*/opencv2/*
*:*/opencv4/*

# System libraries
*:/usr/include/*
*:/usr/local/include/*

# Project third-party directories
*:*/thirdparty/*
*:*/_deps/*
*:*/external/*
*:*/third_party/*
*:*/vendor/*
")

# ============ 过滤编译数据库 ============
if(Python3_FOUND)
    add_custom_target(filter_compile_commands
        COMMAND ${Python3_EXECUTABLE} ${FILTER_SCRIPT} 
                ${CMAKE_BINARY_DIR}/compile_commands.json
                ${FILTERED_COMPILE_COMMANDS}
                ${CMAKE_SOURCE_DIR}
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        COMMENT "Filtering compile_commands.json to exclude third-party libraries..."
        VERBATIM
    )
    set(CPPCHECK_COMPILE_DB ${FILTERED_COMPILE_COMMANDS})
else()
    set(CPPCHECK_COMPILE_DB ${CMAKE_BINARY_DIR}/compile_commands.json)
endif()

# ============ 运行 Cppcheck ============
add_custom_target(run_cppcheck
    COMMAND ${CPPCHECK_EXECUTABLE}
            --inline-suppr
            --std=c++17
            --enable=all
            --quiet
            --force
            --inconclusive
            --language=c++
            --project=${CPPCHECK_COMPILE_DB}
            --suppressions-list=${CPPCHECK_SUPPRESSIONS}
            --suppress=noValidConfiguration
            --suppress=missingIncludeSystem
            --max-configs=1
            -i${CMAKE_SOURCE_DIR}/thirdparty
            -i${CMAKE_BINARY_DIR}/_deps
            --xml
            --xml-version=2
            --output-file=${CPPCHECK_XML_REPORT}
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    COMMENT "Running Cppcheck static analysis..."
    VERBATIM
)

if(Python3_FOUND)
    add_dependencies(run_cppcheck filter_compile_commands)
endif()

# ============ 生成 HTML 报告 ============
add_custom_target(generate_cppcheck_html
    COMMAND ${XSLTPROC_EXECUTABLE} ${CPPCHECK_XSLT_STYLESHEET} ${CPPCHECK_XML_REPORT} > ${CPPCHECK_HTML_REPORT}
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    COMMENT "Generating HTML report from Cppcheck XML..."
    VERBATIM
)

add_dependencies(generate_cppcheck_html run_cppcheck)

# ============ 组合目标 ============
add_custom_target(all_cppcheck_reports)
add_dependencies(all_cppcheck_reports generate_cppcheck_html)

cppcheck_report.xslt

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" encoding="UTF-8" omit-xml-declaration="yes"/>

<xsl:template match="/">
<html>
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Cppcheck 报告</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      margin: 20px;
      color: #333;
      background-color: #f8f9fa;
    }
    .header {
      background: linear-gradient(135deg, #1a237e, #0d47a1); /* 加深蓝色梯度 */
      color: white;
      text-align: center;
      padding: 25px 30px;
      border-radius: 8px;
      margin-bottom: 25px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); /* 加深阴影 */
    }
    .header h1 {
      font-weight: 700; /* 加粗字体 */
      margin-bottom: 8px;
      font-size: 2.4rem; /* 增大字号 */
      text-shadow: 0 2px 4px rgba(0,0,0,0.3); /* 添加文字阴影 */
      letter-spacing: 0.5px; /* 增加字间距 */
    }
    .header p {
      opacity: 0.9;
      font-size: 1.1rem;
      text-shadow: 0 1px 2px rgba(0,0,0,0.2); /* 副标题也添加阴影 */
    }
    @media (max-width: 768px) {
      .header h1 {
        font-size: 1.8rem;
      }
    }
    table {
      border-collapse: collapse;
      width: 100%;
      margin-top: 15px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
      background-color: white;
      border-radius: 8px;
      overflow: hidden;
    }
    th, td {
      border: 1px solid #f0f0f0;
      padding: 14px 20px;
      text-align: left;
      vertical-align: top;
    }
    th {
      background-color: #f5f7ff;
      color: #1a237e;
      font-weight: 600;
      border-bottom: 2px solid #e0e0e0;
    }
    tr:hover {
      background-color: #f8fbff;
    }
    .severity-tag {
      display: inline-block;
      padding: 5px 12px;
      border-radius: 14px;
      font-size: 0.85rem;
      font-weight: 600;
      text-align: center;
      min-width: 80px;
    }
    .error .severity-tag { 
      background-color: #ffcdd2; 
      color: #d32f2f; 
    }
    .warning .severity-tag { 
      background-color: #ffe0b2; 
      color: #f57c00; 
    }
    .style .severity-tag { 
      background-color: #bbdefb; 
      color: #1976d2; 
    }
    .information .severity-tag { 
      background-color: #e1bee7; 
      color: #7b1fa2; 
    }
    .location {
      font-family: monospace;
      font-size: 0.9em;
      color: #555;
    }
    .summary {
      background-color: white;
      padding: 25px;
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
      margin: 20px 0;
    }
    .summary-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
      gap: 18px;
      margin-top: 10px;
    }
    .summary-item {
      background: white;
      padding: 20px 15px;
      border-radius: 8px;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.07);
      text-align: center;
      border: 1px solid #e0e0e0;
      transition: all 0.3s ease;
    }
    .summary-item:hover {
      transform: translateY(-3px);
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
    }
    .stats-label {
      font-size: 0.95rem;
      color: #616161;
      margin-bottom: 8px;
      font-weight: 500;
    }
    .stats-value {
      font-weight: 700;
      font-size: 2.2rem;
    }
    .error-count { color: #d32f2f; }
    .warning-count { color: #f57c00; }
    .style-count { color: #1976d2; }
    .information-count { color: #7b1fa2; }
    .footer {
      text-align: center;
      margin-top: 30px;
      color: #757575;
      font-size: 0.9rem;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>Cppcheck 静态分析报告</h1>
    <p>版本 <xsl:value-of select="results/cppcheck/@version"/> | 报告格式版本 <xsl:value-of select="results/@version"/></p>
  </div>
  
  <!-- 汇总统计 -->
  <div class="summary">
    <h2>📊 问题概览</h2>
    <div class="summary-grid">
      <xsl:variable name="errors" select="count(results/errors/error[@severity='error'])"/>
      <xsl:variable name="warnings" select="count(results/errors/error[@severity='warning'])"/>
      <xsl:variable name="styles" select="count(results/errors/error[@severity='style'])"/>
      <xsl:variable name="infos" select="count(results/errors/error[@severity='information'])"/>
      <xsl:variable name="total" select="count(results/errors/error)"/>
      
      <div class="summary-item">
        <div class="stats-label">问题总数</div>
        <div class="stats-value"><xsl:value-of select="$total"/></div>
      </div>
      
      <div class="summary-item">
        <div class="stats-label">错误</div>
        <div class="stats-value error-count"><xsl:value-of select="$errors"/></div>
      </div>
      
      <div class="summary-item">
        <div class="stats-label">警告</div>
        <div class="stats-value warning-count"><xsl:value-of select="$warnings"/></div>
      </div>
      
      <div class="summary-item">
        <div class="stats-label">代码风格</div>
        <div class="stats-value style-count"><xsl:value-of select="$styles"/></div>
      </div>
      
      <div class="summary-item">
        <div class="stats-label">信息</div>
        <div class="stats-value information-count"><xsl:value-of select="$infos"/></div>
      </div>
    </div>
  </div>
  
  <!-- 问题详情表格 -->
  <h2>🔍 详细问题列表</h2>
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>严重程度</th>
        <th>消息</th>
        <th>位置</th>
        <th>CWE</th>
      </tr>
    </thead>
    <tbody>
      <xsl:choose>
        <xsl:when test="count(results/errors/error) = 0">
          <tr>
            <td colspan="5" style="text-align:center; padding:30px; background-color:#f9f9f9;">
              🎉 恭喜!未发现任何静态分析问题
            </td>
          </tr>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select="results/errors/error"/>
        </xsl:otherwise>
      </xsl:choose>
    </tbody>
  </table>
  
  <div class="footer">
    <p>报告生成时间: <script>document.write(new Date().toLocaleString());</script></p>
    <p>问题反馈: jassimxiong@gmail.com</p>
  </div>
</body>
</html>
</xsl:template>

<xsl:template match="error">
  <tr class="{@severity}">
    <td><xsl:value-of select="@id"/></td>
    <td>
      <span class="severity-tag"><xsl:value-of select="@severity"/></span>
    </td>
    <td>
      <strong><xsl:value-of select="@msg"/></strong>
      <div style="margin-top:5px; font-size:0.9em; color:#666;">
        <xsl:value-of select="@verbose"/>
      </div>
    </td>
    <td class="location">
      <xsl:choose>
        <xsl:when test="location">
          <div><strong>文件:</strong> <xsl:value-of select="location/@file"/></div>
          <div><strong>行:</strong> <xsl:value-of select="location/@line"/></div>
          <xsl:if test="location/@column">
            <div><strong>列:</strong> <xsl:value-of select="location/@column"/></div>
          </xsl:if>
          <xsl:if test="location/symbol">
            <div><strong>符号:</strong> <xsl:value-of select="location/symbol"/></div>
          </xsl:if>
        </xsl:when>
        <xsl:otherwise>全局问题</xsl:otherwise>
      </xsl:choose>
    </td>
    <td>
      <xsl:choose>
        <xsl:when test="@cwe">
          <a href="https://cwe.mitre.org/data/definitions/{@cwe}.html" target="_blank" style="color:#1a237e; text-decoration:none;">
            CWE-<xsl:value-of select="@cwe"/>
          </a>
        </xsl:when>
        <xsl:otherwise>-</xsl:otherwise>
      </xsl:choose>
    </td>
  </tr>
</xsl:template>

</xsl:stylesheet>

使用

使用 Cppcheck 对项目C++代码进行静态分析,自动排除第三方库(ROS、Eigen3、PCL、Boost等)的检查。

  • 依赖

    sudo apt install cppcheck xsltproc python3
    
  • 使用

    cd build
    cmake ..
    
    # 方式1:生成完整报告(推荐)
    make all_cppcheck_reports
    
    # 方式2:只运行静态检查
    make run_cppcheck
    
    # 方式3:只生成HTML报告(需先运行cppcheck)
    make generate_cppcheck_html
    
    # 或者
    cmake --build . --target run_cppcheck --parallel           # 进行静态检查
    cmake --build . --target all_cppcheck_reports --parallel   # 进行静态检查并生成html报告
    
  • 查看结果

    • XML报告build/cppcheck_report.xml

    • HTML报告build/cppcheck_report.html (推荐,可在浏览器中打开)

    • 过滤后的编译数据库build/compile_commands_filtered.json

  • 配置说明

    静态检查配置位于 cmake/static_check.cmake,已配置为:

    • ✅ 只检查 src/app/ 目录下的项目代码

    • ✅ 排除所有第三方库(ROS、Eigen3、PCL、TBB、Boost、OpenCV等)

    • ✅ 抑制配置相关的信息级别警告(noValidConfiguration、missingIncludeSystem)

    • ✅ 使用C++17标准进行检查

​ 如需修改检查范围或规则,编辑 cmake/static_check.cmakecmake/filter_compile_commands.py

动态检测

Sanitizer是由Google发起的开源工具集,用于检测内存泄露等问题。

  • AddressSanitizer,检测内存访问问题
  • MemorySanitizer,检测未初始化内存问题
  • ThreadSanitizer,检测线程竞态和死锁问题
  • LeakSanitizer,检测内存泄露问题

参考sanitizers-cmake实现调用

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project(ioctree_demo)

add_compile_options(-std=c++14 )
set(CMAKE_CXX_FLAGS "-std=c++14 -pthread")  # 注意不能开启代码优化,否则部分内存泄漏会被优化
add_definitions(-w)
find_package(PCL 1.8 REQUIRED)

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
find_package(Sanitizers REQUIRED)

include_directories(
    ${PCL_INCLUDE_DIRS}
    ikd-Tree
    octree2
)

add_executable(time_compare examples/time_compare.cpp ikd-Tree/ikd_Tree.cpp)
target_link_libraries(time_compare ${PCL_LIBRARIES})
add_sanitizers(time_compare)

编译时加入编译选项启用功能,SANITIZE_ADDRESS, SANITIZE_MEMORY, SANITIZE_THREAD or SANITIZE_UNDEFINED

cmake .. -DSANITIZE_ADDRESS=On

AddressSanitizer

问题记录

报错找不到libasan.so.5库,设置-static-libasan,链接静态库。

https://blog.csdn.net/register_k/article/details/120319834

参考

开发调试利器 | 内存检测工具Sanitizer

【NO.114】C++内存管理及内存问题的分析

资源分析

pidstat是sysstat工具的一个命令,用于监控全部或指定进程的cpu、内存、线程、设备IO等系统资源的占用情况。

pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]

常用的参数:

  • -u:默认的参数,显示各个进程的cpu使用统计
  • -r:显示各个进程的内存使用统计
  • -d:显示各个进程的IO使用情况
  • -p:指定进程号
  • -w:显示每个进程的上下文切换情况
  • -t:显示选择任务的线程的统计信息外的额外信息
  • -T { TASK | CHILD | ALL } 这个选项指定了pidstat监控的。TASK表示报告独立的task,CHILD关键字表示报告进程下所有线程统计信息。ALL表示报告独立的task和task下面的所有线程。 注意:task和子线程的全局的统计信息和pidstat选项无关。这些统计信息不会对应到当前的统计间隔,这些统计信息只有在子线程kill或者完成的时候才会被收集。
  • -V:版本号
  • -h:在一行上显示了所有活动,这样其他程序可以容易解析。
  • -I:在SMP环境,表示任务的CPU使用率/内核数量
  • -l:显示命令名和所有参数

例子按2s频率监听进程的CPU和内存占用,并写入到日志里

pidstat -u -r -p 15249 2 > run_mapping.log

使用python解析日志并输出分析

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PIDSTAT Log Analysis Tool
Features:
1. Read pidstat log file with CPU and memory usage data
2. Calculate average and maximum resource usage statistics
3. Generate statistical report
4. Plot CPU and memory usage vs time charts with English labels

Usage:
python pidstat_analyzer.py <log_file_path>

Example:
python pidstat_analyzer.py pidstat.log

Output files:
- resource_report.txt: Resource usage statistics report
- cpu_usage.png: CPU usage chart
- memory_usage.png: Memory usage chart
"""

import re
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime, timedelta
import matplotlib.dates as mdates
import sys
import os
import matplotlib

# Configure matplotlib for better English text rendering
matplotlib.use('Agg')  # Use non-interactive backend
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False  # Fix negative sign display

def parse_pidstat_log(file_path):
    """
    Parse pidstat log file to extract CPU and memory data
    
    Args:
        file_path (str): Path to the log file
        
    Returns:
        tuple: (cpu_data_list, mem_data_list)
            cpu_data_list: List of CPU data dictionaries
            mem_data_list: List of memory data dictionaries
    """
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
    except FileNotFoundError:
        print(f"Error: File '{file_path}' not found")
        sys.exit(1)
    except Exception as e:
        print(f"Error: Failed to read file - {e}")
        sys.exit(1)
    
    cpu_data = []
    mem_data = []
    current_section = None
    header_pattern = re.compile(r'\s*UID\s+PID\s+')
    
    for line_num, line in enumerate(lines, 1):
        line = line.strip()
        if not line:
            continue
        
        # Skip system info line
        if line.startswith('Linux'):
            continue
        
        # Detect CPU data header
        if re.search(r'%usr\s+%system\s+%guest\s+%wait\s+%CPU\s+CPU\s+Command', line):
            current_section = 'cpu'
            continue
        
        # Detect memory data header
        if re.search(r'minflt/s\s+majflt/s\s+VSZ\s+RSS\s+%MEM\s+Command', line):
            current_section = 'mem'
            continue
        
        # Skip header lines that contain "UID PID"
        if header_pattern.match(line):
            continue
        
        # Parse time - handle both Chinese and numeric formats
        time_match = re.match(r'(\d+)[时:](\d+)[分:](\d+)秒?', line)
        if not time_match:
            continue
        
        hour, minute, second = map(int, time_match.groups())
        time_str = f"{hour:02d}:{minute:02d}:{second:02d}"
        
        # Extract data part after time
        time_prefix = f"{hour}{minute}{second}秒"
        if time_prefix in line:
            data_part = line[len(time_prefix):].strip()
        else:
            # Try alternative time format
            time_prefix_alt = f"{hour:02d}:{minute:02d}:{second:02d}"
            if time_prefix_alt in line:
                data_part = line[len(time_prefix_alt):].strip()
            else:
                data_part = line[time_match.end():].strip()
        
        if not data_part:
            continue
        
        # Split data by whitespace, but handle command name properly
        parts = re.split(r'\s+', data_part)
        
        try:
            if current_section == 'cpu':
                # Expected format: UID PID %usr %system %guest %wait %CPU CPU Command
                if len(parts) < 9:
                    print(f"Warning: Line {line_num} has insufficient CPU data fields ({len(parts)} vs expected 9+)")
                    continue
                
                uid = int(parts[0])
                pid = int(parts[1])
                usr = float(parts[2])
                system = float(parts[3])
                guest = float(parts[4])
                wait = float(parts[5])
                cpu_percent = float(parts[6])
                cpu_core = int(float(parts[7]))  # Handle potential float values
                command = ' '.join(parts[8:])
                
                cpu_data.append({
                    'time': time_str,
                    'pid': pid,
                    'usr': usr,
                    'system': system,
                    'guest': guest,
                    'wait': wait,
                    'cpu_percent': cpu_percent,
                    'cpu_core': cpu_core,
                    'command': command.strip()
                })
            
            elif current_section == 'mem':
                # Expected format: UID PID minflt/s majflt/s VSZ RSS %MEM Command
                if len(parts) < 8:
                    print(f"Warning: Line {line_num} has insufficient memory data fields ({len(parts)} vs expected 8+)")
                    continue
                
                uid = int(parts[0])
                pid = int(parts[1])
                minflt = float(parts[2])
                majflt = float(parts[3])
                vsz = int(float(parts[4]))  # Handle potential float values
                rss = int(float(parts[5]))  # Handle potential float values
                mem_percent = float(parts[6])
                command = ' '.join(parts[7:])
                
                mem_data.append({
                    'time': time_str,
                    'pid': pid,
                    'minflt/s': minflt,
                    'majflt/s': majflt,
                    'VSZ': vsz,
                    'RSS': rss,
                    'mem_percent': mem_percent,
                    'command': command.strip()
                })
        
        except (ValueError, IndexError) as e:
            print(f"Warning: Failed to parse line {line_num} - {e}")
            print(f"  Line content: {line}")
            print(f"  Parts: {parts}")
            continue
    
    return cpu_data, mem_data

def calculate_statistics(cpu_df, mem_df):
    """
    Calculate resource usage statistics
    
    Args:
        cpu_df (DataFrame): CPU data DataFrame
        mem_df (DataFrame): Memory data DataFrame
        
    Returns:
        dict: Statistics dictionary
    """
    stats = {'cpu': {}, 'mem': {}}
    
    if not cpu_df.empty:
        stats['cpu'] = {
            'avg_cpu_percent': cpu_df['cpu_percent'].mean(),
            'max_cpu_percent': cpu_df['cpu_percent'].max(),
            'avg_usr': cpu_df['usr'].mean(),
            'max_usr': cpu_df['usr'].max(),
            'avg_system': cpu_df['system'].mean(),
            'max_system': cpu_df['system'].max(),
            'avg_wait': cpu_df['wait'].mean(),
            'max_wait': cpu_df['wait'].max(),
            'avg_guest': cpu_df['guest'].mean(),
            'max_guest': cpu_df['guest'].max(),
            'max_cpu_time': cpu_df.loc[cpu_df['cpu_percent'].idxmax(), 'time'] if not cpu_df.empty else '',
            'max_core_usage': cpu_df['cpu_core'].value_counts().idxmax() if not cpu_df.empty else 0
        }
    
    if not mem_df.empty:
        stats['mem'] = {
            'avg_mem_percent': mem_df['mem_percent'].mean(),
            'max_mem_percent': mem_df['mem_percent'].max(),
            'avg_rss': mem_df['RSS'].mean(),
            'max_rss': mem_df['RSS'].max(),
            'avg_vsz': mem_df['VSZ'].mean(),
            'max_vsz': mem_df['VSZ'].max(),
            'avg_minflt': mem_df['minflt/s'].mean(),
            'max_minflt': mem_df['minflt/s'].max(),
            'avg_majflt': mem_df['majflt/s'].mean(),
            'max_majflt': mem_df['majflt/s'].max(),
            'max_mem_time': mem_df.loc[mem_df['mem_percent'].idxmax(), 'time'] if not mem_df.empty else '',
            'max_rss_time': mem_df.loc[mem_df['RSS'].idxmax(), 'time'] if not mem_df.empty else ''
        }
    
    return stats

def plot_cpu_usage(cpu_df, output_prefix='cpu_usage'):
    """
    Plot CPU usage chart
    
    Args:
        cpu_df (DataFrame): CPU data DataFrame
        output_prefix (str): Output file prefix
    """
    if cpu_df.empty:
        print("Warning: No CPU data available, skipping plot")
        return
    
    # Convert time to datetime objects
    base_date = datetime(2025, 11, 14)  # Use date from log
    cpu_df['datetime'] = cpu_df['time'].apply(
        lambda x: base_date + timedelta(
            hours=int(x.split(':')[0]),
            minutes=int(x.split(':')[1]),
            seconds=int(x.split(':')[2])
        )
    )
    
    plt.figure(figsize=(14, 10))
    
    # CPU Usage Overview
    ax1 = plt.subplot(2, 1, 1)
    ax1.plot(cpu_df['datetime'], cpu_df['cpu_percent'], 'b-', linewidth=2.5, 
             label=f'Total CPU Usage (Avg: {cpu_df["cpu_percent"].mean():.1f}%)')
    ax1.plot(cpu_df['datetime'], cpu_df['usr'], 'g--', linewidth=2, 
             label=f'User Mode (Avg: {cpu_df["usr"].mean():.1f}%)')
    ax1.plot(cpu_df['datetime'], cpu_df['system'], 'r--', linewidth=2, 
             label=f'System Mode (Avg: {cpu_df["system"].mean():.1f}%)')
    ax1.plot(cpu_df['datetime'], cpu_df['wait'], 'y--', linewidth=2, 
             label=f'I/O Wait (Avg: {cpu_df["wait"].mean():.1f}%)')
    
    ax1.set_title('CPU Usage Monitoring', fontsize=16, fontweight='bold', pad=20)
    ax1.set_ylabel('CPU Usage (%)', fontsize=12)
    ax1.grid(True, alpha=0.3, linestyle='--')
    ax1.legend(loc='upper right', fontsize=10)
    
    # CPU Core Allocation
    ax2 = plt.subplot(2, 1, 2)
    ax2.plot(cpu_df['datetime'], cpu_df['cpu_core'], 'o-', color='purple', 
             linewidth=2, markersize=8, label=f'CPU Core (Most used: {cpu_df["cpu_core"].value_counts().idxmax()})')
    ax2.fill_between(cpu_df['datetime'], cpu_df['cpu_core'], alpha=0.2, color='purple')
    
    ax2.set_title('CPU Core Allocation', fontsize=16, fontweight='bold', pad=20)
    ax2.set_ylabel('CPU Core Number', fontsize=12)
    ax2.set_xlabel('Time', fontsize=12)
    ax2.grid(True, alpha=0.3, linestyle='--')
    ax2.legend(loc='upper right', fontsize=10)
    ax2.set_ylim(-0.5, cpu_df['cpu_core'].max() + 1)
    
    # Format x-axis
    for ax in [ax1, ax2]:
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
        ax.xaxis.set_major_locator(mdates.AutoDateLocator())
        plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
    
    plt.tight_layout()
    output_file = f'{output_prefix}.png'
    plt.savefig(output_file, dpi=300, bbox_inches='tight')
    print(f"✓ CPU chart generated: {output_file}")
    plt.close()

def plot_memory_usage(mem_df, output_prefix='memory_usage'):
    """
    Plot memory usage chart
    
    Args:
        mem_df (DataFrame): Memory data DataFrame
        output_prefix (str): Output file prefix
    """
    if mem_df.empty:
        print("Warning: No memory data available, skipping plot")
        return
    
    # Convert time to datetime objects
    base_date = datetime(2025, 11, 14)  # Use date from log
    mem_df['datetime'] = mem_df['time'].apply(
        lambda x: base_date + timedelta(
            hours=int(x.split(':')[0]),
            minutes=int(x.split(':')[1]),
            seconds=int(x.split(':')[2])
        )
    )
    
    plt.figure(figsize=(14, 12))
    
    # Memory Usage Percentage
    ax1 = plt.subplot(2, 1, 1)
    ax1.plot(mem_df['datetime'], mem_df['mem_percent'], 'b-', linewidth=3, 
             label=f'Memory Usage (Avg: {mem_df["mem_percent"].mean():.2f}%)')
    
    ax1.set_title('Memory Usage Monitoring', fontsize=16, fontweight='bold', pad=20)
    ax1.set_ylabel('Memory Usage (%)', fontsize=12)
    ax1.grid(True, alpha=0.3, linestyle='--')
    ax1.legend(loc='upper right', fontsize=10)
    
    # Memory Size (RSS vs VSZ)
    ax2 = plt.subplot(2, 1, 2)
    rss_mb = mem_df['RSS'] / 1024  # Convert to MB
    vsz_mb = mem_df['VSZ'] / 1024  # Convert to MB
    
    ax2.plot(mem_df['datetime'], rss_mb, 'g-', linewidth=2.5, 
             label=f'RSS Physical Memory (Avg: {rss_mb.mean():.1f} MB)')
    ax2.plot(mem_df['datetime'], vsz_mb, 'r-', linewidth=2.5, 
             label=f'VSZ Virtual Memory (Avg: {vsz_mb.mean():.1f} MB)')
    
    ax2.set_title('Memory Size Monitoring', fontsize=16, fontweight='bold', pad=20)
    ax2.set_ylabel('Memory Size (MB)', fontsize=12)
    ax2.set_xlabel('Time', fontsize=12)
    ax2.grid(True, alpha=0.3, linestyle='--')
    ax2.legend(loc='upper right', fontsize=10)
    
    # Format x-axis
    for ax in [ax1, ax2]:
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
        ax.xaxis.set_major_locator(mdates.AutoDateLocator())
        plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
    
    plt.tight_layout()
    output_file = f'{output_prefix}.png'
    plt.savefig(output_file, dpi=300, bbox_inches='tight')
    print(f"✓ Memory chart generated: {output_file}")
    plt.close()

def generate_report(stats, cpu_df, mem_df, output_file='resource_report.txt'):
    """
    Generate resource usage statistics report
    
    Args:
        stats (dict): Statistics dictionary
        cpu_df (DataFrame): CPU data DataFrame
        mem_df (DataFrame): Memory data DataFrame
        output_file (str): Output report filename
    """
    with open(output_file, 'w', encoding='utf-8') as f:
        f.write("=" * 60 + "\n")
        f.write("                    Resource Usage Statistics Report\n")
        f.write("=" * 60 + "\n\n")
        
        f.write(f"Report Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write(f"Data Points Analyzed: CPU: {len(cpu_df)}, Memory: {len(mem_df)}\n")
        if not cpu_df.empty and not mem_df.empty:
            f.write(f"Process Analyzed: {cpu_df['command'].iloc[0]}\n")
        f.write("\n")
        
        if 'cpu' in stats and stats['cpu']:
            f.write("📈 CPU USAGE STATISTICS:\n")
            f.write("-" * 60 + "\n")
            f.write(f"  Average CPU Usage:        {stats['cpu']['avg_cpu_percent']:6.2f}%\n")
            f.write(f"  Maximum CPU Usage:        {stats['cpu']['max_cpu_percent']:6.2f}%\n")
            f.write(f"  Average User Mode:        {stats['cpu']['avg_usr']:6.2f}%\n")
            f.write(f"  Maximum User Mode:        {stats['cpu']['max_usr']:6.2f}%\n")
            f.write(f"  Average System Mode:      {stats['cpu']['avg_system']:6.2f}%\n")
            f.write(f"  Maximum System Mode:      {stats['cpu']['max_system']:6.2f}%\n")
            f.write(f"  Average I/O Wait:         {stats['cpu']['avg_wait']:6.2f}%\n")
            f.write(f"  Maximum I/O Wait:         {stats['cpu']['max_wait']:6.2f}%\n")
            f.write(f"  Average Guest Mode:       {stats['cpu']['avg_guest']:6.2f}%\n")
            f.write(f"  Maximum Guest Mode:       {stats['cpu']['max_guest']:6.2f}%\n")
            f.write(f"  Most Used CPU Core:       {stats['cpu']['max_core_usage']}\n")
            f.write(f"  Peak CPU Usage Time:      {stats['cpu'].get('max_cpu_time', 'N/A')}\n\n")
        
        if 'mem' in stats and stats['mem']:
            f.write("📊 MEMORY USAGE STATISTICS:\n")
            f.write("-" * 60 + "\n")
            f.write(f"  Average Memory Usage:     {stats['mem']['avg_mem_percent']:6.2f}%\n")
            f.write(f"  Maximum Memory Usage:     {stats['mem']['max_mem_percent']:6.2f}%\n")
            f.write(f"  Average RSS Memory:       {stats['mem']['avg_rss']/1024:6.2f} MB\n")
            f.write(f"  Maximum RSS Memory:       {stats['mem']['max_rss']/1024:6.2f} MB\n")
            f.write(f"  Average VSZ Memory:       {stats['mem']['avg_vsz']/1024:6.2f} MB\n")
            f.write(f"  Maximum VSZ Memory:       {stats['mem']['max_vsz']/1024:6.2f} MB\n")
            f.write(f"  Average Minor Page Faults:{stats['mem']['avg_minflt']:6.1f}/sec\n")
            f.write(f"  Maximum Minor Page Faults:{stats['mem']['max_minflt']:6.1f}/sec\n")
            f.write(f"  Average Major Page Faults:{stats['mem']['avg_majflt']:6.1f}/sec\n")
            f.write(f"  Maximum Major Page Faults:{stats['mem']['max_majflt']:6.1f}/sec\n")
            f.write(f"  Peak Memory Time:         {stats['mem'].get('max_mem_time', 'N/A')}\n")
            f.write(f"  Peak RSS Memory Time:     {stats['mem'].get('max_rss_time', 'N/A')}\n\n")
        
        f.write("=" * 60 + "\n")
        f.write("ANALYSIS NOTES:\n")
        f.write("-" * 60 + "\n")
        f.write("• CPU Usage = User Mode + System Mode + I/O Wait + Guest Mode\n")
        f.write("• RSS (Resident Set Size): Actual physical memory used by the process\n")
        f.write("• VSZ (Virtual Memory Size): Total virtual memory allocated to the process\n")
        f.write("• %MEM: Percentage of total physical memory used by the process\n")
        f.write("• Minor Page Faults: Page faults that didn't require disk I/O\n")
        f.write("• Major Page Faults: Page faults that required disk I/O\n")
        f.write("=" * 60 + "\n")
    
    print(f"✓ Statistics report generated: {output_file}")

def main():
    """
    Main function
    """
    if len(sys.argv) != 2:
        print("\n" + "=" * 60)
        print("                    PIDSTAT LOG ANALYSIS TOOL")
        print("=" * 60)
        print(f"\nUsage: {os.path.basename(sys.argv[0])} <pidstat_log_file_path>")
        print("\nExamples:")
        print(f"  {os.path.basename(sys.argv[0])} pidstat.log")
        print(f"  {os.path.basename(sys.argv[0])} /path/to/your/pidstat_output.log")
        print("\n" + "=" * 60)
        print("OUTPUT FILES:")
        print("  resource_report.txt - Detailed statistics report")
        print("  cpu_usage.png      - CPU usage visualization")
        print("  memory_usage.png   - Memory usage visualization")
        print("=" * 60 + "\n")
        sys.exit(1)
    
    log_file = sys.argv[1]
    
    print("\n" + "=" * 60)
    print("STARTING PIDSTAT LOG ANALYSIS")
    print("=" * 60)
    print(f"Log file: {log_file}")
    print(f"Start time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("-" * 60 + "\n")
    
    # Parse log
    cpu_data, mem_data = parse_pidstat_log(log_file)
    
    if not cpu_data and not mem_data:
        print("Error: No valid CPU or memory data parsed. Please check log file format")
        sys.exit(1)
    
    # Convert to DataFrames
    cpu_df = pd.DataFrame(cpu_data) if cpu_data else pd.DataFrame()
    mem_df = pd.DataFrame(mem_data) if mem_data else pd.DataFrame()
    
    print(f"✅ PARSING COMPLETED SUCCESSFULLY")
    print(f"   • CPU data points: {len(cpu_df)}")
    print(f"   • Memory data points: {len(mem_df)}")
    if not cpu_df.empty:
        print(f"   • Process name (CPU): {cpu_df['command'].iloc[0]}")
    if not mem_df.empty:
        print(f"   • Process name (Memory): {mem_df['command'].iloc[0]}")
    print()
    
    # Calculate statistics
    stats = calculate_statistics(cpu_df, mem_df)
    
    # Generate report
    generate_report(stats, cpu_df, mem_df)
    
    # Plot charts
    if not cpu_df.empty:
        plot_cpu_usage(cpu_df)
    
    if not mem_df.empty:
        plot_memory_usage(mem_df)
    
    # Print summary
    print("\n" + "=" * 60)
    print("📊 ANALYSIS SUMMARY")
    print("=" * 60)
    
    if not cpu_df.empty:
        print(f"CPU USAGE SUMMARY:")
        print(f"  • Average Total CPU: {stats['cpu']['avg_cpu_percent']:.2f}%")
        print(f"  • Maximum Total CPU: {stats['cpu']['max_cpu_percent']:.2f}%")
        print(f"  • Peak CPU Time: {stats['cpu'].get('max_cpu_time', 'N/A')}")
        print(f"  • Most Used Core: #{stats['cpu']['max_core_usage']}")
    
    if not mem_df.empty:
        print(f"\nMEMORY USAGE SUMMARY:")
        print(f"  • Average Memory Usage: {stats['mem']['avg_mem_percent']:.2f}%")
        print(f"  • Maximum Memory Usage: {stats['mem']['max_mem_percent']:.2f}%")
        print(f"  • Average RSS Memory: {stats['mem']['avg_rss']/1024:.2f} MB")
        print(f"  • Maximum RSS Memory: {stats['mem']['max_rss']/1024:.2f} MB")
        print(f"  • Peak Memory Time: {stats['mem'].get('max_mem_time', 'N/A')}")
    
    print(f"\nFILES GENERATED:")
    print(f"  • resource_report.txt")
    print(f"  • cpu_usage.png")
    print(f"  • memory_usage.png")
    
    print("\n" + "=" * 60)
    print("ANALYSIS COMPLETED SUCCESSFULLY!")
    print(f"End time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 60)

if __name__ == "__main__":
    main()