
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 混为一谈。计数回答第二个问题:重投、补报会不会算错。每条报告都有唯一 report_id,服务按它 UPSERT(有则更新、无则插入);occurrences(客户端声明的发生次数)以最新值覆盖,绝不累加;累计次数由明细记录求和得出。重投 5 次,记录数不变;occurrences 从 3 改成 8,结果就是 8。
状态码也要按约定返回:配置或鉴权错误回 401/403/404,不能回 400。客户端收到 400 会丢弃队列,收到 401/403/404 则退避保留;回错一次,现场攒下的报告就没了。
告警回答第三个问题:什么时候值得叫醒人。原则是只报状态变化,不按每次崩溃逐条发送:
单条新签名的首条告警会晚几秒到几十秒;发布引发签名突增时,串行分析可能让首条告警推迟到分钟级,但后续告警不会被一次失败的分析拖住。
运行期内存数据来自客户端按增量上报的摘要:客户端汇总自上次成功上报以来的采样数、P95(95 分位)、峰值与 OOM(内存耗尽)次数。生产默认跨天后上传前一天的摘要,服务端按天累加。
告警条件 | 阈值 |
|---|---|
样本前提 | 样本数 ≥ 100 |
OOM | ≥ 3 次 |
P95 回归 | 较同版本近 7 天基线上涨 ≥ 20% |
冷却 | 同一版本、同一规则 24 小时 |
分位数跨批次合并时按样本数加权;页面标注为趋势近似值。
确认修复后,同签名收到新的上报(不是重投同一 report_id)时,触发一次回归告警;系统不自动关 GitHub issue,也不替人下结论。
证据回答第四个问题(什么可信),分析回答第五个问题(证据不全时能下什么结论)。
给模型的证据分为六类:服务端统计、一条代表性上报、版本发布记录、附近变更、运行期内存摘要、历史修复案例。
它们的可信度不同:
两个边界必须守住:
abnormal_exit(异常退出)线索,无法区分崩溃与被杀。分析由可替换的 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 加盐哈希存储,系统保护最后一个启用的管理员/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)。
CloudQ 是腾讯云的多云 AIOps 智能体,Skill 是它可按需加载的指令资产。这套系统里,适合交给模型的是分析这一段;归并、计数、告警、修复与权限仍留在代码和服务里。
分析本身与 G500 无关:同类客户端崩溃上报系统都要回答同样的问题——如何组织证据、如何在证据有限时给出可信结论。于是我们把分析部分抽成 cloudq-crash-signature-triage,遵循 CloudQ 的目录约定——一个目录、一个 SKILL.md,元数据只有 name 与 description。
---
name: cloudq-crash-signature-triage
description: 对单个崩溃签名做根因分析——依据服务端统计,以及标记为不可信的发布记录、
原始堆栈与历史修复案例,产出固定 JSON 结构的结论、严重度与置信度。当调用方给出
一个崩溃签名及其证据包时直接执行;也响应「这个崩溃为什么」「分析新崩溃」等请求。
只做只读分析,不执行任何写操作。
---调用方的证据包,就是上文六类证据的集合。一个最小输入输出示例(省略了 cause / user_impact / suggested_action 等必填字段):
输入(节选):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 删除。