代码执行性能分析
对请求的每个阶段进行计时可以用证据代替直觉。一旦持续时间是结构化事件,您就可以比较运行、发布和输入大小类别的功能。
持续时间配置文件会告诉您哪个功能值得首先优化。
设置项目
npm init -y
npm install telemetry-sh
仪器每个已完成的步骤
每个步骤使用一个事件,以便相同的列跨工作流程工作。 run_id 连接属于一个请求或作业的步骤。
const telemetry = require("telemetry-sh");
const { randomUUID } = require("node:crypto");
telemetry.init(process.env.TELEMETRY_API_KEY);
async function measureStep({ runId, stepName }, operation) {
const startedAt = Date.now();
try {
const result = await operation();
await telemetry.log("profile_step_completed", {
run_id: runId,
step_name: stepName,
status: "success",
duration_ms: Date.now() - startedAt,
release: process.env.APP_RELEASE ?? "unknown",
});
return result;
} catch (error) {
await telemetry.log("profile_step_completed", {
run_id: runId,
step_name: stepName,
status: "error",
duration_ms: Date.now() - startedAt,
error_type: error.constructor?.name ?? "Error",
release: process.env.APP_RELEASE ?? "unknown",
});
throw error;
}
}
async function profileRequest() {
const runId = randomUUID();
const project = await measureStep(
{ runId, stepName: "load_project" },
() => loadProject()
);
return measureStep(
{ runId, stepName: "generate_report" },
() => generateReport(project)
);
}
默认情况下避免记录函数参数、数据库记录或异常消息。 input_size_bucket、cache_status 或 query_name 等安全类别可以在不存储原始内容的情况下解释性能。
查询平均和尾部持续时间
SELECT
step_name,
COUNT(*) AS runs,
ROUND(AVG(duration_ms), 0) AS avg_duration_ms,
ROUND(approx_percentile_cont(duration_ms, 0.95), 0) AS p95_duration_ms,
ROUND(
100.0 * SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0),
2
) AS error_rate_pct
FROM profile_step_completed
WHERE timestamp_utc >= now() - INTERVAL '7 days'
GROUP BY step_name
ORDER BY p95_duration_ms DESC;
使用 p95 而不是仅使用平均值,这样间歇性的缓慢步骤仍然可见。部署后按 release 进行拆分,并在多个慢速步骤属于同一请求时检查各个 run_id 值。
构建绩效视图
从以下开始:
- p95 持续时间的条形图(按步骤);
- p95 随着时间变化的折线图,显示最慢的步骤;
- 按版本划分的结果表;
- 过滤为错误或极端持续时间行的最近事件表。
在有用的边界处进行轮廓分析。不要为热循环中的每个小函数发出事件;这会增加开销并产生难以解释的数据集。
后续步骤
当测量的工作流程是 API 请求时,使用 API 延迟百分位数配方 进行完整的结果可视化、仪表板布局和告警设计。