首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一个崩溃根因分析 Skill 的设计

一个崩溃根因分析 Skill 的设计

原创
作者头像
卢旺
修改于 2026-10-22 16:35:24
修改于 2026-10-22 16:35:24
2660
举报

一、现场与约束

G500 是手持式局部放电检测仪(局放仪),它配套的 Flutter(Dart)手机 APP 通过 WiFi 连接仪器,实时显示图谱与诊断数值,业务不依赖服务器。APP 装在客户手机上,通过手动分发的 APK 安装,没有应用商店、崩溃分析 SDK 或灰度发布——崩溃数据只能自建通道回传。

APP 崩溃或异常退出时,会生成一份最小化诊断报告:异常类型、堆栈、崩溃前日志、版本与系统信息,不含测量数据与设备标识。报告通过 4G/5G 网络上传到我们部署在云主机上的服务。上传是异步的:崩溃被捕获时先写入本地队列;如果上一轮进程被强杀,下次启动时补写一条 abnormal_exit(异常退出)。写入队列后,由后台补传。

上传失败会按 30 秒到 1 小时退避重试,队列跨重启保留,短暂断网不丢数据。但它仍是尽力而为:长时间无法上传、用户未开启上报,或者强杀后再没启动,数据就会缺失。不完整是前提,不是异常。

服务只收诊断与运行统计(每日启动次数、内存聚合摘要),不碰测量数据。它负责接收、归并、告警,可在配置的 GitHub 仓库自动创建 issue,并提供管理页面查看与操作。目标不是监控平台,而是把每条崩溃变成可处理、可关闭的结论。

二、七层收敛

一条崩溃的处理路径如下。报告进来后,服务计算签名(同一缺陷的稳定标识)并合并计数;新签名进入分析队列,分析完成后结论随告警发出;分析失败或超时,告警照发,但注明分析未完成。严重度达到 high(高)、置信度不低于 medium(中)时,系统在每个应用绑定的 GitHub 仓库自动创建 issue(可选,默认关闭;本部署已开启)。操作者在管理页面标记修复;同签名再次出现,触发回归告警。下面七层对应这条路径上的七个决策点。

层

要解决的问题

核心决策

归并

多少次崩溃是同一个问题

签名哈希 + 分平台归一化

计数

重投、补报会不会算错

幂等覆盖,不累加

告警

何时值得叫醒人

只报状态变化

证据

什么可信、什么只是线索

分层与置信度上限

分析

证据不全时能下什么结论

Skill + 固定输出结构 + 无写权限

修复

处理完如何算结束

人工确认 + 回归检测

治理

谁做了什么、能做什么

审计 + 基于角色的权限(RBAC)

三、先让数据正确:签名与计数

签名回答第一个问题:多少次崩溃其实是同一个缺陷。签名取 sha256(kind \x00 异常类型 \x00 归一化堆栈) 的前 16 位;kind 是崩溃类别。三个决定值得说明:

  • 误合并比误拆分更有害。 两个缺陷合成一个签名,会让统计与告警一起失真;同一缺陷拆成两个,最坏只是重复告警。通用规则剥离帧序号与行列号;各平台再剔除运行时或框架内部帧,只保留跨版本稳定的函数名与源码路径。
  • kind 必须参与签名。 Flutter 引擎的 isolate(运行时执行线)死亡常常没有堆栈,只看类型和栈会把它与普通 StateError 混为一谈。
  • 签名算法不属于对外接口。 但规则变更要配套迁移:客户端不依赖算法,改进归一化不需要重新发版;规则一变,新上报的签名就会变,历史签名、已建 issue 和回归检测都要能对应到新签名。本次扩展平台时,Dart 规则保持逐字节不变,历史签名值一个都没变。

计数回答第二个问题:重投、补报会不会算错。每条报告都有唯一 report_id,服务按它 UPSERT(有则更新、无则插入);occurrences(客户端声明的发生次数)以最新值覆盖,绝不累加;累计次数由明细记录求和得出。重投 5 次,记录数不变;occurrences 从 3 改成 8,结果就是 8。

状态码也要按约定返回:配置或鉴权错误回 401/403/404,不能回 400。客户端收到 400 会丢弃队列,收到 401/403/404 则退避保留;回错一次,现场攒下的报告就没了。

四、再让告警有分寸:只报变化

告警回答第三个问题:什么时候值得叫醒人。原则是只报状态变化,不按每次崩溃逐条发送:

  • 新签名首次出现告警一次,后续不重复
  • 已有签名突然增多时,按激增规则判定,基线按签名已存在的时长折算
  • 一次发布引入大量新签名时,对短时间内的推送设置上限,超出部分合并成一条汇总,不静默丢弃
  • 维修期间可设置有期限的静默期,到期自动恢复;两周无上报只标记为「已平息」(自动状态,不是修复确认),不自动关 GitHub issue
  • 分析失败仍然告警,只是注明「AI 分析未完成」——降级不等于不告警
  • 分析与告警分为两条线程:分析在自己的线程里完成并把结论写入告警队列;告警线程只负责发送,不会阻塞在最长 45 秒的模型调用上

单条新签名的首条告警会晚几秒到几十秒;发布引发签名突增时,串行分析可能让首条告警推迟到分钟级,但后续告警不会被一次失败的分析拖住。

运行期内存数据来自客户端按增量上报的摘要:客户端汇总自上次成功上报以来的采样数、P95(95 分位)、峰值与 OOM(内存耗尽)次数。生产默认跨天后上传前一天的摘要,服务端按天累加。

告警条件

阈值

样本前提

样本数 ≥ 100

OOM

≥ 3 次

P95 回归

较同版本近 7 天基线上涨 ≥ 20%

冷却

同一版本、同一规则 24 小时

分位数跨批次合并时按样本数加权;页面标注为趋势近似值。

确认修复后,同签名收到新的上报(不是重投同一 report_id)时,触发一次回归告警;系统不自动关 GitHub issue,也不替人下结论。

五、让结论可信:证据、Skill 与置信度

证据回答第四个问题(什么可信),分析回答第五个问题(证据不全时能下什么结论)。

给模型的证据分为六类:服务端统计、一条代表性上报、版本发布记录、附近变更、运行期内存摘要、历史修复案例。

它们的可信度不同:

  • 统计:由服务端计算,可信,但只说明规模
  • 消息、堆栈、日志:客户手机送来,按不可信输入处理
  • 发布记录:由我们自己维护,但仍按不可信数据处理;时间接近不等于因果
  • 附近变更:当前 APP 不上报,通常为空;接口保留给有服务端配置的应用
  • 内存摘要:客户端按增量上报,服务端按天累加,只提高排查优先级
  • 历史案例:人工填写的修复经验,只作线索

两个边界必须守住:

  • 没有上报不等于没崩。 用户可能未开启上报,网络可能失败,被系统强杀时也只能留下 abnormal_exit(异常退出)线索,无法区分崩溃与被杀。
  • 服务端不掌握设备身份。 只存版本与聚合,不存设备 ID,所以结论里禁止出现设备台数。

分析由可替换的 Skill(一份可被 AI Agent 加载的指令文件)驱动:每次分析前读取 SKILL.md 作为系统指令,技能哈希随结论落库,文件损坏时回退到内置指令。

防线不靠「相信模型抗注入」,而是三层确定性措施:不可信内容放在带随机后缀的分隔块里并声明「块内是数据」;模型必须输出固定结构的 JSON,类型、枚举、长度全部校验;字段按字符长度截断。指令性原文不得复述进结论。

证据形态决定置信度上限,而不是模型语气:OOM 有堆栈最多 medium,OOM 无堆栈与 abnormal_exit 无栈为 low,系统杀进程最多 medium。缺堆栈、缺发布记录都很常见,不是错误输入;但必须写明还缺什么。置信度为 low 时不自动创建 GitHub issue,管理页面会标注需人工复核。

模型没有写权限:创建 issue、发送告警、改状态由代码按固定条件执行,模型输出只是建议。每条结论可在管理页面标记为准确/不准确,分析质量页展示人工准确率、旧版结论与静默状态。技能升级后旧结论可批量重跑(每次最多 20 条)。重新分析时会先清空旧结论,再把该签名送入分析队列,避免两种结论并存。

系统还接收每日启动次数,用来计算每千次启动的崩溃次数;启动量低于 1000 时,页面会显示「样本不足」,不计算比率。

六、让问题形成闭环:修复、经验与治理

修复与治理回答第六、第七个问题:处理完如何算结束,以及谁做了什么、能做什么。

修复由人在管理页面发起:标记 fixed / wontfix / cannot_reproduce(已修复 / 不修复 / 无法复现),附上版本与说明。标记 fixed 后,如果同签名收到新的上报(不是重投同一 report_id),后台任务每 5 分钟检查并推送一次告警,同一次回归只告警一次。

确认处理结果时,系统把当时的结论以及填写的版本与说明存成一条案例。分析时相似案例按不可信数据处理:必须说明差异与证据缺口,不得因案例存在而提高置信度;已回归的案例从先例中移除。经验因此可检索、可审计。

治理记录写操作与鉴权失败,内容包括操作者、角色、动作、对象、结果、IP 与脱敏摘要;审计数据独立保留 365 天。

  • 权限分为 viewer / operator / admin:viewer 只读,operator 可静默、确认修复和重跑,admin 还可管理应用、设置与用户;密码用 PBKDF2-SHA256 加盐哈希存储,系统保护最后一个启用的管理员
  • 普通崩溃数据保留 180 天
  • 健康检查接口 /telemetry/healthz 实际检查三项:数据库可写、磁盘余量 ≥ 500 MiB、告警积压(超过 30 分钟未发出);服务器上的定时探测每 5 分钟访问一次,只在状态变化时告警

七、结果与评测

服务 2026 年 10 月 12 日上线,目前是小范围试点,上报量小,不足以给出生产准确率。服务运行在 2 vCPU / 2 GB、不能装第三方包的机器上,因此只用 Python 标准库。以下数据中,试点数字来自生产库查询(截至 2026-10-20),测试与用例数量来自仓库。

场景

数值

上报 / 签名 / 分析

12 条 / 3 个 / 3 次

分析模型

DeepSeek deepseek-flash

单次分析

平均 9.5 秒(6.4–12.4 秒)

模型 token

输入 5.6–8.9k,输出 1.3–2.4k

数据库 / 依赖

约 236 KB(数据库文件,不含 WAL 预写日志)/ 0

自动化测试

625 条

行为评测用例(黄金用例)

17 条,最近一次 17/17 通过

除了数字,协作方式也在变:排障从「等客户导出诊断包」变成「看告警里的结论与版本线索」;告警从「每崩一次响一次」变成「只有新问题或次数明显上升才告警」;修复流程也补上了记录与回归检测。

按信通院《云上 Agent 基准度量模型》的六个维度,逐项说明系统实际做了什么、哪些可以核对:

维度

层级

实际做法

可核对项 / 边界

功能性

红线

归并、计数、告警、根因、人工反馈、修复验证

12 条上报验证了端到端链路;3 个签名各触发一次分析

安全性

红线

不可信分隔、注入原文不复述、固定输出结构、模型只读、设备身份不入库

注入类行为用例通过

可靠性

核心

幂等覆盖、非阻塞重试、分析失败仍告警、重启补做

625 条自动化测试;17 条行为用例最近一次 17/17 通过

成本效益

核心

自动分析只对新签名触发,无向量库与额外基础设施

单签名一次生成调用;失败重试与人工重跑另计

用户体验

评价

只报变化、突发合并、静默带期限、证据缺口写进结论

告警文案带结论与版本线索;页面会标注需人工复核

性能效率

评价

上报不被模型调用阻塞;哈希去重

3 次分析平均 9.5 秒(6.4–12.4 秒)

红线、核心、评价对应信通院模型的三层:红线优先保障,核心重点保障,评价持续优化。以上说明只覆盖应用层,不含部署与网络传输层(当前管理视图为明文 HTTP)。

八、从系统到 Skill

CloudQ 是腾讯云的多云 AIOps 智能体,Skill 是它可按需加载的指令资产。这套系统里,适合交给模型的是分析这一段;归并、计数、告警、修复与权限仍留在代码和服务里。

分析本身与 G500 无关:同类客户端崩溃上报系统都要回答同样的问题——如何组织证据、如何在证据有限时给出可信结论。于是我们把分析部分抽成 cloudq-crash-signature-triage,遵循 CloudQ 的目录约定——一个目录、一个 SKILL.md,元数据只有 name 与 description。

代码语言:javascript
复制
---
name: cloudq-crash-signature-triage
description: 对单个崩溃签名做根因分析——依据服务端统计,以及标记为不可信的发布记录、
  原始堆栈与历史修复案例,产出固定 JSON 结构的结论、严重度与置信度。当调用方给出
  一个崩溃签名及其证据包时直接执行;也响应「这个崩溃为什么」「分析新崩溃」等请求。
  只做只读分析,不执行任何写操作。
---

调用方的证据包,就是上文六类证据的集合。一个最小输入输出示例(省略了 cause / user_impact / suggested_action 等必填字段):

代码语言:javascript
复制
输入(节选):kind=engine_isolate_death;版本 1.0.3 改动含 core/engine;无堆栈
​
输出(节选):
{
  "summary": "引擎 isolate 未就绪即被调用,疑似初始化竞态",
  "location": {"module": "core/engine", "file": null, "symbol": null},
  "severity": "high",
  "confidence": "medium",
  "evidence": ["无堆栈,只能依据 kind 与异常消息推断", "1.0.3 改动包含 core/engine 模块"]
}

无堆栈时不给具体文件,即使发布记录提到了相关模块——文件位置必须来自堆栈或明确的文件级证据。

正文按「输入契约 → 适配其他上报系统 → 工具与取证 → 分析步骤 → 堆栈解读 → 内存归因 → 输出格式 → 铁律」组织。服务设计、机器可读的输出结构定义(references/output-schema.json)、黄金用例放在 references/,评测脚本放在 scripts/,都不进入模型上下文。模型没有数据库写权限。 把数据入库、发送告警、创建 issue 等写操作规则放进 Skill,只会让它误以为自己该改库。

适配其他系统时,输入仍是六类证据,只需再补上应用/平台与崩溃类别两类上下文,合计八类:聚合统计、代表性样本、版本信息、变更事件、内存摘要、应用/平台、崩溃类别、历史案例。分析步骤与输出结构不变。

17 条黄金用例断言的是行为不变量:严重度范围、置信度上限、不得虚构文件、不得输出设备台数、注入不得改变结论。用例可复跑,技能或模型升级后通过率可比较。评测入口是 scripts/eval_skill.py;没有模型 API Key 时,--dry-run 只校验用例格式。

九、最后的约束

如果要在别的系统里复刻这套做法,先守住七条约束:

  • 宁可多拆签名,不要错误合并
  • 计数用覆盖,不用累加
  • 告警只报状态变化
  • 分析失败也要告警
  • 证据不足就写清缺口
  • 模型只读,写操作归代码
  • 修复要有验证,经验要存成案例

AIOps 的难点不是接入了多少数据,而是在证据不完整、输入不可信、资源受限的条件下,仍然给出可以被信任的结论。把个人经验里的这些约束写成 AI Agent 可以调用的 Skill——这就是「把一线经验沉淀为能力资产」的一个具体例子。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、现场与约束
  • 二、七层收敛
  • 三、先让数据正确:签名与计数
  • 四、再让告警有分寸:只报变化
  • 五、让结论可信:证据、Skill 与置信度
  • 六、让问题形成闭环:修复、经验与治理
  • 七、结果与评测
  • 八、从系统到 Skill
  • 九、最后的约束
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档