qwen-code 的/stats生成时序指标(TTFT / Generation Time / TPS):从实现到验证
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
导读
本文聚焦 qwen-code(An open-source AI coding agent that lives in your terminal)终端 CLI 中/stats命令新增的Generation Metrics(生成时序指标)模块,讲解 TTFT(首 token 延迟)、Generation Time(生成耗时)、Output Tokens、TPS(每秒输出 token 数)以及 Session 级平均 TTFT 与 Session TPS 的数据模型、计算口径与采样过滤规则,并完整还原该功能的自动化验证、手动验证场景与回归检查清单。读者读完可以掌握该指标体系的设计意图、源码位置与可复现的验证方法,为在自己环境中核查或二次开发该功能提供依据。
一、功能背景:/stats里的生成时序指标
/stats是 qwen-code 的内置斜杠命令,提供使用统计仪表盘。它既可以在交互式 TUI 中弹出统计对话框,也可以在非交互式命令面上输出纯文本。在新增 Generation Metrics 之前,/stats的会话统计只有 token 用量、模型用量、工具调用、文件变更等维度,缺少对“生成过程本身有多快”的量化;而 TTFT 等时序数据此前仅存在于 tracing(链路追踪)中。
本文依据的验证文档(.qwen/e2e-tests/2026-07-24-generation-stats.md)正是为这一功能制定的自动化与手动验证基线,核心目标是回答:
/stats的 Session 标签页能否展示最新一次请求的生成时序(模型、TTFT、生成耗时、输出 token 数、TPS);- 多次请求后,会话级请求计数、平均 TTFT、Session TPS是否正确累计;
- 在非交互式命令面上,同样的指标能否以文本形式输出;
- 在各种边界情况下(无流式响应、非流式响应、零耗时、内部 prompt、无用户可见内容)指标不会出现空节、编造数值或 Infinity。
本文所引用的指标实现、测试与验证步骤,均可在当前仓库
.qwen/e2e-tests/2026-07-24-generation-stats.md、packages/core/src/telemetry/uiTelemetry.ts、packages/cli/src/ui/commands/statsCommand.ts、packages/cli/src/ui/components/StatsSessionTab.tsx及对应测试文件中找到原始依据。
二、数据模型:GenerationMetrics 与 GenerationTimingSample
生成时序指标定义在 packages/core/src/telemetry/uiTelemetry.ts 中,由两个核心接口承载:
export interface GenerationTimingSample { model: string; ttftMs: number; generationDurationMs: number; outputTokens: number; } export interface GenerationMetrics { timedRequests: number; totalTtftMs: number; totalGenerationDurationMs: number; totalThroughputOutputTokens: number; last?: GenerationTimingSample; }GenerationTimingSample(last字段):最新一次被采样的请求快照,包含模型名、TTFT、生成耗时与输出 token 数,用于“Latest Request”展示;GenerationMetrics的四个累计字段:timedRequests(被采样的请求数)、totalTtftMs(TTFT 累加和)、totalGenerationDurationMs(生成耗时累加和)、totalThroughputOutputTokens(用于计算吞吐的输出 token 累加和)。
它们挂在SessionMetrics的可选字段generation?: GenerationMetrics上,随会话指标一起被统计、查询和重置。默认空值由createInitialGenerationMetrics()生成(timedRequests: 0),确保任何会话初始时都能安全访问。
从源码结构看,
GenerationMetrics是一个增量累加模型:每次事件到来时更新累计值并覆盖last快照,因此“Session 级平均值”可由累加值在展示层计算,而无需保存每个请求的历史明细。
三、指标口径:TTFT、Generation Time、TPS 的计算方式
3.1 采样逻辑(core 侧)
在 packages/core/src/telemetry/uiTelemetry.ts 中,#accumulateApiMetrics对每次 API 事件做如下处理:
if ( event.ttft_ms === undefined || !Number.isFinite(event.ttft_ms) || event.ttft_ms < 0 || isInternalPromptId(event.prompt_id) ) { return; } const generation = metrics.generation ?? (metrics.generation = createInitialGenerationMetrics()); const generationDurationMs = Math.max(0, event.duration_ms - event.ttft_ms); generation.timedRequests++; generation.totalTtftMs += event.ttft_ms; if (generationDurationMs > 0) { generation.totalGenerationDurationMs += generationDurationMs; generation.totalThroughputOutputTokens += event.output_token_count; } generation.last = { model: event.model, ttftMs: event.ttft_ms, generationDurationMs, outputTokens: event.output_token_count, };关键规则可归纳为:
- TTFT 有效性:
ttft_ms必须存在(undefined不算)、是有限数值(Number.isFinite)、且不小于 0;duration_ms - ttft_ms可能为负(例如数据上报抖动),因此生成耗时用Math.max(0, ...)夹取到 0; - 内部 prompt 排除:
isInternalPromptId(event.prompt_id)为真时直接返回,不产生采样,保证内部 prompt 不会覆盖最新用户可见的生成样本; - 零生成耗时不计入吞吐:当
generationDurationMs === 0时,请求仍然计入timedRequests与totalTtftMs,但不累加totalGenerationDurationMs与totalThroughputOutputTokens,从根上避免除零导致的 Infinity。
TTFT 的原始测量点位于 packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts:当响应流出现首个用户可见内容时记录ttftMs = Date.now() - startTime;如果流直到结束都没有用户可见内容,则ttftMs保持undefined,进而不会被采样——这与回归检查“A stream with no user-visible content does not create a timing sample”完全对应。
3.2 展示与计算(CLI 侧)
在 packages/cli/src/ui/components/StatsSessionTab.tsx 中,Generation Metrics 区域按以下公式渲染:
| 指标 | 公式 | 边界处理 |
|---|---|---|
| 最新请求 TPS | last.outputTokens / (last.generationDurationMs / 1000) | 仅当generationDurationMs > 0时计算,否则不显示(避免 Infinity) |
| Average TTFT | generation.totalTtftMs / generation.timedRequests | 仅当timedRequests > 0时计算,否则不显示 |
| Session TPS | generation.totalThroughputOutputTokens / (generation.totalGenerationDurationMs / 1000) | 仅当totalGenerationDurationMs > 0时计算,否则不显示 |
同样的公式以纯文本形式存在于 packages/cli/src/ui/commands/statsCommand.ts 的formatGenerationMetrics()中(见下文非交互式输出)。三个平均值都遵循“分母为零则不显示”的口径,保证任何极端情况下不会输出 Infinity。
StatsSessionTab.tsx的展示结构为:
- Latest Request 区:Model(最新模型名)、TTFT、Generation Time、Output Tokens、TPS;
- Session 区:Requests(
timedRequests)、Average TTFT、Session TPS。
会话级指标由 packages/cli/src/ui/contexts/SessionContext.tsx 提供上下文数据,/stats打开时读取的是同一个SessionMetrics,因此 TUI 展示与文本输出口径天然一致。
四、自动化验证:聚焦测试与仓库级校验
验证文档给出了可复现的自动化验证路径,分为两个层次。
4.1 聚焦的 Core 测试
在仓库根目录或packages/core下执行:
cd packages/core npx vitest run src/telemetry/uiTelemetry.test.ts npx vitest run src/core/loggingContentGenerator/loggingContentGenerator.test.ts- packages/core/src/telemetry/uiTelemetry.test.ts 覆盖采样逻辑:构造带
ttft_ms的事件后断言generation数据正确累计;同时覆盖“缺少 TTFT 或内部 prompt 不产生采样”“duration_ms < ttft_ms时生成耗时夹取为 0”“按会话隔离”“service 重置时清空 generation 指标”等场景; - packages/core/src/core/loggingContentGenerator/loggingContentGenerator.test.ts 覆盖 TTFT 测量点,即“首个用户可见内容才记录 TTFT”。
4.2 聚焦的 CLI 测试
cd packages/cli npx vitest run src/ui/commands/statsCommand.test.ts npx vitest run src/ui/components/StatsSessionTab.test.tsx npx vitest run src/ui/contexts/SessionContext.test.tsx npx vitest run src/i18n/mustTranslateKeys.test.tsstatsCommand.test.ts:验证/stats命令及其子命令(model、tools、skills、daily、monthly、export)的文本输出与参数解析;StatsSessionTab.test.tsx:验证 Session 标签页对 Generation Metrics 的渲染,包括各指标的存在性与格式;SessionContext.test.tsx:验证会话统计上下文的组装与下发;mustTranslateKeys.test.ts:国际化的强制校验,确保Generation Metrics、Latest Request、TTFT、Generation Time、Output Tokens、TPS、Average TTFT、Session TPS等新增文案都有对应的翻译键,避免遗漏。
4.3 仓库级校验
聚焦测试通过后,再执行仓库级校验:
npm run format npm run lint npm run build npm run typecheck这四步分别对应格式化、静态检查、构建与类型检查,确保新指标代码不影响仓库整体的可维护性与可发布性。
五、手动验证场景:一步一步核对指标
验证文档同时给出了端到端手动验证步骤,可作为功能验收的标准流程:
- 启动开发版 CLI,使用流式模型(streaming model),保证响应可产生多 token 输出;
- 提交一个 prompt,让其产生多 token 的流式响应;
- 打开
/stats(交互式 TUI); - 在Session 标签页确认Generation Metrics显示:
- 最新模型(latest model);
- TTFT;
- Generation Time(生成耗时);
- Output Tokens(输出 token 数);
- TPS(每秒输出 token 数)。
- 提交第二个 prompt,再次打开
/stats; - 确认:
- Latest Request 字段已更新为第二个请求的数据;
- Session 请求计数(Requests)变为 2,Average TTFT与Session TPS同时包含两次被计时的请求;
- 在非交互式命令面运行
/stats,确认同样的指标以纯文本形式出现(见下一节)。
注意:
/stats daily、/stats monthly、/stats export属于 token 用量统计,不包含生成时序,验证时不应期待它们出现 TTFT/TPS。
六、非交互式输出:文本形式的 Generation Metrics
/stats命令在 packages/cli/src/ui/commands/statsCommand.ts 中根据context.executionMode区分行为:交互式模式下弹出stats对话框;非交互式(non_interactive与acp)模式下返回文本消息。formatGenerationMetrics()生成的文本格式为:
Generation Metrics (Latest Request) Model: <model> TTFT: <formatted duration> Generation Time: <formatted duration> Output Tokens: <count> TPS: <x.x> tok/s Requests: <count> Average TTFT: <formatted duration> Session TPS: <x.x> tok/s其中:
- 若尚无任何计时样本(
metrics或last为空),整段返回空,不会渲染空壳标题; - 当最新请求的
generationDurationMs为 0 时,TPS 显示为—(formatDuration之外的另一处防除零处理); - 当
timedRequests为 0 时,Average TTFT 显示为—; - 当累计生成耗时为 0 时,Session TPS 显示为
—; - 数字使用
Intl.NumberFormat按当前语言本地化格式化。
这与回归检查“Opening/statsbefore any streamed response does not show an empty Generation Metrics section”以及“A zero generation duration displays TPS as unavailable instead of Infinity”直接对应。此外,/stats export输出的文本末尾带有明确提示:Note: generation timing (TTFT/TPS) belongs to generation metrics.,用于避免与 token 用量统计混淆。
七、回归检查清单:六条不可回归的边界
验证文档将以下场景列为回归红线,每条都有对应的实现或测试佐证:
| # | 回归检查项 | 实现保证(源码/测试依据) |
|---|---|---|
| 1 | 打开/stats但尚无任何流式响应时,不显示空的 Generation Metrics 节 | formatGenerationMetrics对空 metrics 返回空数组;StatsSessionTab仅在有lastGeneration时渲染该区域 |
| 2 | 非流式响应不编造 TTFT 或 TPS | TTFT 仅在流式响应出现用户可见内容时记录(loggingContentGenerator);无ttft_ms的事件直接跳过采样 |
| 3 | 无用户可见内容的流不产生计时样本 | hasUserVisibleContent(response)为假时ttftMs保持undefined,事件被采样过滤 |
| 4 | 零生成耗时显示 TPS 为不可用而非 Infinity | generationDurationMs > 0才参与 TPS/吞吐累计;展示层分母为零时输出— |
| 5 | 内部 prompt 不覆盖最新用户可见生成样本 | isInternalPromptId(event.prompt_id)直接 return,不更新generation.last |
| 6 | /stats daily、/stats monthly、/stats export行为不变 | 子命令走独立的queryTokenUsage路径,Generation Metrics 不介入 token 用量聚合 |
对应测试集中在 packages/core/src/telemetry/uiTelemetry.test.ts,例如:
it('does not create samples for missing TTFT or internal prompts')同时覆盖检查 2、3、5;- 对
duration_ms: 100, ttft_ms: 150的事件断言generationDurationMs: 0,对应检查 4; - 按 session 隔离与 service reset 的用例保证会话切换不会串数据。
八、基线状态:变更前的可验证前提
验证文档记录了一个重要的环境约束:当前验证环境中没有发布版全局qwen可执行文件,因此无法运行“变更前”的手动场景对比。其基线结论为:
- TTFT 已通过 tracing(链路追踪)输出——即时序数据此前就存在于观测侧;
/stats在变更前没有生成时序区块——即该功能属于对/stats的增量扩展,而非重构。
这意味着本功能的行为验证完全依赖源码与新增测试来确立基线:uiTelemetry.test.ts与loggingContentGenerator.test.ts中的断言即代表了“变更后”的期望行为,手动场景仅作为 TUI 层面的最终验收补充。
九、相关文件速查
围绕本功能,仓库中的关键文件与角色如下:
| 文件 | 角色 |
|---|---|
| .qwen/e2e-tests/2026-07-24-generation-stats.md | 本功能的验证与回归检查规范(本文依据) |
| packages/core/src/telemetry/uiTelemetry.ts | GenerationMetrics/GenerationTimingSample定义与采样累加逻辑 |
| packages/core/src/telemetry/uiTelemetry.test.ts | core 侧采样与边界场景测试 |
| packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts | TTFT 测量点(首个用户可见内容) |
| packages/cli/src/ui/components/StatsSessionTab.tsx | TUI Session 标签页的 Generation Metrics 渲染 |
| packages/cli/src/ui/commands/statsCommand.ts | /stats命令、文本输出与daily/monthly/export子命令 |
| packages/cli/src/ui/contexts/SessionContext.tsx | 会话统计上下文数据源 |
| .qwen/specs/2025-06-03-stats-dashboard-redesign.md | /stats仪表盘整体设计(Session 标签页保持不变的背景) |
结语
Generation Metrics 为 qwen-code 的/stats补齐了“生成过程性能”这一观测维度,其设计有三个显著特点:增量累加模型(用last快照 + 四个累计值同时支撑单请求与会话级指标)、防御式采样(对缺失 TTFT、内部 prompt、零耗时、无可见内容等边界一律显式处理)、双出口一致性(TUI 渲染与非交互文本输出共享同一数据源与计算口径)。配合.qwen/e2e-tests/2026-07-24-generation-stats.md中给出的自动化与手动验证流程,任何开发者都可以在几分钟内独立验证该功能的正确性。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考