1041 lines
24 KiB
Markdown
1041 lines
24 KiB
Markdown
|
|
# Perfetto分析Trace提效方案
|
|||
|
|
|
|||
|
|
## 一、提效概述
|
|||
|
|
|
|||
|
|
### 1. 提效目标
|
|||
|
|
|
|||
|
|
- **减少分析时间**:从数小时缩短到数十分钟
|
|||
|
|
- **提高分析准确性**:减少人为错误
|
|||
|
|
- **标准化流程**:建立可复用的分析流程
|
|||
|
|
- **自动化分析**:减少重复性工作
|
|||
|
|
- **知识积累**:建立分析模板和案例库
|
|||
|
|
|
|||
|
|
### 2. 提效策略
|
|||
|
|
|
|||
|
|
1. **工具自动化**:使用脚本和工具自动化重复操作
|
|||
|
|
2. **流程标准化**:建立标准化的分析流程
|
|||
|
|
3. **模板复用**:使用配置模板和分析模板
|
|||
|
|
4. **批量处理**:批量录制和分析Trace
|
|||
|
|
5. **智能分析**:使用SQL和工具进行智能分析
|
|||
|
|
|
|||
|
|
## 二、工具和脚本自动化
|
|||
|
|
|
|||
|
|
### 1. 快速录制脚本
|
|||
|
|
|
|||
|
|
#### 1.1 通用录制脚本
|
|||
|
|
|
|||
|
|
**quick_trace.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 快速录制Trace脚本
|
|||
|
|
# 用法: ./quick_trace.sh [时长] [输出文件] [应用包名]
|
|||
|
|
|
|||
|
|
DURATION=${1:-10}
|
|||
|
|
OUTPUT=${2:-trace.pb}
|
|||
|
|
PACKAGE=${3:-""}
|
|||
|
|
|
|||
|
|
echo "开始录制 ${DURATION} 秒的Trace..."
|
|||
|
|
|
|||
|
|
if [ -z "$PACKAGE" ]; then
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t ${DURATION}s
|
|||
|
|
else
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
|||
|
|
-a ${PACKAGE} -t ${DURATION}s
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
echo "拉取Trace文件..."
|
|||
|
|
adb pull /data/misc/perfetto-traces/trace ${OUTPUT}
|
|||
|
|
|
|||
|
|
echo "Trace文件已保存为: ${OUTPUT}"
|
|||
|
|
echo "可以在 https://ui.perfetto.dev/ 打开分析"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使用方法:**
|
|||
|
|
```bash
|
|||
|
|
chmod +x quick_trace.sh
|
|||
|
|
|
|||
|
|
# 录制10秒
|
|||
|
|
./quick_trace.sh 10 trace.pb
|
|||
|
|
|
|||
|
|
# 录制指定应用
|
|||
|
|
./quick_trace.sh 10 startup.pb com.example.app
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 启动分析脚本
|
|||
|
|
|
|||
|
|
**startup_trace.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 应用启动分析脚本
|
|||
|
|
# 用法: ./startup_trace.sh [应用包名] [输出文件]
|
|||
|
|
|
|||
|
|
PACKAGE=${1:-com.example.app}
|
|||
|
|
OUTPUT=${2:-startup_trace.pb}
|
|||
|
|
|
|||
|
|
echo "准备录制 ${PACKAGE} 的启动Trace..."
|
|||
|
|
echo "请在3秒内启动应用..."
|
|||
|
|
|
|||
|
|
sleep 3
|
|||
|
|
|
|||
|
|
echo "开始录制..."
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
|||
|
|
-a ${PACKAGE} -t 10s
|
|||
|
|
|
|||
|
|
echo "拉取Trace文件..."
|
|||
|
|
adb pull /data/misc/perfetto-traces/trace ${OUTPUT}
|
|||
|
|
|
|||
|
|
echo "启动Trace已保存为: ${OUTPUT}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.3 卡顿分析脚本
|
|||
|
|
|
|||
|
|
**jank_trace.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 卡顿分析脚本
|
|||
|
|
# 用法: ./jank_trace.sh [时长] [输出文件] [应用包名]
|
|||
|
|
|
|||
|
|
DURATION=${1:-30}
|
|||
|
|
OUTPUT=${2:-jank_trace.pb}
|
|||
|
|
PACKAGE=${3:-""}
|
|||
|
|
|
|||
|
|
echo "准备录制卡顿Trace..."
|
|||
|
|
echo "请在3秒内复现卡顿问题..."
|
|||
|
|
|
|||
|
|
sleep 3
|
|||
|
|
|
|||
|
|
echo "开始录制 ${DURATION} 秒..."
|
|||
|
|
|
|||
|
|
if [ -z "$PACKAGE" ]; then
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t ${DURATION}s
|
|||
|
|
else
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
|||
|
|
-a ${PACKAGE} -t ${DURATION}s
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
echo "拉取Trace文件..."
|
|||
|
|
adb pull /data/misc/perfetto-traces/trace ${OUTPUT}
|
|||
|
|
|
|||
|
|
echo "卡顿Trace已保存为: ${OUTPUT}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 批量分析脚本
|
|||
|
|
|
|||
|
|
#### 2.1 批量录制脚本
|
|||
|
|
|
|||
|
|
**batch_record.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 批量录制Trace脚本
|
|||
|
|
# 用法: ./batch_record.sh [次数] [时长] [应用包名]
|
|||
|
|
|
|||
|
|
COUNT=${1:-5}
|
|||
|
|
DURATION=${2:-10}
|
|||
|
|
PACKAGE=${3:-""}
|
|||
|
|
|
|||
|
|
echo "批量录制 ${COUNT} 次Trace,每次 ${DURATION} 秒..."
|
|||
|
|
|
|||
|
|
for i in $(seq 1 $COUNT); do
|
|||
|
|
echo "录制第 ${i} 次Trace..."
|
|||
|
|
|
|||
|
|
if [ -z "$PACKAGE" ]; then
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace_${i} -t ${DURATION}s
|
|||
|
|
else
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace_${i} \
|
|||
|
|
-a ${PACKAGE} -t ${DURATION}s
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
adb pull /data/misc/perfetto-traces/trace_${i} trace_${i}.pb
|
|||
|
|
echo "第 ${i} 次Trace已保存为 trace_${i}.pb"
|
|||
|
|
|
|||
|
|
sleep 2
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
echo "所有Trace录制完成"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2 批量拉取脚本
|
|||
|
|
|
|||
|
|
**batch_pull.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 批量拉取Trace文件
|
|||
|
|
# 用法: ./batch_pull.sh [文件数量]
|
|||
|
|
|
|||
|
|
COUNT=${1:-5}
|
|||
|
|
OUTPUT_DIR=${2:-traces}
|
|||
|
|
|
|||
|
|
mkdir -p ${OUTPUT_DIR}
|
|||
|
|
|
|||
|
|
echo "批量拉取 ${COUNT} 个Trace文件..."
|
|||
|
|
|
|||
|
|
for i in $(seq 1 $COUNT); do
|
|||
|
|
echo "拉取 trace_${i}..."
|
|||
|
|
adb pull /data/misc/perfetto-traces/trace_${i} ${OUTPUT_DIR}/trace_${i}.pb 2>/dev/null
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
echo "所有Trace文件已保存到 ${OUTPUT_DIR}/"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 自动化分析脚本
|
|||
|
|
|
|||
|
|
#### 3.1 SQL查询脚本
|
|||
|
|
|
|||
|
|
**analyze_trace.py:**
|
|||
|
|
```python
|
|||
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
Perfetto Trace自动化分析脚本
|
|||
|
|
用法: python analyze_trace.py trace.pb
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import sys
|
|||
|
|
import subprocess
|
|||
|
|
import json
|
|||
|
|
|
|||
|
|
def run_sql_query(trace_file, sql_query):
|
|||
|
|
"""执行SQL查询"""
|
|||
|
|
# 使用perfetto命令行工具执行SQL查询
|
|||
|
|
# 注意:这需要perfetto命令行工具支持SQL查询
|
|||
|
|
cmd = ['perfetto', '--query', sql_query, trace_file]
|
|||
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|||
|
|
return result.stdout
|
|||
|
|
|
|||
|
|
def analyze_startup(trace_file):
|
|||
|
|
"""分析启动性能"""
|
|||
|
|
queries = {
|
|||
|
|
'process_creation': """
|
|||
|
|
SELECT ts, dur / 1000000.0 AS duration_ms
|
|||
|
|
FROM slice
|
|||
|
|
WHERE name LIKE '%startProcess%'
|
|||
|
|
LIMIT 1
|
|||
|
|
""",
|
|||
|
|
'application_init': """
|
|||
|
|
SELECT ts, dur / 1000000.0 AS duration_ms
|
|||
|
|
FROM slice
|
|||
|
|
WHERE name LIKE '%handleBindApplication%'
|
|||
|
|
LIMIT 1
|
|||
|
|
""",
|
|||
|
|
'activity_creation': """
|
|||
|
|
SELECT ts, dur / 1000000.0 AS duration_ms
|
|||
|
|
FROM slice
|
|||
|
|
WHERE name LIKE '%performLaunchActivity%'
|
|||
|
|
LIMIT 1
|
|||
|
|
""",
|
|||
|
|
'first_frame': """
|
|||
|
|
SELECT ts, dur / 1000000.0 AS duration_ms
|
|||
|
|
FROM slice
|
|||
|
|
WHERE name = 'Choreographer#doFrame'
|
|||
|
|
ORDER BY ts
|
|||
|
|
LIMIT 1
|
|||
|
|
"""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
results = {}
|
|||
|
|
for key, query in queries.items():
|
|||
|
|
output = run_sql_query(trace_file, query)
|
|||
|
|
results[key] = output
|
|||
|
|
|
|||
|
|
return results
|
|||
|
|
|
|||
|
|
def analyze_jank(trace_file):
|
|||
|
|
"""分析卡顿"""
|
|||
|
|
query = """
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) AS jank_count,
|
|||
|
|
AVG(dur) / 1000000.0 AS avg_duration_ms,
|
|||
|
|
MAX(dur) / 1000000.0 AS max_duration_ms
|
|||
|
|
FROM slice
|
|||
|
|
WHERE name = 'Choreographer#doFrame'
|
|||
|
|
AND dur > 16666667
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
return run_sql_query(trace_file, query)
|
|||
|
|
|
|||
|
|
def analyze_fps(trace_file):
|
|||
|
|
"""分析帧率"""
|
|||
|
|
query = """
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps
|
|||
|
|
FROM slice
|
|||
|
|
WHERE name = 'Choreographer#doFrame'
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
return run_sql_query(trace_file, query)
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
if len(sys.argv) < 2:
|
|||
|
|
print("用法: python analyze_trace.py trace.pb")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
trace_file = sys.argv[1]
|
|||
|
|
|
|||
|
|
print("分析启动性能...")
|
|||
|
|
startup_results = analyze_startup(trace_file)
|
|||
|
|
print(json.dumps(startup_results, indent=2))
|
|||
|
|
|
|||
|
|
print("\n分析卡顿...")
|
|||
|
|
jank_results = analyze_jank(trace_file)
|
|||
|
|
print(jank_results)
|
|||
|
|
|
|||
|
|
print("\n分析帧率...")
|
|||
|
|
fps_results = analyze_fps(trace_file)
|
|||
|
|
print(fps_results)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2 报告生成脚本
|
|||
|
|
|
|||
|
|
**generate_report.py:**
|
|||
|
|
```python
|
|||
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
生成性能分析报告
|
|||
|
|
用法: python generate_report.py trace.pb report.html
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import sys
|
|||
|
|
import json
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
def generate_html_report(trace_file, output_file):
|
|||
|
|
"""生成HTML报告"""
|
|||
|
|
html_template = """
|
|||
|
|
<!DOCTYPE html>
|
|||
|
|
<html>
|
|||
|
|
<head>
|
|||
|
|
<title>Perfetto Trace分析报告</title>
|
|||
|
|
<style>
|
|||
|
|
body { font-family: Arial, sans-serif; margin: 20px; }
|
|||
|
|
h1 { color: #333; }
|
|||
|
|
h2 { color: #666; margin-top: 30px; }
|
|||
|
|
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
|||
|
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|||
|
|
th { background-color: #f2f2f2; }
|
|||
|
|
.metric { margin: 10px 0; padding: 10px; background-color: #f9f9f9; }
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<h1>Perfetto Trace分析报告</h1>
|
|||
|
|
<p>生成时间: {timestamp}</p>
|
|||
|
|
<p>Trace文件: {trace_file}</p>
|
|||
|
|
|
|||
|
|
<h2>性能指标</h2>
|
|||
|
|
<div class="metric">
|
|||
|
|
<h3>启动性能</h3>
|
|||
|
|
<p>进程创建: {process_creation} ms</p>
|
|||
|
|
<p>Application初始化: {app_init} ms</p>
|
|||
|
|
<p>Activity创建: {activity_creation} ms</p>
|
|||
|
|
<p>首帧渲染: {first_frame} ms</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="metric">
|
|||
|
|
<h3>帧率</h3>
|
|||
|
|
<p>平均帧率: {fps} FPS</p>
|
|||
|
|
<p>掉帧次数: {jank_count}</p>
|
|||
|
|
<p>平均掉帧时长: {avg_jank_duration} ms</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h2>分析建议</h2>
|
|||
|
|
<ul>
|
|||
|
|
<li>检查主线程耗时操作</li>
|
|||
|
|
<li>优化渲染性能</li>
|
|||
|
|
<li>减少内存分配</li>
|
|||
|
|
</ul>
|
|||
|
|
</body>
|
|||
|
|
</html>
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
# 这里应该调用实际的SQL查询获取数据
|
|||
|
|
# 为了示例,使用占位符
|
|||
|
|
report = html_template.format(
|
|||
|
|
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|||
|
|
trace_file=trace_file,
|
|||
|
|
process_creation="N/A",
|
|||
|
|
app_init="N/A",
|
|||
|
|
activity_creation="N/A",
|
|||
|
|
first_frame="N/A",
|
|||
|
|
fps="N/A",
|
|||
|
|
jank_count="N/A",
|
|||
|
|
avg_jank_duration="N/A"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|||
|
|
f.write(report)
|
|||
|
|
|
|||
|
|
print(f"报告已生成: {output_file}")
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
if len(sys.argv) < 3:
|
|||
|
|
print("用法: python generate_report.py trace.pb report.html")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
trace_file = sys.argv[1]
|
|||
|
|
output_file = sys.argv[2]
|
|||
|
|
|
|||
|
|
generate_html_report(trace_file, output_file)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 三、配置模板复用
|
|||
|
|
|
|||
|
|
### 1. 配置文件模板库
|
|||
|
|
|
|||
|
|
#### 1.1 启动分析配置模板
|
|||
|
|
|
|||
|
|
**templates/startup_config.txt:**
|
|||
|
|
```protobuf
|
|||
|
|
buffers: {
|
|||
|
|
size_kb: 63488
|
|||
|
|
fill_policy: DISCARD
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data_sources: {
|
|||
|
|
config {
|
|||
|
|
name: "linux.ftrace"
|
|||
|
|
ftrace_config {
|
|||
|
|
ftrace_events: "sched/sched_switch"
|
|||
|
|
ftrace_events: "sched/sched_waking"
|
|||
|
|
ftrace_events: "sched/sched_process_exit"
|
|||
|
|
ftrace_events: "sched/sched_process_free"
|
|||
|
|
ftrace_events: "task/task_newtask"
|
|||
|
|
ftrace_events: "task/task_rename"
|
|||
|
|
ftrace_events: "power/cpu_frequency"
|
|||
|
|
ftrace_events: "power/cpu_idle"
|
|||
|
|
ftrace_events: "power/suspend_resume"
|
|||
|
|
atrace_categories: "am"
|
|||
|
|
atrace_categories: "wm"
|
|||
|
|
atrace_categories: "gfx"
|
|||
|
|
atrace_categories: "view"
|
|||
|
|
atrace_categories: "binder_driver"
|
|||
|
|
atrace_categories: "binder_lock"
|
|||
|
|
atrace_categories: "input"
|
|||
|
|
atrace_categories: "res"
|
|||
|
|
atrace_categories: "dalvik"
|
|||
|
|
buffer_size_kb: 4096
|
|||
|
|
drain_period_ms: 250
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data_sources: {
|
|||
|
|
config {
|
|||
|
|
name: "android.surfaceflinger.frame"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
duration_ms: 10000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 卡顿分析配置模板
|
|||
|
|
|
|||
|
|
**templates/jank_config.txt:**
|
|||
|
|
```protobuf
|
|||
|
|
buffers: {
|
|||
|
|
size_kb: 63488
|
|||
|
|
fill_policy: DISCARD
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data_sources: {
|
|||
|
|
config {
|
|||
|
|
name: "linux.ftrace"
|
|||
|
|
ftrace_config {
|
|||
|
|
ftrace_events: "sched/sched_switch"
|
|||
|
|
ftrace_events: "sched/sched_waking"
|
|||
|
|
ftrace_events: "power/cpu_frequency"
|
|||
|
|
ftrace_events: "power/cpu_idle"
|
|||
|
|
ftrace_events: "gfx/mali_gpu_total"
|
|||
|
|
ftrace_events: "gpu_mem_total/gpu_mem_total"
|
|||
|
|
atrace_categories: "gfx"
|
|||
|
|
atrace_categories: "view"
|
|||
|
|
atrace_categories: "sched"
|
|||
|
|
atrace_categories: "freq"
|
|||
|
|
atrace_categories: "idle"
|
|||
|
|
buffer_size_kb: 8192
|
|||
|
|
drain_period_ms: 250
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data_sources: {
|
|||
|
|
config {
|
|||
|
|
name: "android.surfaceflinger.frame"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
duration_ms: 30000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 模板管理脚本
|
|||
|
|
|
|||
|
|
**use_template.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 使用配置模板
|
|||
|
|
# 用法: ./use_template.sh [模板名] [输出文件] [时长]
|
|||
|
|
|
|||
|
|
TEMPLATE=${1:-startup}
|
|||
|
|
OUTPUT=${2:-config.txt}
|
|||
|
|
DURATION=${3:-10000}
|
|||
|
|
|
|||
|
|
TEMPLATE_DIR="templates"
|
|||
|
|
TEMPLATE_FILE="${TEMPLATE_DIR}/${TEMPLATE}_config.txt"
|
|||
|
|
|
|||
|
|
if [ ! -f "$TEMPLATE_FILE" ]; then
|
|||
|
|
echo "模板文件不存在: $TEMPLATE_FILE"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 复制模板并替换时长
|
|||
|
|
sed "s/duration_ms: [0-9]*/duration_ms: ${DURATION}/" \
|
|||
|
|
"$TEMPLATE_FILE" > "$OUTPUT"
|
|||
|
|
|
|||
|
|
echo "配置已生成: $OUTPUT"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 四、工作流程优化
|
|||
|
|
|
|||
|
|
### 1. 标准化分析流程
|
|||
|
|
|
|||
|
|
#### 1.1 启动分析流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 准备阶段
|
|||
|
|
- 明确分析目标
|
|||
|
|
- 准备测试环境
|
|||
|
|
- 选择配置模板
|
|||
|
|
|
|||
|
|
2. 录制阶段
|
|||
|
|
- 使用启动分析脚本
|
|||
|
|
- 启动应用
|
|||
|
|
- 等待录制完成
|
|||
|
|
|
|||
|
|
3. 分析阶段
|
|||
|
|
- 打开Trace文件
|
|||
|
|
- 定位关键节点
|
|||
|
|
- 分析各阶段耗时
|
|||
|
|
- 识别性能瓶颈
|
|||
|
|
|
|||
|
|
4. 报告阶段
|
|||
|
|
- 记录分析结果
|
|||
|
|
- 生成分析报告
|
|||
|
|
- 提供优化建议
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 卡顿分析流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 准备阶段
|
|||
|
|
- 明确卡顿场景
|
|||
|
|
- 准备复现步骤
|
|||
|
|
- 选择配置模板
|
|||
|
|
|
|||
|
|
2. 录制阶段
|
|||
|
|
- 使用卡顿分析脚本
|
|||
|
|
- 复现卡顿问题
|
|||
|
|
- 等待录制完成
|
|||
|
|
|
|||
|
|
3. 分析阶段
|
|||
|
|
- 打开Trace文件
|
|||
|
|
- 定位卡顿时间点
|
|||
|
|
- 分析主线程状态
|
|||
|
|
- 分析渲染线程状态
|
|||
|
|
- 分析系统资源
|
|||
|
|
|
|||
|
|
4. 报告阶段
|
|||
|
|
- 记录卡顿原因
|
|||
|
|
- 生成分析报告
|
|||
|
|
- 提供优化建议
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 分析检查清单
|
|||
|
|
|
|||
|
|
#### 2.1 启动分析检查清单
|
|||
|
|
|
|||
|
|
- [ ] 进程创建时间
|
|||
|
|
- [ ] Application初始化时间
|
|||
|
|
- [ ] Activity创建时间
|
|||
|
|
- [ ] 首帧渲染时间
|
|||
|
|
- [ ] 主线程阻塞情况
|
|||
|
|
- [ ] 系统服务调用耗时
|
|||
|
|
- [ ] CPU使用情况
|
|||
|
|
- [ ] 内存使用情况
|
|||
|
|
|
|||
|
|
#### 2.2 卡顿分析检查清单
|
|||
|
|
|
|||
|
|
- [ ] 卡顿时间点定位
|
|||
|
|
- [ ] 帧率统计
|
|||
|
|
- [ ] 主线程状态分析
|
|||
|
|
- [ ] RenderThread状态分析
|
|||
|
|
- [ ] CPU性能分析
|
|||
|
|
- [ ] 内存性能分析
|
|||
|
|
- [ ] GPU性能分析
|
|||
|
|
- [ ] 根本原因定位
|
|||
|
|
|
|||
|
|
## 五、快捷键和技巧
|
|||
|
|
|
|||
|
|
### 1. Perfetto Web UI快捷键
|
|||
|
|
|
|||
|
|
| 快捷键 | 功能 |
|
|||
|
|
|--------|------|
|
|||
|
|
| `W` | 放大时间轴 |
|
|||
|
|
| `S` | 缩小时间轴 |
|
|||
|
|
| `A` | 向左移动 |
|
|||
|
|
| `D` | 向右移动 |
|
|||
|
|
| `M` | 添加书签 |
|
|||
|
|
| `G` | 跳转到书签 |
|
|||
|
|
| `F` | 查找 |
|
|||
|
|
| `?` | 显示所有快捷键 |
|
|||
|
|
| `Shift + 拖拽` | 选择时间范围 |
|
|||
|
|
| `Ctrl + F` | 搜索框 |
|
|||
|
|
|
|||
|
|
### 2. 快速定位技巧
|
|||
|
|
|
|||
|
|
#### 2.1 使用书签
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 找到关键时间点
|
|||
|
|
2. 按M键添加书签
|
|||
|
|
3. 给书签命名(如"启动开始"、"首帧")
|
|||
|
|
4. 使用G键快速跳转
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2 使用时间选择
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. Shift + 鼠标拖拽选择时间范围
|
|||
|
|
2. 只显示选择范围内的事件
|
|||
|
|
3. 缩小分析范围
|
|||
|
|
4. 提高分析效率
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.3 使用筛选器
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 在进程列表中筛选
|
|||
|
|
2. 在线程列表中筛选
|
|||
|
|
3. 使用搜索框筛选事件
|
|||
|
|
4. 只关注相关进程和线程
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. SQL查询模板
|
|||
|
|
|
|||
|
|
#### 3.1 启动时间查询模板
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 查询启动各阶段耗时
|
|||
|
|
SELECT
|
|||
|
|
'Process Creation' AS stage,
|
|||
|
|
(SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1) AS start_ts,
|
|||
|
|
(SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) AS end_ts,
|
|||
|
|
((SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) -
|
|||
|
|
(SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1)) / 1000000.0 AS duration_ms
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT
|
|||
|
|
'Application Init' AS stage,
|
|||
|
|
(SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) AS start_ts,
|
|||
|
|
(SELECT ts FROM slice WHERE name LIKE '%performLaunchActivity%' LIMIT 1) AS end_ts,
|
|||
|
|
((SELECT ts FROM slice WHERE name LIKE '%performLaunchActivity%' LIMIT 1) -
|
|||
|
|
(SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1)) / 1000000.0 AS duration_ms;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2 卡顿分析查询模板
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 查询掉帧统计
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) AS jank_count,
|
|||
|
|
AVG(dur) / 1000000.0 AS avg_duration_ms,
|
|||
|
|
MAX(dur) / 1000000.0 AS max_duration_ms,
|
|||
|
|
MIN(dur) / 1000000.0 AS min_duration_ms
|
|||
|
|
FROM slice
|
|||
|
|
WHERE name = 'Choreographer#doFrame'
|
|||
|
|
AND dur > 16666667;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.3 主线程分析查询模板
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 查询主线程耗时操作
|
|||
|
|
SELECT
|
|||
|
|
name,
|
|||
|
|
dur / 1000000.0 AS duration_ms,
|
|||
|
|
ts
|
|||
|
|
FROM slice
|
|||
|
|
WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1)
|
|||
|
|
ORDER BY dur DESC
|
|||
|
|
LIMIT 20;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 六、批量处理方案
|
|||
|
|
|
|||
|
|
### 1. 批量录制和分析
|
|||
|
|
|
|||
|
|
#### 1.1 批量录制脚本
|
|||
|
|
|
|||
|
|
**batch_analysis.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 批量录制和分析脚本
|
|||
|
|
# 用法: ./batch_analysis.sh [次数] [时长] [应用包名]
|
|||
|
|
|
|||
|
|
COUNT=${1:-5}
|
|||
|
|
DURATION=${2:-10}
|
|||
|
|
PACKAGE=${3:-com.example.app}
|
|||
|
|
OUTPUT_DIR="batch_traces_$(date +%Y%m%d_%H%M%S)"
|
|||
|
|
|
|||
|
|
mkdir -p ${OUTPUT_DIR}
|
|||
|
|
|
|||
|
|
echo "批量录制和分析 ${COUNT} 次Trace..."
|
|||
|
|
|
|||
|
|
for i in $(seq 1 $COUNT); do
|
|||
|
|
echo "========== 第 ${i} 次 =========="
|
|||
|
|
|
|||
|
|
# 录制
|
|||
|
|
echo "录制Trace..."
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace_${i} \
|
|||
|
|
-a ${PACKAGE} -t ${DURATION}s
|
|||
|
|
|
|||
|
|
# 拉取
|
|||
|
|
echo "拉取Trace文件..."
|
|||
|
|
adb pull /data/misc/perfetto-traces/trace_${i} \
|
|||
|
|
${OUTPUT_DIR}/trace_${i}.pb
|
|||
|
|
|
|||
|
|
# 分析(使用Python脚本)
|
|||
|
|
echo "分析Trace..."
|
|||
|
|
python analyze_trace.py ${OUTPUT_DIR}/trace_${i}.pb > \
|
|||
|
|
${OUTPUT_DIR}/analysis_${i}.txt
|
|||
|
|
|
|||
|
|
echo "第 ${i} 次完成"
|
|||
|
|
echo ""
|
|||
|
|
|
|||
|
|
sleep 2
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
echo "所有Trace已保存到 ${OUTPUT_DIR}/"
|
|||
|
|
echo "分析结果已保存到 ${OUTPUT_DIR}/analysis_*.txt"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 批量对比脚本
|
|||
|
|
|
|||
|
|
**batch_compare.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# 批量对比脚本
|
|||
|
|
# 用法: ./batch_compare.sh [基准目录] [对比目录] [输出文件]
|
|||
|
|
|
|||
|
|
BASELINE_DIR=${1:-baseline_traces}
|
|||
|
|
COMPARE_DIR=${2:-compare_traces}
|
|||
|
|
OUTPUT=${3:-compare_report.txt}
|
|||
|
|
|
|||
|
|
echo "批量对比分析..."
|
|||
|
|
echo "基准目录: ${BASELINE_DIR}"
|
|||
|
|
echo "对比目录: ${COMPARE_DIR}"
|
|||
|
|
echo "输出文件: ${OUTPUT}"
|
|||
|
|
|
|||
|
|
# 这里应该实现对比逻辑
|
|||
|
|
# 对比启动时间、帧率、卡顿等指标
|
|||
|
|
|
|||
|
|
echo "对比报告已生成: ${OUTPUT}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 自动化报告生成
|
|||
|
|
|
|||
|
|
#### 2.1 报告模板
|
|||
|
|
|
|||
|
|
**report_template.html:**
|
|||
|
|
```html
|
|||
|
|
<!DOCTYPE html>
|
|||
|
|
<html>
|
|||
|
|
<head>
|
|||
|
|
<title>性能分析报告</title>
|
|||
|
|
<style>
|
|||
|
|
/* 样式定义 */
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<h1>性能分析报告</h1>
|
|||
|
|
<!-- 报告内容 -->
|
|||
|
|
</body>
|
|||
|
|
</html>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2 报告生成脚本
|
|||
|
|
|
|||
|
|
**generate_batch_report.py:**
|
|||
|
|
```python
|
|||
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
批量生成分析报告
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import json
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
def generate_batch_report(trace_dir, output_file):
|
|||
|
|
"""批量生成报告"""
|
|||
|
|
reports = []
|
|||
|
|
|
|||
|
|
for trace_file in os.listdir(trace_dir):
|
|||
|
|
if trace_file.endswith('.pb'):
|
|||
|
|
# 分析每个Trace文件
|
|||
|
|
# 这里应该调用实际的分析函数
|
|||
|
|
report = {
|
|||
|
|
'file': trace_file,
|
|||
|
|
'timestamp': datetime.now().isoformat(),
|
|||
|
|
'metrics': {}
|
|||
|
|
}
|
|||
|
|
reports.append(report)
|
|||
|
|
|
|||
|
|
# 生成汇总报告
|
|||
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|||
|
|
json.dump(reports, f, indent=2)
|
|||
|
|
|
|||
|
|
print(f"批量报告已生成: {output_file}")
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
import sys
|
|||
|
|
trace_dir = sys.argv[1] if len(sys.argv) > 1 else 'traces'
|
|||
|
|
output_file = sys.argv[2] if len(sys.argv) > 2 else 'batch_report.json'
|
|||
|
|
generate_batch_report(trace_dir, output_file)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 七、团队协作方案
|
|||
|
|
|
|||
|
|
### 1. 共享配置和模板
|
|||
|
|
|
|||
|
|
#### 1.1 Git仓库管理
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
perfetto-tools/
|
|||
|
|
├── templates/ # 配置模板
|
|||
|
|
│ ├── startup_config.txt
|
|||
|
|
│ ├── jank_config.txt
|
|||
|
|
│ └── memory_config.txt
|
|||
|
|
├── scripts/ # 脚本
|
|||
|
|
│ ├── quick_trace.sh
|
|||
|
|
│ ├── analyze_trace.py
|
|||
|
|
│ └── generate_report.py
|
|||
|
|
├── sql/ # SQL查询模板
|
|||
|
|
│ ├── startup_queries.sql
|
|||
|
|
│ ├── jank_queries.sql
|
|||
|
|
│ └── memory_queries.sql
|
|||
|
|
└── docs/ # 文档
|
|||
|
|
├── README.md
|
|||
|
|
└── best_practices.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 配置版本管理
|
|||
|
|
|
|||
|
|
- 使用Git管理配置模板
|
|||
|
|
- 记录配置变更历史
|
|||
|
|
- 共享最佳实践配置
|
|||
|
|
- 建立配置评审机制
|
|||
|
|
|
|||
|
|
### 2. 知识库建设
|
|||
|
|
|
|||
|
|
#### 2.1 案例库
|
|||
|
|
|
|||
|
|
- **成功案例**:记录优化成功的案例
|
|||
|
|
- **问题案例**:记录常见问题和解决方案
|
|||
|
|
- **分析模板**:建立标准分析模板
|
|||
|
|
- **优化建议**:积累优化建议库
|
|||
|
|
|
|||
|
|
#### 2.2 文档管理
|
|||
|
|
|
|||
|
|
- **分析流程文档**:标准化的分析流程
|
|||
|
|
- **工具使用文档**:工具和脚本使用说明
|
|||
|
|
- **最佳实践文档**:总结最佳实践
|
|||
|
|
- **FAQ文档**:常见问题解答
|
|||
|
|
|
|||
|
|
### 3. 协作工具
|
|||
|
|
|
|||
|
|
#### 3.1 共享Trace文件
|
|||
|
|
|
|||
|
|
- 使用云存储共享Trace文件
|
|||
|
|
- 建立Trace文件命名规范
|
|||
|
|
- 记录Trace文件元数据
|
|||
|
|
- 建立Trace文件索引
|
|||
|
|
|
|||
|
|
#### 3.2 协作分析
|
|||
|
|
|
|||
|
|
- 使用在线协作工具
|
|||
|
|
- 共享分析结果
|
|||
|
|
- 建立分析讨论机制
|
|||
|
|
- 记录分析决策
|
|||
|
|
|
|||
|
|
## 八、智能分析方案
|
|||
|
|
|
|||
|
|
### 1. 自动化问题检测
|
|||
|
|
|
|||
|
|
#### 1.1 问题检测规则
|
|||
|
|
|
|||
|
|
**detect_issues.py:**
|
|||
|
|
```python
|
|||
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
自动检测性能问题
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def detect_startup_issues(trace_file):
|
|||
|
|
"""检测启动问题"""
|
|||
|
|
issues = []
|
|||
|
|
|
|||
|
|
# 检测启动时间过长
|
|||
|
|
# 检测主线程阻塞
|
|||
|
|
# 检测系统服务调用耗时
|
|||
|
|
# ...
|
|||
|
|
|
|||
|
|
return issues
|
|||
|
|
|
|||
|
|
def detect_jank_issues(trace_file):
|
|||
|
|
"""检测卡顿问题"""
|
|||
|
|
issues = []
|
|||
|
|
|
|||
|
|
# 检测掉帧
|
|||
|
|
# 检测主线程阻塞
|
|||
|
|
# 检测渲染瓶颈
|
|||
|
|
# ...
|
|||
|
|
|
|||
|
|
return issues
|
|||
|
|
|
|||
|
|
def generate_issue_report(issues, output_file):
|
|||
|
|
"""生成问题报告"""
|
|||
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|||
|
|
for issue in issues:
|
|||
|
|
f.write(f"{issue['type']}: {issue['description']}\n")
|
|||
|
|
f.write(f" 位置: {issue['location']}\n")
|
|||
|
|
f.write(f" 建议: {issue['suggestion']}\n\n")
|
|||
|
|
|
|||
|
|
print(f"问题报告已生成: {output_file}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 性能基准对比
|
|||
|
|
|
|||
|
|
**compare_baseline.py:**
|
|||
|
|
```python
|
|||
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
与性能基准对比
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def load_baseline(baseline_file):
|
|||
|
|
"""加载性能基准"""
|
|||
|
|
with open(baseline_file, 'r') as f:
|
|||
|
|
return json.load(f)
|
|||
|
|
|
|||
|
|
def compare_with_baseline(trace_file, baseline):
|
|||
|
|
"""与基准对比"""
|
|||
|
|
# 分析Trace文件
|
|||
|
|
# 与基准对比
|
|||
|
|
# 生成对比报告
|
|||
|
|
pass
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 机器学习辅助分析
|
|||
|
|
|
|||
|
|
#### 2.1 异常检测
|
|||
|
|
|
|||
|
|
- 使用机器学习检测异常模式
|
|||
|
|
- 自动识别性能问题
|
|||
|
|
- 提供问题分类和优先级
|
|||
|
|
|
|||
|
|
#### 2.2 预测分析
|
|||
|
|
|
|||
|
|
- 预测性能趋势
|
|||
|
|
- 识别潜在问题
|
|||
|
|
- 提供预防建议
|
|||
|
|
|
|||
|
|
## 九、性能监控集成
|
|||
|
|
|
|||
|
|
### 1. CI/CD集成
|
|||
|
|
|
|||
|
|
#### 1.1 自动化测试
|
|||
|
|
|
|||
|
|
**ci_performance_test.sh:**
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# CI/CD性能测试脚本
|
|||
|
|
|
|||
|
|
echo "开始性能测试..."
|
|||
|
|
|
|||
|
|
# 录制Trace
|
|||
|
|
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 10s
|
|||
|
|
adb pull /data/misc/perfetto-traces/trace ci_trace.pb
|
|||
|
|
|
|||
|
|
# 分析Trace
|
|||
|
|
python analyze_trace.py ci_trace.pb > ci_analysis.txt
|
|||
|
|
|
|||
|
|
# 检查性能指标
|
|||
|
|
python check_performance.py ci_analysis.txt
|
|||
|
|
|
|||
|
|
# 如果性能不达标,退出
|
|||
|
|
if [ $? -ne 0 ]; then
|
|||
|
|
echo "性能测试失败"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
echo "性能测试通过"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 性能回归检测
|
|||
|
|
|
|||
|
|
- 在CI/CD中集成性能测试
|
|||
|
|
- 自动检测性能回归
|
|||
|
|
- 生成性能报告
|
|||
|
|
- 通知相关人员
|
|||
|
|
|
|||
|
|
### 2. 持续监控
|
|||
|
|
|
|||
|
|
#### 2.1 定期分析
|
|||
|
|
|
|||
|
|
- 定期录制和分析Trace
|
|||
|
|
- 建立性能趋势图
|
|||
|
|
- 识别性能退化
|
|||
|
|
- 及时发现问题
|
|||
|
|
|
|||
|
|
#### 2.2 告警机制
|
|||
|
|
|
|||
|
|
- 设置性能阈值
|
|||
|
|
- 自动触发告警
|
|||
|
|
- 通知相关人员
|
|||
|
|
- 记录告警历史
|
|||
|
|
|
|||
|
|
## 十、总结
|
|||
|
|
|
|||
|
|
### 1. 提效关键点
|
|||
|
|
|
|||
|
|
1. **工具自动化**:使用脚本自动化重复操作
|
|||
|
|
2. **流程标准化**:建立标准化的分析流程
|
|||
|
|
3. **模板复用**:使用配置和分析模板
|
|||
|
|
4. **批量处理**:批量录制和分析Trace
|
|||
|
|
5. **智能分析**:使用SQL和工具进行智能分析
|
|||
|
|
6. **团队协作**:建立共享配置和知识库
|
|||
|
|
7. **持续监控**:集成到CI/CD和监控系统
|
|||
|
|
|
|||
|
|
### 2. 实施建议
|
|||
|
|
|
|||
|
|
1. **逐步实施**:从简单的脚本开始,逐步完善
|
|||
|
|
2. **持续改进**:根据实际使用情况不断优化
|
|||
|
|
3. **知识积累**:建立案例库和最佳实践
|
|||
|
|
4. **团队培训**:培训团队成员使用工具和流程
|
|||
|
|
|
|||
|
|
### 3. 预期效果
|
|||
|
|
|
|||
|
|
- **分析时间**:从数小时缩短到数十分钟
|
|||
|
|
- **分析准确性**:提高分析准确性
|
|||
|
|
- **知识积累**:建立可复用的知识库
|
|||
|
|
- **团队效率**:提高团队整体效率
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*最后更新:2024年*
|