feat(protocol): 全量 opt 打点按帧分组,定位每个模型消费的位数

This commit is contained in:
TermiNexus committed 2026-10-21 04:03:51 +08:00
1 parent d95e35e5c9
commit b8651b4a8a
2 files changed
+116 -31

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
# analyze_opt_trace.py - 分析客户端「全量 opt 读取」打点,按帧还原每个模型消费了几位
#
# 数据来源:镜像站 module_24635.js 的 Ht.t7h 补丁。每条记录:
# rid = 位读取器实例 id(同一帧共享 → 同 rid 即同帧)
# pos = 读位前的绝对位索引;bit = 读到的位
# slots = 内层读器槽位名(模型字段);elemKeys = 列表元素读器槽位名
# 槽位名全局唯一,所以能用 names/codec_fields.json 的 Slots 反查出具体模型。
#
# 用法: python tools/analyze_opt_trace.py [--names names] [--only bs,Tu,tu]
import json
import os
import sys
from collections import defaultdict
Root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def Arg(Name, Def):
if '--' + Name in sys.argv:
I = sys.argv.index('--' + Name)
if I + 1 < len(sys.argv):
return sys.argv[I + 1]
return Def
NamesDir = os.path.abspath(Arg('names', os.path.join(Root, 'names')))
TracePath = os.path.join(NamesDir, 'opt_trace.json')
FieldsPath = os.path.join(NamesDir, 'codec_fields.json')
def LoadSlotIndex():
with open(FieldsPath, 'r', encoding='utf-8') as F:
Codecs = json.load(F).get('Codecs') or {}
Index = {}
for Key, C in Codecs.items():
Slots = tuple(sorted(S.get('Slot') for S in (C.get('Slots') or [])))
if Slots and Slots not in Index:
Index[Slots] = Key
return Index
def Main():
if not os.path.exists(TracePath):
print('缺少 ' + TracePath)
print('请先用镜像站进一局战斗(Ht.t7h 已打全量 opt 打点)。')
return 1
with open(TracePath, 'r', encoding='utf-8') as F:
Data = json.load(F)
Rows = Data.get('trace') or []
print('opt 打点:%d 条(采集于 %s)' % (len(Rows), Data.get('at', '?')))
Index = LoadSlotIndex()
Only = [S for S in (Arg('only', '') or '').split(',') if S]
Frames = defaultdict(list)
for R in Rows:
Frames[R.get('rid')].append(R)
print(' 帧数:%d' % len(Frames))
Shown = 0
for Rid in sorted(Frames, key=lambda X: (X is None, X)):
Seq = Frames[Rid]
Items = []
Names = []
for R in Seq:
Slots = tuple(sorted(R.get('slots') or []))
Key = Index.get(Slots)
if not Key:
ElemKeys = tuple(sorted(R.get('elemKeys') or []))
Key = ('list<' + (Index.get(ElemKeys) or '?') + '>') if ElemKeys else ('?' + str(Slots))
Short = Key.split('::')[-1]
Names.append(Short)
Items.append('%s@%s=%s' % (Short, R.get('pos'), R.get('bit')))
if Only and not any(N in Only for N in Names):
continue
Shown += 1
if Shown > 12:
continue
print(' rid=%s(%d 位):%s' % (Rid, len(Seq), ' '.join(Items)))
if Shown > 12:
print(' …还有 %d 帧匹配' % (Shown - 12))
return 0
if __name__ == '__main__':
sys.exit(Main())
@@ -413,26 +413,29 @@
null == n ? t.f7h_1.b7h(!0) : (t.f7h_1.b7h(!1), this.r7h_1.s7h(t, n));
}),
(z(Ht).t7h = function (t) {
// [Hornet 调试] 可选读器的位痕迹采样:只对「可选 + 列表」这类罕见的组合记录
// (判据:内层读器带列表槽位 z7h_1),把位值、内层读器类名、调用栈节流 POST 到
// /__dump__/nullmap_trace.json,用于确认 opt<list<…>> 在真实数据里到底占不占位。
// 位只读一次:采样失败也只是少一条日志,绝不改变解码结果。
// [Hornet 调试] 可选读器打点(全量 opt,按帧分组):记录
// rid = 位读取器实例 id(一帧内所有记录共享同一个位读取器 → 同一 rid 即同一帧)
// pos = 读位前的绝对位索引;bit = 读到的位
// slots = 内层读器的槽位名(模型字段 → 槽位名全局唯一,可离线反查是哪个模型)
// 目的:直接看客户端对「某个模型」到底消费了几个 opt 位,从而定位我们少算的那一位。
// 位只读一次;打点异常不影响解码。
var Inner = this.r7h_1;
if (Inner && Inner.z7h_1 !== undefined) {
var B0 = t.f7h_1.xy();
var BR = t && t.f7h_1;
if (BR && typeof BR.xy === "function" && BR.y7g_1 !== undefined) {
var B0 = BR.xy();
try {
if (typeof window !== "undefined") {
var T = (window.__HornetBitTrace__ = window.__HornetBitTrace__ || {
rows: [], sent: 0, timer: 0,
var T0 = (window.__HornetOptTrace__ = window.__HornetOptTrace__ || {
rows: [], timer: 0, nextRid: 1,
push: function (Row) {
if (this.rows.length >= 4000) return;
if (this.rows.length >= 8000) return;
this.rows.push(Row);
if (!this.timer) {
var Me = this;
this.timer = setTimeout(function () {
Me.timer = 0;
try {
fetch("/__dump__/nullmap_trace.json", {
fetch("/__dump__/opt_trace.json", {
method: "POST",
body: JSON.stringify({ at: new Date().toISOString(), count: Me.rows.length, trace: Me.rows }),
});
@@ -441,30 +444,26 @@
}
},
});
var St = "";
try {
St = String(new Error().stack || "").split("\n").slice(1, 4).join(" | ");
} catch (E3) {}
// hid:给每个可选包装器实例分配稳定 id(同一字段的多次读取共享 id);
// elem/elemKeys:内层列表的元素 codec 类名与槽位名——槽位名全局唯一,可离线唯一定位字段。
var Elem = Inner.z7h_1;
if (!Inner.__hornetHid) {
Inner.__hornetHid = T.nextId || 1;
T.nextId = Inner.__hornetHid + 1;
}
var ElemName = '';
var Slots = [];
var ElemKeys = [];
try {
ElemName = (Elem && Elem.constructor && Elem.constructor.name) || '';
ElemKeys = Elem ? Object.keys(Elem) : [];
Slots = Inner ? Object.keys(Inner) : [];
ElemKeys = Inner && Inner.z7h_1 ? Object.keys(Inner.z7h_1) : [];
} catch (E5) {}
T.push({
hid: Inner.__hornetHid, bit: B0, inner: Inner.constructor && Inner.constructor.name,
elem: ElemName, elemKeys: ElemKeys,
// pos = 客户端位读取器的绝对游标(Et.y7g_1,读之前)。用来和我们离线解的位游标对比
pos: (t.f7h_1 && typeof t.f7h_1.y7g_1 === "number") ? t.f7h_1.y7g_1 : -1,
stack: St,
});
// 只记「能唯一定位」的:模型槽位 或 列表元素槽位;基础类型 opt 太碎且无指纹,跳过
if (Slots.length > 0 || ElemKeys.length > 0) {
if (!BR.__hornetRid) {
BR.__hornetRid = T0.nextRid || 1;
T0.nextRid = BR.__hornetRid + 1;
}
T0.push({
rid: BR.__hornetRid,
pos: BR.y7g_1 - 1,
bit: B0,
slots: Slots,
elemKeys: ElemKeys,
});
}
}
} catch (E4) {}
return B0 ? null : Inner.t7h(t);