main.go 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603
  1. package main
  2. import (
  3. "encoding/binary"
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "math"
  8. "math/big"
  9. "net/http"
  10. _ "net/http/pprof" // /debug/pprof/* 挂在 DefaultServeMux 上, 供排查内存/CPU
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "runtime/debug"
  15. "strconv"
  16. "strings"
  17. "sync"
  18. "time"
  19. "unsafe"
  20. // analyzer 的 go.mod 有 replace TsDB2 => ../, 可以直接读 core 的类型表。
  21. // 列型的宽度/名字改为投影自 typeRegistry, 见 fSz / fName 的注释。
  22. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  23. )
  24. const (
  25. PGSZ = 16384
  26. HDRSZ = 4096
  27. PHDR = 20 // PageHdr actual size (with alignment)
  28. DP_HDR = 48 // DataPage header (DB/core/page/datapage.go DataPageHdrSize):
  29. // PageHdr(20) + Count(4) + Min(8) + Max(8) + LiveCnt(4) + Dead(4) = 48
  30. // v10 在 Count/Min/Max 之后新增了 LiveCnt/Dead 用于 O(1) COUNT 与
  31. // 失效占比判断; 漏掉这 8B 会让 eachRow 从 LiveCnt/Dead 上读出
  32. // 假 row header, pkID 全部错位, 进而把整库二级索引条目误判为悬空。
  33. IDX_HDR = 32 // PHDR(20) + pad(1) + IndexType(1) + EntryLen(2) + Count(4) + IsLeaf(1) + pad(3)
  34. // 下面两个尺寸与 TI_* / IM_* 偏移与 DB/core 的 IndexMeta/TableInfo 布局一一对应。
  35. // TableNameSize = 48 且 IndexNameSize = 64 时: IndexMeta 100 字节, TableInfo 104 字节。
  36. // 这里的每一个常量都由 layout_assert.go 的编译期断言钉在 DB/core 的真实结构体上,
  37. // 改了 DB/core 布局却漏改这里会直接编译失败 (而不是静默按错位读盘)。
  38. META_SZ = 100 // IndexMeta entry size (with Go alignment padding)
  39. TINFO_SZ = 104 // TableInfo struct size (with alignment)
  40. PG_HEAD = 0
  41. PG_TABLE = 1
  42. PG_DATA = 2
  43. PG_HEAP = 3
  44. PG_IMETA = 4
  45. PG_IDATA = 5
  46. PG_OVER = 6 // DataOverflowPage: 数据页溢出页 (已废弃)
  47. PG_WAL = 7 // WALIndex: WAL 日志页
  48. PG_SEXT = 8 // SchemaExtIndex: 全库共享的表结构扩展页
  49. // 分析器自造的哨兵
  50. PG_NEVER = -1 // 页号 >= NextFreePage, 从未被分配过
  51. PG_RESERVED = -2 // 页 1: 引擎预留, 从不分配 (header_page.go newHeader)
  52. PG_ORPHAN = -3 // 已下发区间内、类型字节=0 却不在空闲链上 (被遗漏的空闲页/泄漏页)
  53. PG_OTHER = -4 // 未知类型字节
  54. // PageHdr 字段偏移 (共 20 字节)
  55. PH_REF = 2 // RefCount uint16 (仅堆页维护: 指向本页的活引用数)
  56. PH_PREV = 8 // PrePage uint32
  57. PH_NEXT = 12 // NextPage uint32
  58. PH_USE = 16 // UseSize uint32
  59. // 表页在 PageHdr 之后为共享扩展页链头 (SchemaExtPageID) 统一预留 4 字节,
  60. // 因此每条表记录的起始偏移是 PHDR + SEXT_HDR (所有表页一致)。
  61. SEXT_HDR = 4
  62. REC_OFF = PHDR + SEXT_HDR
  63. // SchemaFragHdr: NextPage(4B BE) + NextOff(2B BE) + DataLen(2B BE)
  64. FRAG_HDR = 8
  65. // TableInfo 字段偏移 (共 104 字节, TableNameSize=48)
  66. TI_FLAG = 48 // Flag byte (0x80 = 已删除/空洞)
  67. TI_HPCNT = 52 // HeapPageCount uint32 (复用 TotalRows 前的对齐填充)
  68. TI_ROWS = 56 // TotalRows uint64
  69. TI_FHEAP = 64 // FirstHeap uint32
  70. TI_FDATA = 72 // FirstData uint32
  71. TI_ROWSZ = 80 // RowSize uint16
  72. TI_ALLOC = 82 // FieldAllocateSize uint16
  73. TI_FIDX = 88 // FirstIndexID uint32
  74. TI_EXTPG = 96 // ExtPage uint32 (schema 续块所在扩展页)
  75. TI_EXTOFF = 100 // ExtOff uint16 (首个续块在页内的偏移)
  76. // IndexMeta 字段偏移 (共 100 字节, IndexNameSize=64)
  77. //
  78. // 这些偏移全都随 IndexNameSize 整体平移 —— 索引名数组一变长, 其后每个字段的位置
  79. // 都跟着变。名字长度单独列成 IM_NAMSZ, slice 时就不必再写 64 这个魔法数字。
  80. IM_NAMSZ = 64 // IndexName [IndexNameSize]byte 的字节数 (索引名: im[0:64])
  81. IM_TYPE = 64 // IndexType byte
  82. IM_FCNT = 65 // FieldCnt byte
  83. IM_FLDS = 66 // FieldIDs [16]byte
  84. IM_EP = 84 // EntryPage uint32 (跳过 2 字节对齐填充)
  85. IM_FLAG = 96 // Flag byte
  86. // 索引 Flag 的位 (对应 core 的 IndexFlag*: bit7 软删除 / bit0 唯一索引)。
  87. // 取值由 layout_assert.go 拿 core 的常量编译期钉住 —— 这里手写位掩码是为了
  88. // 不依赖 core 的常量名, 但绝不能和它漂移。
  89. IM_FLAG_DELETED = 0x80
  90. IM_FLAG_UNIQUE = 0x01
  91. // 列 (FieldInfo) Flag 的位, 对应 core/types 的 Flag* 常量:
  92. // bit7 = 已删除 / bit6 = 可为 NULL / bit5 = 唯一
  93. // bit4..bit0 = 列级类型参数 (按类型解释: decimal 是 scale, 字符串列是宽度码)
  94. // 掩码名沿用 FLAG_SCALE (core 侧叫 FlagScaleMask), 两个用途共用同一格。
  95. FLAG_DELETED = 0x80
  96. FLAG_NULLABLE = 0x40
  97. FLAG_UNIQUE = 0x20
  98. FLAG_SCALE = 0x1f
  99. )
  100. // ===== Data Structures =====
  101. type TInfo struct {
  102. Name string `json:"name"`
  103. RowN uint64 `json:"rowN"`
  104. FSz uint16 `json:"fsz"`
  105. DP0 uint32 `json:"dp0"`
  106. IP0 uint32 `json:"ip0"`
  107. HP0 uint32 `json:"hp0"`
  108. HPC uint32 `json:"hpc"` // HeapPageCount: 声明式堆链长度
  109. ActiveVer byte `json:"activeVer"`
  110. MasterFields []MFInfo `json:"masterFields"`
  111. Versions []VInfo `json:"versions"`
  112. Idxs []IInfo `json:"idxs"`
  113. }
  114. type MFInfo struct {
  115. Name string `json:"name"`
  116. Type byte `json:"type"`
  117. TypeName string `json:"typeName"`
  118. // Flag 列级标志字节, 随 schema 落盘 (对应 types.FieldInfo.Flag):
  119. //
  120. // bit7 bit6 bit5 | bit4 ........ bit0
  121. // 删除 可为 NULL 唯一 | 列级类型参数 (0..31)
  122. //
  123. // 低 5 位按类型解释 (如 decimal:2 → 2, maxlen 声明出来的字符串列 → 宽度码);
  124. // 删除标记不再占用 Typ 的最高位。
  125. Flag byte `json:"flag"`
  126. // Param 列级类型参数 (Flag 低 5 位): decimal 是 scale, 字符串家族是行内宽度码,
  127. // 其余类型为 0。
  128. Param byte `json:"param"`
  129. // Nullable / Unique 从 Flag 拆出来的状态位。
  130. //
  131. // 位掩码只在这一个地方拆一次, 前端 / 检查逻辑都用这两个布尔 —— 每个消费方
  132. // 各自去 &0x40 / &0x20, 迟早会有人写错位, 而"列可不可空"读错的后果是悄悄
  133. // 把 NULL 当成 0 来解释。
  134. Nullable bool `json:"nullable"`
  135. Unique bool `json:"unique"`
  136. Sz int `json:"sz"`
  137. // Label / Tip 类型徽章的展示文本与悬浮说明, 由 fLabel / fTip 在【后端】算好。
  138. //
  139. // 字符串列的 label 会显式带出行内宽度 (string(1024B)): 类型档 (string) 与真实
  140. // 宽度矛盾时, 列表里能一眼看出这列到底占多大, 不必往右看 Size 列。
  141. // 放在后端算与 nullable/unique 同一个理由: Flag 低 5 位是【按类型重载】的一格
  142. // (decimal 是 scale, 字符串家族是宽度码), 前端若能拿到它, 迟早有人当字节数显示 ——
  143. // 而"宽度码 20"对应的那列是 1024 字节, 差 50 倍。
  144. Label string `json:"label"`
  145. Tip string `json:"tip"`
  146. Dropped bool `json:"dropped"`
  147. }
  148. type VInfo struct {
  149. Ver byte `json:"ver"`
  150. Fields []string `json:"fields"`
  151. // Indices 本版字段在 masterFields 里的下标 (顺序 = 行内字段顺序)。
  152. //
  153. // 对外必须是 []int 而不是盘上原样的 []byte: 盘上每版字段数是 1 字节, 解析时直接
  154. // copy 最省事, 但 []byte 过 JSON 会变成 base64 字符串 —— 前端 v.indices[i] 拿到的是
  155. // 第 i 个字符, t.masterFields['A'] 恒为 undefined, "这一版每列多宽"整块静默消失。
  156. // 宽化在解析层一次做完, 前端拿到的就是能直接当下标用的数字。
  157. Indices []int `json:"indices"`
  158. }
  159. type FInfo struct {
  160. Name string `json:"name"`
  161. Type byte `json:"type"`
  162. Sz int `json:"sz"`
  163. }
  164. type IInfo struct {
  165. Name string `json:"name"`
  166. Type byte `json:"type"`
  167. TS string `json:"ts"`
  168. EP uint32 `json:"ep"`
  169. Flds []int `json:"flds"`
  170. // Uniq 唯一索引 (IndexMeta.Flag 的 bit0)。索引的 Flag 字节里只有两位:
  171. //
  172. // bit7 bit6 bit0
  173. // 已删除 (未用) 唯一索引
  174. //
  175. // 唯一位不参与一致性校验 (它只约束写入路径, 不影响盘上结构), 但漏掉它会让
  176. // 分析器把唯一索引和普通索引用同一种样子画出来 —— 从外部看, 一张表的约束
  177. // 就完全不可见了。
  178. Uniq bool `json:"uniq"`
  179. }
  180. type DP struct {
  181. ID uint32 `json:"id"`
  182. Nx uint32 `json:"nx"`
  183. Pv uint32 `json:"pv"`
  184. Cn uint32 `json:"cn"`
  185. Mx uint64 `json:"mx"`
  186. Mn uint64 `json:"mn"`
  187. Us float64 `json:"us"`
  188. Rs []DR `json:"rs"`
  189. }
  190. type DR struct {
  191. I int `json:"i"`
  192. PKID uint64 `json:"pk"`
  193. SchemaV byte `json:"sv"`
  194. Hex string `json:"h"`
  195. Decoded string `json:"decoded"`
  196. }
  197. type IP struct {
  198. ID uint32 `json:"id"`
  199. Nx uint32 `json:"nx"`
  200. Pv uint32 `json:"pv"`
  201. Lf bool `json:"lf"`
  202. Cn uint32 `json:"cn"`
  203. Us float64 `json:"us"`
  204. Ents []IE `json:"ents"`
  205. IdxType byte `json:"it"`
  206. }
  207. type IE struct {
  208. K string `json:"k"`
  209. P uint32 `json:"p"`
  210. DP uint64 `json:"dp"`
  211. }
  212. type HdrInfo struct {
  213. Magic string `json:"magic"`
  214. Version uint32 `json:"ver"`
  215. PageSize uint32 `json:"pgsz"`
  216. NextFree uint32 `json:"nf"`
  217. TablePage uint32 `json:"tp"`
  218. TableCount uint32 `json:"tc"`
  219. CreateTime string `json:"ct"`
  220. ModifyTime string `json:"mt"`
  221. FreeStart uint32 `json:"fs"`
  222. FreeEnd uint32 `json:"fe"`
  223. FreeCount uint32 `json:"fc"`
  224. GlobalFlags uint64 `json:"gf"`
  225. WALFirst uint32 `json:"wal0"`
  226. WALLast uint32 `json:"wal1"`
  227. TxnID uint64 `json:"txn"`
  228. Comment string `json:"cm"`
  229. // 实算值 (与上面的声明值互校)
  230. FreeWalked int `json:"fw"` // 实走空闲链长度
  231. Pages int `json:"pg"` // 数据页总数
  232. Allocated int `json:"ap"` // 已分配页数
  233. NeverUsed int `json:"nu"` // 从未分配的尾页
  234. FreeVerdict string `json:"fv"` // 空闲链自检结论
  235. FreeDetail string `json:"fd"` // 自检说明
  236. }
  237. type HeapChain struct {
  238. Pages []HeapPageInfo `json:"pages"`
  239. Total int `json:"total"`
  240. }
  241. type HeapPageInfo struct {
  242. ID uint32 `json:"id"`
  243. Next uint32 `json:"nx"`
  244. Prev uint32 `json:"pv"`
  245. Use uint32 `json:"use"`
  246. Us float64 `json:"us"`
  247. Ref uint16 `json:"ref"` // 页头 RefCount: 指向本页的活引用数 (仅堆页有意义)
  248. }
  249. var (
  250. dbF string
  251. fp string
  252. hdr [HDRSZ]byte
  253. tabs []TInfo
  254. mu sync.RWMutex
  255. )
  256. func main() {
  257. log.SetFlags(log.LstdFlags | log.Lshortfile)
  258. fmt.Println("╔══════════════════════════════════════════════╗")
  259. fmt.Println("║ TsDB2 Analyzer v3.0 (Schema Aware) ║")
  260. fmt.Println("║ http://localhost:8081 ║")
  261. fmt.Println("╚══════════════════════════════════════════════╝")
  262. http.HandleFunc("/", homeH)
  263. http.HandleFunc("/api/list-dir", ldH)
  264. http.HandleFunc("/api/open-db", openH)
  265. http.HandleFunc("/api/stats", stH)
  266. http.HandleFunc("/api/tables", taH)
  267. http.HandleFunc("/api/table/", tdH)
  268. http.HandleFunc("/api/data-page", dpH)
  269. http.HandleFunc("/api/index-page", ipH)
  270. http.HandleFunc("/api/btree", btH)
  271. http.HandleFunc("/api/header", hdrH)
  272. http.HandleFunc("/api/heap-chain", heapH)
  273. http.HandleFunc("/api/free-pages", freeH)
  274. http.HandleFunc("/api/pages", pagesH)
  275. http.HandleFunc("/api/pagemap", pagemapH)
  276. http.HandleFunc("/api/find-pk", findPkH)
  277. http.HandleFunc("/api/audit", auditH)
  278. http.HandleFunc("/api/mem", memH)
  279. http.HandleFunc("/api/audit/issues", auditIssuesH)
  280. http.HandleFunc("/api/audit/unacc", auditUnaccH)
  281. http.HandleFunc("/api/heap-chains", heapChainsH)
  282. http.HandleFunc("/api/page-info", pageInfoH)
  283. // 静态资源 (index.html 引用的 js/css/字体等) 一律走 embed, 不再暴露工作目录。
  284. http.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.FS(webRoot()))))
  285. log.Fatal(http.ListenAndServe(":8081", countInflight(http.DefaultServeMux)))
  286. }
  287. // countInflight 把每个请求登记为"在飞读任务": 请求期间可能持有指向映射区的
  288. // 页切片, 换库时不能把映射解掉 (见 audit.go 的 gcRetired)。
  289. func countInflight(h http.Handler) http.Handler {
  290. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  291. enterRead()
  292. defer exitRead()
  293. h.ServeHTTP(w, r)
  294. })
  295. }
  296. // ===== Low-level page reading =====
  297. // rp 读一页 —— 统一走 audit.go 的共享句柄 (全库审计要读上千页, 不能每页都 open)。
  298. func rp(pid uint32) ([]byte, error) { return readPage(pid) }
  299. // rHdr 把 4096 字节的 HeaderPage 读进全局 hdr。
  300. func rHdr() {
  301. f, e := dbFile()
  302. if e != nil {
  303. return
  304. }
  305. f.ReadAt(hdr[:], 0)
  306. }
  307. // ===== Parse all tables from TablePage chain =====
  308. func pTabs() []TInfo {
  309. var R []TInfo
  310. tp := binary.LittleEndian.Uint32(hdr[48:52]) // TablePage (v11 起偏移 48)
  311. if tp == 0 {
  312. return R
  313. }
  314. for pid := tp; pid != 0; {
  315. b, e := rp(pid)
  316. if e != nil {
  317. break
  318. }
  319. us := binary.LittleEndian.Uint32(b[PH_USE : PH_USE+4])
  320. if us > PGSZ {
  321. us = PGSZ
  322. }
  323. // 所有表页都在 PageHdr 之后统一预留 SEXT_HDR 字节, 记录从 REC_OFF 开始
  324. off := uint32(REC_OFF)
  325. for off+TINFO_SZ <= us && off+TINFO_SZ <= PGSZ {
  326. tb := b[off : off+TINFO_SZ]
  327. alloc := uint32(binary.LittleEndian.Uint16(tb[TI_ALLOC : TI_ALLOC+2]))
  328. if tb[TI_FLAG]&0x80 != 0 {
  329. // Drop 留下的空洞 (墓碑): 按记录自身长度跳过
  330. off += TINFO_SZ + alloc
  331. continue
  332. }
  333. nm := strings.TrimRight(string(tb[:48]), "\x00") // 表名: [0, TableNameSize)
  334. rn := binary.LittleEndian.Uint64(tb[TI_ROWS : TI_ROWS+8])
  335. hpc := binary.LittleEndian.Uint32(tb[TI_HPCNT : TI_HPCNT+4])
  336. // 重建完整 schema: 内联块 (alloc 字节) + 共享扩展页续块链
  337. schema := readSchemaBytes(b, off, alloc, tb)
  338. // Parse new schema format
  339. activeVer, masterFields, versions := parseSchemaFields(schema)
  340. // Parse indexes
  341. var is []IInfo
  342. iip := binary.LittleEndian.Uint32(tb[TI_FIDX : TI_FIDX+4])
  343. for ip := iip; ip != 0; {
  344. ib, e := rp(ip)
  345. if e != nil {
  346. break
  347. }
  348. if ib[0] != PG_IMETA {
  349. break
  350. }
  351. iu := binary.LittleEndian.Uint32(ib[PH_USE : PH_USE+4])
  352. if iu > PGSZ {
  353. iu = PGSZ
  354. }
  355. io := uint32(PHDR)
  356. for io+META_SZ <= iu && io+META_SZ <= PGSZ {
  357. im := ib[io : io+META_SZ]
  358. // IndexMeta struct (Name[64] + Type + FieldCnt + FieldIDs[16] + pad(2)
  359. // + EntryPage + Prev + Next + Flag + pad(3) = 100): 各字段偏移见文件头的 IM_*
  360. // 常量, 由 layout_assert.go 的编译期断言钉在 DB/core 的真实结构体上。
  361. if im[IM_FLAG]&IM_FLAG_DELETED == 0 {
  362. in := strings.TrimRight(string(im[:IM_NAMSZ]), "\x00") // IndexName: [0, IndexNameSize)
  363. it := im[IM_TYPE] // IndexType
  364. ic := int(im[IM_FCNT]) // FieldCnt
  365. ep := binary.LittleEndian.Uint32(im[IM_EP : IM_EP+4]) // EntryPage (跳过 2B 对齐填充)
  366. uq := im[IM_FLAG]&IM_FLAG_UNIQUE != 0 // 唯一索引
  367. // 验证 EntryPage 指向有效索引页 (防止已删除表的孤立条目)
  368. if ep != 0 {
  369. pb, pe := rp(ep)
  370. if pe != nil || pb[0] != PG_IDATA {
  371. ep = 0 // 无效页, 跳过
  372. }
  373. }
  374. if ep != 0 {
  375. var fds []int
  376. for j := 0; j < ic; j++ {
  377. fds = append(fds, int(im[IM_FLDS+j])) // FieldIDs
  378. }
  379. is = append(is, IInfo{Name: in, Type: it, TS: tNm(it), EP: ep, Flds: fds, Uniq: uq})
  380. }
  381. }
  382. io += META_SZ
  383. }
  384. ip = binary.LittleEndian.Uint32(ib[PH_NEXT : PH_NEXT+4])
  385. }
  386. R = append(R, TInfo{
  387. Name: nm, RowN: rn,
  388. FSz: binary.LittleEndian.Uint16(tb[TI_ROWSZ : TI_ROWSZ+2]),
  389. DP0: binary.LittleEndian.Uint32(tb[TI_FDATA : TI_FDATA+4]),
  390. IP0: binary.LittleEndian.Uint32(tb[TI_FIDX : TI_FIDX+4]),
  391. HP0: binary.LittleEndian.Uint32(tb[TI_FHEAP : TI_FHEAP+4]),
  392. HPC: hpc,
  393. ActiveVer: activeVer, MasterFields: masterFields, Versions: versions, Idxs: is,
  394. })
  395. off += TINFO_SZ + alloc
  396. }
  397. nx := binary.LittleEndian.Uint32(b[PH_NEXT : PH_NEXT+4])
  398. if nx == 0 || nx == pid {
  399. break
  400. }
  401. pid = nx
  402. }
  403. return R
  404. }
  405. // readSchemaBytes 重建一张表的完整 schema 字节:
  406. // 表页内联块 (alloc 字节) + 共享扩展页里的续块链 [SchemaFragHdr 8B][payload]
  407. func readSchemaBytes(pageBuf []byte, recOff, alloc uint32, tb []byte) []byte {
  408. schema := make([]byte, 0, alloc)
  409. start := recOff + TINFO_SZ
  410. if end := start + alloc; end <= uint32(len(pageBuf)) {
  411. schema = append(schema, pageBuf[start:end]...)
  412. } else if start < uint32(len(pageBuf)) {
  413. schema = append(schema, pageBuf[start:]...)
  414. }
  415. extPage := binary.LittleEndian.Uint32(tb[TI_EXTPG : TI_EXTPG+4])
  416. extOff := uint32(binary.LittleEndian.Uint16(tb[TI_EXTOFF : TI_EXTOFF+2]))
  417. for pid, off := extPage, extOff; pid != 0; {
  418. fb, e := rp(pid)
  419. if e != nil || int(off)+FRAG_HDR > len(fb) {
  420. break
  421. }
  422. nextPage := binary.BigEndian.Uint32(fb[off : off+4])
  423. nextOff := binary.BigEndian.Uint16(fb[off+4 : off+6])
  424. dataLen := int(binary.BigEndian.Uint16(fb[off+6 : off+8]))
  425. s := int(off) + FRAG_HDR
  426. if s+dataLen > len(fb) {
  427. break
  428. }
  429. schema = append(schema, fb[s:s+dataLen]...)
  430. pid, off = nextPage, uint32(nextOff)
  431. }
  432. return schema
  433. }
  434. // parseSchemaFields 解析新版 schema 字段定义链
  435. //
  436. // 格式 (与 DB/core 的 serializeSchema 逐字节对应):
  437. //
  438. // [activeVer 1B][masterCount 1B][typ 1B][flag 1B][nameLen 1B][name]×masterCount
  439. // [verCount 1B][ver 1B][indexCount 1B][indices]×verCount
  440. //
  441. // flag 是**列级标志字节**, 紧跟在 typ 之后 (对应 types.FieldInfo.Flag):
  442. //
  443. // bit7 bit6 bit5 | bit4 ........ bit0
  444. // 删除 可为 NULL 唯一 | 列级类型参数 (0..31)
  445. //
  446. // 字节位置与原来的 param 完全一致, 只是含义被扩展了: 低 5 位【按类型解释】——
  447. // decimal 是 scale (例如 `12 02 06 "Amount"` 就是 decimal(2)), 字符串家族是行内宽度码
  448. // (maxlen 声明出来的列靠它决定宽度)。其余类型低 5 位为 0, 但字节一定在。
  449. //
  450. // 所以下面算 Sz 时必须把 flag 一起传进去 (fSzWithParam): 只看 typ 会给宽度码列算成
  451. // 类型默认档 256, 而它是 2048 就整行错位 —— 与本函数注释开头说的那次事故同一类。
  452. //
  453. // 【这里漏掉这个字节曾造成整表读错】: 原实现按 `[typ][nameLen][name]` 走位, 于是把
  454. // 它当成了 nameLen —— 第一个字段名读成空串, 之后 masterFields / versions 全部错位。
  455. // 症状很误导人: 表显示成"只有 1 个字段且无名"、版本号是乱数、数据页一行都解不出, 而
  456. // 页头那些不依赖 schema 的字段 (TotalRows / RowSize / 页号) 却完全正确, 于是很难往
  457. // "schema 解析"上想。TestParseSchemaAgainstCore 用 core 造的**真库**来钉这件事:
  458. // 它不重述格式, 而是要求两侧对同一份盘上字节的解释一致。
  459. func parseSchemaFields(b []byte) (byte, []MFInfo, []VInfo) {
  460. if len(b) < 2 {
  461. return 0, nil, nil
  462. }
  463. pos := 0
  464. activeVer := b[pos]
  465. pos++
  466. masterCount := int(b[pos])
  467. pos++
  468. var masterFields []MFInfo
  469. for i := 0; i < masterCount && pos+3 <= len(b); i++ {
  470. typ := b[pos]
  471. flag := b[pos+1]
  472. nameLen := int(b[pos+2])
  473. pos += 3
  474. if pos+nameLen > len(b) {
  475. break
  476. }
  477. name := strings.TrimRight(string(b[pos:pos+nameLen]), "\x00")
  478. pos += nameLen
  479. // typ 是纯类型值 (删除标记不再占最高位), 状态位全在 flag 里。
  480. // param / sz 各算一次并复用: 宽度是"行内按多少字节付满"的唯一依据,
  481. // 下面三处 (Sz / Label / Tip) 必须拿到同一个值, 分头算迟早会漂移。
  482. param := flag & FLAG_SCALE
  483. sz := fSzWithParam(typ, param)
  484. masterFields = append(masterFields, MFInfo{
  485. Name: name, Type: typ, TypeName: fName(typ),
  486. Flag: flag, Sz: sz, Param: param,
  487. Label: fLabel(typ, param, sz),
  488. Tip: fTip(typ, param, sz),
  489. Dropped: flag&FLAG_DELETED != 0,
  490. Nullable: flag&FLAG_NULLABLE != 0,
  491. Unique: flag&FLAG_UNIQUE != 0,
  492. })
  493. }
  494. var versions []VInfo
  495. if pos < len(b) {
  496. verCount := int(b[pos])
  497. pos++
  498. for i := 0; i < verCount && pos < len(b); i++ {
  499. verNo := b[pos]
  500. pos++
  501. if pos >= len(b) {
  502. break
  503. }
  504. fieldCount := int(b[pos])
  505. pos++
  506. // 下标逐个宽化成 int (见 VInfo.Indices): 盘上 1 字节/个, 但对外要数字数组。
  507. indices := make([]int, fieldCount)
  508. if pos+fieldCount <= len(b) {
  509. for i, raw := range b[pos : pos+fieldCount] {
  510. indices[i] = int(raw)
  511. }
  512. pos += fieldCount
  513. }
  514. // Build field name list for this version
  515. var fieldNames []string
  516. for _, idx := range indices {
  517. if idx < len(masterFields) {
  518. mf := masterFields[idx]
  519. n := mf.Name
  520. if mf.Dropped {
  521. n += " (dropped)"
  522. }
  523. fieldNames = append(fieldNames, n)
  524. } else {
  525. fieldNames = append(fieldNames, "?")
  526. }
  527. }
  528. versions = append(versions, VInfo{Ver: verNo, Fields: fieldNames, Indices: indices})
  529. }
  530. }
  531. return activeVer, masterFields, versions
  532. }
  533. // ===== Field type helpers =====
  534. // fSz 列在行内的字节宽度。
  535. //
  536. // 【为什么是投影而不是自己 switch】这里原本手写了一张 1..15 的表, core 新增 7 个列型
  537. // (bool/float32/decimal/uuid/year/int128/blob) 时没同步, 于是 fSz 对新类型返回 0 ——
  538. // 这不是"显示成 ?"那么轻: 行解码靠它推进偏移 (decodeRows 里的 off += sz), 0 会让新类型
  539. // 字段【之后的所有列】都从同一个错位偏移解码, 整行静默错位; indexcheck 也会因为
  540. // size <= 0 直接放弃索引键校验。
  541. //
  542. // 改成投影到 core 的 typeRegistry 之后, 这类漏同步在结构上不可能再发生: core 那边有
  543. // allTypes + CheckRegistry 兜底, 这里自动覆盖全部已声明类型。复用 core 的布局事实是
  544. // 本项目既有做法 (见 layout_assert.go 的编译期断言)。
  545. //
  546. // 注意它给的是【类型默认档】宽度; 行内真实宽度还要乘上列级参数 (字符串列的宽度码),
  547. // 行解码/索引键切分一律用 fSzWithParam。
  548. func fSz(t byte) int { return int(types.GetSize(types.FieldType(t))) }
  549. // fSzWithParam 列在行内的真实字节宽度 = types.FieldSize(类型, 列级参数)。
  550. //
  551. // param 取列 flag 的低 5 位 (字符串列是宽度码, decimal 是 scale)。少传它就会给
  552. // "maxlen 声明出来的字符串列"算出类型默认档 256, 而盘上那一列是 1024 字节 ——
  553. // off += sz 之后整行字段静默错位, 且错得"很像正常数据" (字段都有值, 只是全错位)。
  554. // 返回 0 表示类型未注册或参数非法: 调用方必须放弃解析该行, 不能当真值用。
  555. func fSzWithParam(t byte, param byte) int {
  556. return int(types.FieldSize(types.FieldType(t), param))
  557. }
  558. func tNm(t byte) string {
  559. switch t {
  560. case 1:
  561. return "Time"
  562. case 2:
  563. return "Primary"
  564. case 3:
  565. return "String"
  566. case 4:
  567. return "Compose"
  568. case 5:
  569. return "BTree"
  570. case 6:
  571. return "Hash"
  572. default:
  573. return "?"
  574. }
  575. }
  576. // fName 列类型的可读名。同样投影自 core 的 typeRegistry。
  577. //
  578. // 名字一律投影自 types 注册表 (uint8/uint16/…/string/dynamic/float64/int8/…/int64),
  579. // 因此 core 增删类型时这里自动跟随 —— 不需要再维护第二份会漂移的名单。
  580. func fName(t byte) string {
  581. if n := types.TypeNameOf(types.FieldType(t)); n != "" {
  582. return n
  583. }
  584. return "?"
  585. }
  586. // fLabel 类型徽章的展示文本 (MFInfo.Label)。
  587. //
  588. // 【为什么字符串列要改写】定长字符串列的行内宽度不是类型自带的, 而是靠 Flag 低 5 位的
  589. // 【宽度码】声明 (见 types.FieldSize)。于是同一个 String 类型下会出现"真实行宽 1024 字节"
  590. // 的列 —— 列表里只写类型名, 看起来就是默认的 256 字节, 与盘上差了 4 倍, 而"字符串大小"
  591. // 这件事恰恰是看 schema 时最想知道的。所以宽度码不为 0 的字符串列显示成 string(1024B)。
  592. //
  593. // 宽度码为 0 (沿用类型默认宽度 256) 时只显示类型名 string。
  594. //
  595. // decimal 显示 scale (与改动前一致); 其余类型就是类型名; 未注册类型给 "?"。
  596. func fLabel(t byte, param byte, sz int) string {
  597. name := fName(t)
  598. if name == "?" {
  599. return unknownFieldValue
  600. }
  601. ft := types.FieldType(t)
  602. switch {
  603. case ft == types.FieldType_Decimal:
  604. if param > 0 {
  605. return fmt.Sprintf("%s(%d)", name, param)
  606. }
  607. case types.IsStringType(ft) && param > 0:
  608. return fmt.Sprintf("string(%dB)", sz)
  609. }
  610. return name
  611. }
  612. // fTip 类型徽章的悬浮说明 (MFInfo.Tip): 把"类型档 + 列级参数 + 行内宽度"三件事一次讲清。
  613. //
  614. // 徽章上放不下这些, 而它们正是排查行宽问题时要对照的东西 —— 尤其是字符串列:
  615. // 盘上只存宽度码, 声明值本身【没有保留】, 因此这里只能给"向上取档后的宽度",
  616. // 这一点必须在提示里说破, 否则会有人拿它当声明值对账。
  617. func fTip(t byte, param byte, sz int) string {
  618. name := fName(t)
  619. if name == "?" {
  620. return fmt.Sprintf("未注册的类型 %d: 行内宽度算不出来, 本行无法解码", t)
  621. }
  622. ft := types.FieldType(t)
  623. switch {
  624. case types.IsStringType(ft):
  625. tip := fmt.Sprintf("定长字符串列: 类型档 %s, 行内宽度 %d 字节", name, sz)
  626. if param > 0 {
  627. tip += fmt.Sprintf(" (宽度码 %d; 声明容量向上取档后的宽度, 每行按它付满)", param)
  628. }
  629. return tip
  630. case ft == types.FieldType_Decimal:
  631. return fmt.Sprintf("定点小数: scale=%d, 行内宽度 %d 字节", param, sz)
  632. case sz <= 0:
  633. return fmt.Sprintf("类型 %s: 行内宽度算不出来", name)
  634. }
  635. return fmt.Sprintf("%s: 行内宽度 %d 字节", name, sz)
  636. }
  637. // ===== Read DataPage with schema version awareness =====
  638. // rDP reads a DataPage (sequential row layout, no directory)
  639. func rDP(pid uint32) *DP {
  640. b, e := rp(pid)
  641. if e != nil {
  642. return nil
  643. }
  644. if b[0] != PG_DATA {
  645. return nil
  646. }
  647. cn := binary.LittleEndian.Uint32(b[PHDR : PHDR+4])
  648. mn := binary.BigEndian.Uint64(b[PHDR+4 : PHDR+12])
  649. mx := binary.BigEndian.Uint64(b[PHDR+12 : PHDR+20])
  650. us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100
  651. var rs []DR
  652. off := DP_HDR
  653. for i := uint32(0); i < cn && off+4 <= len(b); i++ {
  654. // 长度字段最高位 (0x80000000) 为软删除标志, 必须屏蔽后再取实际长度
  655. rawLen := binary.LittleEndian.Uint32(b[off : off+4])
  656. rl := int(rawLen &^ 0x80000000)
  657. deleted := rawLen&0x80000000 != 0
  658. off += 4
  659. if off+rl > len(b) || rl <= 0 {
  660. break
  661. }
  662. storeRow := b[off : off+rl]
  663. off += rl
  664. if deleted {
  665. continue
  666. }
  667. // storeRow = [pkID(8B BE)] + rowData
  668. var pkID uint64
  669. var rowData []byte
  670. if len(storeRow) >= 8 {
  671. pkID = binary.BigEndian.Uint64(storeRow[:8])
  672. rowData = storeRow[8:]
  673. } else {
  674. rowData = storeRow
  675. }
  676. schemaV := byte(0)
  677. if len(rowData) > 0 {
  678. schemaV = rowData[0]
  679. }
  680. hx := fmt.Sprintf("%x", storeRow[:min(len(storeRow), 64)])
  681. if len(storeRow) > 64 {
  682. hx += "..."
  683. }
  684. decoded := decodeRowData(rowData, schemaV)
  685. rs = append(rs, DR{int(i), pkID, schemaV, hx, decoded})
  686. }
  687. return &DP{pid, binary.LittleEndian.Uint32(b[PH_NEXT : PH_NEXT+4]), binary.LittleEndian.Uint32(b[PH_PREV : PH_PREV+4]),
  688. cn, mx, mn, us, rs}
  689. }
  690. // decodeRowData 尝试解码行数据 (显示 schemaVer + 各字段值)
  691. //
  692. // 行布局 v9: [schemaVer 1B][列0 W0 B][列结束偏移表 2N B][列1..N-1 内容][NULL 位图 ⌈n/8⌉B]
  693. // (位图长度按该行自己的版本算, n = 该版本 Indices 长度)。两处必须照做, 否则整行错位:
  694. // - 列位置一律由【行内偏移表】给出: 列 0 恒占 W0 字节 (档位宽度, 不足补零), 偏移表基址
  695. // = 1+W0, 表项 0 = 变长内容区起点 (= 1+W0+2N), 表项 i(i≥1) = 列 i 结束偏移, 列 i(i≥1)
  696. // 起点 = 表项 i-1, 位图起点 = 表末项。v9 字符串列按实际长度存储, 再沿用"累加档位宽度"
  697. // 定位会越算越偏 (表现为整行错列)。
  698. // - 列 0 恒占 W0 字节也让时序键固定在绝对偏移 9 (读取侧写死的偏移, W0 == 8 时成立)。
  699. // - 已删除列在行内【仍占位】(DropField 不新增版本, 该列不会从 Indices 消失), 所以判定
  700. // Dropped 只能跳过"显示", 绝不能影响列定位 —— 偏移表项照样按 i 取。
  701. func decodeRowData(rowData []byte, schemaV byte) string {
  702. if len(rowData) < 1 {
  703. return ""
  704. }
  705. parts := []string{fmt.Sprintf("v%d", schemaV)}
  706. if len(rowData) < 2 {
  707. return strings.Join(parts, " ")
  708. }
  709. for _, t := range tabs {
  710. sv := findVersion(t, schemaV)
  711. if sv == nil {
  712. continue
  713. }
  714. rowN := len(sv.Indices)
  715. if rowN == 0 {
  716. break
  717. }
  718. if sv.Indices[0] >= len(t.MasterFields) || t.MasterFields[sv.Indices[0]].Sz <= 0 {
  719. break
  720. }
  721. tblBase := 1 + t.MasterFields[sv.Indices[0]].Sz // 偏移表基址 == 列 0 结束偏移
  722. header := tblBase + 2*rowN // 变长内容区起点 == 表项 0
  723. if header > len(rowData) {
  724. break
  725. }
  726. if int(binary.BigEndian.Uint16(rowData[tblBase:tblBase+2])) != header {
  727. break // 表项 0 不是变长内容区起点: 布局不可信, 不猜
  728. }
  729. bitmapLen := (rowN + 7) / 8
  730. prev := header
  731. bad := false
  732. for i := 0; i < rowN; i++ {
  733. off := int(binary.BigEndian.Uint16(rowData[tblBase+2*i:tblBase+2*i+2])) & rowVarOffMask
  734. if off < prev || off > len(rowData) {
  735. bad = true
  736. break
  737. }
  738. prev = off
  739. }
  740. if bad || prev+bitmapLen > len(rowData) {
  741. break
  742. }
  743. bitmap := rowData[prev : prev+bitmapLen]
  744. for i, idx := range sv.Indices {
  745. if idx >= len(t.MasterFields) {
  746. break
  747. }
  748. mf := t.MasterFields[idx]
  749. start := 1
  750. if i > 0 {
  751. start = int(binary.BigEndian.Uint16(rowData[tblBase+2*(i-1):tblBase+2*i])) & rowVarOffMask
  752. }
  753. end := tblBase // 列 0 结束恒为 1+W0 == 表基址
  754. if i > 0 {
  755. end = int(binary.BigEndian.Uint16(rowData[tblBase+2*i:tblBase+2*i+2])) & rowVarOffMask
  756. }
  757. if end < start || end > len(rowData) {
  758. break
  759. }
  760. if !mf.Dropped { // 已删除列仍占位, 只是不显示
  761. if bitmap[i>>3]&(1<<uint(i&7)) != 0 {
  762. parts = append(parts, mf.Name+"=NULL")
  763. } else {
  764. // 行内可能是实际长度的短值 (字符串列), 补零回档位宽度再解, 与引擎同源。
  765. parts = append(parts, mf.Name+"="+decodeFieldValue(padToWidth(rowData[start:end], mf.Sz), mf.Type))
  766. }
  767. }
  768. }
  769. return strings.Join(parts, ", ")
  770. }
  771. return strings.Join(parts, " ")
  772. }
  773. func findVersion(t TInfo, ver byte) *VInfo {
  774. for i := range t.Versions {
  775. if t.Versions[i].Ver == ver {
  776. return &t.Versions[i]
  777. }
  778. }
  779. return nil
  780. }
  781. // unknownFieldValue 未同步列型的显式哨兵。
  782. //
  783. // 为什么不用"当作定长字符串打印"兜底: 那样输出看着像正常内容, 实际是错的 ——
  784. // 而 analyzer 最怕的正是这个 (页照常渲染, 数据全错)。用哨兵才能让
  785. // TestDecodeCoversEveryRegisteredType 当场发现"core 加了新类型而这里没跟"。
  786. const unknownFieldValue = "?"
  787. // decodeFieldValue 把行内字节解成可读文本。约定: len(buf) == 该列的 fSzWithParam(typ, param)。
  788. //
  789. // 与 DB/core 的 DecodeValue 是【两份独立实现】, 这是刻意的: analyzer 是"第二读取器",
  790. // 存在的意义就是独立验一遍 core 读出来的东西 —— 若共用同一份解码代码, core 解错时
  791. // analyzer 会跟着一起错, 校验价值就没了。所以这里只借用"布局事实" (宽度 fSz / 名字
  792. // fName, 投影自 core 的 typeRegistry), 值语义自己实现一遍。
  793. //
  794. // 每个已注册列型都必须有明确分支; 落到 unknownFieldValue 说明漏同步了。
  795. func decodeFieldValue(buf []byte, typ byte) string {
  796. // 一律用 core 导出的类型常量, 不再写字面量序号 —— 否则 core 一旦重排类型值,
  797. // 这里的 case 会静默错位 (把 1 字节当 2 字节解码, 整段乱掉)。判据同源于 types 注册表。
  798. switch types.FieldType(typ) {
  799. // ===== 1 字节 =====
  800. case types.FieldType_Bool:
  801. // 盘上只允许 0x00 / 0x01 (写入侧已规范化, 不会存下 2)
  802. if buf[0] != 0 {
  803. return "true"
  804. }
  805. return "false"
  806. case types.FieldType_Uint8:
  807. return fmt.Sprintf("%d", buf[0])
  808. case types.FieldType_Int8:
  809. // 有符号列盘上是保序编码 (符号位翻转), 读回来要先翻回去
  810. return fmt.Sprintf("%d", int8(buf[0]^0x80))
  811. // ===== 2 字节 =====
  812. case types.FieldType_Uint16:
  813. return fmt.Sprintf("%d", binary.BigEndian.Uint16(buf))
  814. case types.FieldType_Int16:
  815. return fmt.Sprintf("%d", int16(binary.BigEndian.Uint16(buf)^0x8000))
  816. case types.FieldType_Year:
  817. // 无符号大端 uint16 (无保序编码; 解码载体是 int16)
  818. return fmt.Sprintf("%d", binary.BigEndian.Uint16(buf))
  819. // ===== 4 字节 =====
  820. case types.FieldType_Uint32:
  821. return fmt.Sprintf("%d", binary.BigEndian.Uint32(buf))
  822. case types.FieldType_Int32:
  823. return fmt.Sprintf("%d", int32(binary.BigEndian.Uint32(buf)^0x80000000))
  824. case types.FieldType_Float32:
  825. // 保序编码 (非负翻符号位, 负数整体取反; 见 types.Float32ToSortKey)
  826. u := binary.BigEndian.Uint32(buf)
  827. if u&(1<<31) != 0 {
  828. u ^= 1 << 31
  829. } else {
  830. u = ^u
  831. }
  832. return strconv.FormatFloat(float64(math.Float32frombits(u)), 'g', -1, 32)
  833. // ===== 8 字节 =====
  834. case types.FieldType_Uint64:
  835. return fmt.Sprintf("%d", binary.BigEndian.Uint64(buf))
  836. case types.FieldType_Int64:
  837. return fmt.Sprintf("%d", int64(binary.BigEndian.Uint64(buf)^0x8000000000000000))
  838. case types.FieldType_Float64:
  839. // 保序编码: 非负翻符号位, 负数整体取反 (见 types.Float64ToSortKey)
  840. u := binary.BigEndian.Uint64(buf)
  841. if u&(1<<63) != 0 {
  842. u ^= 1 << 63
  843. } else {
  844. u = ^u
  845. }
  846. return strconv.FormatFloat(math.Float64frombits(u), 'g', -1, 64)
  847. // ===== 16 字节 =====
  848. case types.FieldType_Int128:
  849. return decodeInt128(buf).String()
  850. case types.FieldType_UUID:
  851. // 16 字节原样二进制, 显示成标准 8-4-4-4-12
  852. h := fmt.Sprintf("%x", buf[:16])
  853. return h[:8] + "-" + h[8:12] + "-" + h[12:16] + "-" + h[16:20] + "-" + h[20:]
  854. // ===== 18 字节: 定点小数 [16B 系数保序编码][2B scale 大端], 值 = 系数 / 10^scale =====
  855. case types.FieldType_Decimal:
  856. return decimalText(decodeInt128(buf), int(binary.BigEndian.Uint16(buf[16:])))
  857. // ===== 定长字符串 (宽度由 Flag 低 5 位宽度码决定, 与这里无关) =====
  858. case types.FieldType_String:
  859. return bytesFieldText(buf)
  860. // ===== 动态列: 行内只有 6 字节堆引用 [PageID uint32 大端][Offset uint16 大端] =====
  861. //
  862. // 以前动态落到 default 分支, 把页号和偏移当字符串打印出来 —— 真正想看的
  863. // "内容在哪一页" 反而看不见。
  864. case types.FieldType_Dynamicbyte:
  865. pid := binary.BigEndian.Uint32(buf)
  866. if pid == 0 {
  867. return "(empty)" // PageID == 0 表示空值
  868. }
  869. return fmt.Sprintf("(heap page=%d off=%d)", pid, binary.BigEndian.Uint16(buf[4:]))
  870. // ===== blob 列 (v10): 行内 8 字节链引用 [TotalLen uint32 大端][FirstPageID uint32 大端] =====
  871. //
  872. // 与动态列长得像但读法完全不同: 内容不在堆页上的某一块, 而在一条可跨页的链上,
  873. // 所以这里报"链首页 + 内容长度"; 顺着 page=%d 用 tbl.BlobRead(ref) 能整块读出来。
  874. case types.FieldType_Blob:
  875. total := binary.BigEndian.Uint32(buf)
  876. pid := binary.BigEndian.Uint32(buf[4:])
  877. if total == 0 && pid == 0 {
  878. return "(empty)" // (0, 0) 表示空 blob
  879. }
  880. return fmt.Sprintf("(blob page=%d len=%d)", pid, total)
  881. default:
  882. return unknownFieldValue
  883. }
  884. }
  885. // bytesFieldText 定长字节串的文本形式: 去掉尾部补零, 全空则显示 (empty)。
  886. func bytesFieldText(buf []byte) string {
  887. s := strings.TrimRight(string(buf), "\x00")
  888. if len(s) == 0 {
  889. return "(empty)"
  890. }
  891. return s
  892. }
  893. // decodeInt128 还原 core 的 int128ToSortKeyBytes: [16B 补码大端, 最高位已翻转]。
  894. //
  895. // 判符号必须在【翻回来之前】看: 盘上最高位为 0 代表原值是负数 (编码时对最高位异或了
  896. // 0x80); 顺序判反会把全部非负数读成负数。
  897. func decodeInt128(b []byte) *big.Int {
  898. var raw [16]byte
  899. copy(raw[:], b)
  900. neg := raw[0]&0x80 == 0
  901. raw[0] ^= 0x80
  902. v := new(big.Int).SetBytes(raw[:])
  903. if neg {
  904. v.Sub(v, new(big.Int).Lsh(big.NewInt(1), 128))
  905. }
  906. return v
  907. }
  908. // decimalText 把 [系数, scale] 渲染成小数字面量 (值 = 系数 / 10^scale)。
  909. //
  910. // 必须按 scale 补足位数: 系数 150 + scale 2 是 "1.50", 而系数 5 + scale 2 是 "0.05"
  911. // —— 少了补零就会读成 "5", 差 100 倍。
  912. func decimalText(coef *big.Int, scale int) string {
  913. s := coef.String()
  914. if scale <= 0 {
  915. return s
  916. }
  917. sign := ""
  918. if strings.HasPrefix(s, "-") {
  919. sign, s = "-", s[1:]
  920. }
  921. if len(s) <= scale {
  922. s = strings.Repeat("0", scale-len(s)+1) + s
  923. }
  924. return sign + s[:len(s)-scale] + "." + s[len(s)-scale:]
  925. }
  926. // ===== Index page reading =====
  927. func rIP(pid uint32, it byte) *IP {
  928. b, e := rp(pid)
  929. if e != nil {
  930. return nil
  931. }
  932. if b[0] != PG_IDATA {
  933. return nil
  934. }
  935. lf := b[28] == 1
  936. cn := binary.LittleEndian.Uint32(b[24:28])
  937. el := int(binary.LittleEndian.Uint16(b[22:24]))
  938. if el <= 4 {
  939. el = 12
  940. }
  941. ents := pEE(b, int(cn), el, it, lf)
  942. us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100
  943. return &IP{pid, binary.LittleEndian.Uint32(b[PH_NEXT : PH_NEXT+4]), binary.LittleEndian.Uint32(b[PH_PREV : PH_PREV+4]),
  944. lf, cn, us, ents, it}
  945. }
  946. func pEE(b []byte, cn, el int, it byte, lf bool) []IE {
  947. var ents []IE
  948. for i := 0; i < cn; i++ {
  949. o := IDX_HDR + i*el
  950. if o+el > len(b) {
  951. break
  952. }
  953. entry := b[o : o+el]
  954. var ie IE
  955. switch it {
  956. case 2:
  957. ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8]))
  958. ie.P = binary.BigEndian.Uint32(entry[8:12])
  959. case 1:
  960. ie.K = fmt.Sprintf("%d ~ %d", binary.BigEndian.Uint64(entry[:8]), binary.BigEndian.Uint64(entry[8:16]))
  961. ie.P = binary.BigEndian.Uint32(entry[16:20])
  962. case 3:
  963. ie.K = strings.TrimRight(string(entry[:16]), "\x00")
  964. ie.DP = binary.BigEndian.Uint64(entry[16:24])
  965. if lf {
  966. ie.P = binary.BigEndian.Uint32(entry[20:24])
  967. }
  968. case 4:
  969. nk := (el - 8) / 8
  970. parts := make([]string, nk)
  971. for j := 0; j < nk; j++ {
  972. parts[j] = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[j*8:]))
  973. }
  974. ie.K = strings.Join(parts, ", ")
  975. ie.DP = binary.BigEndian.Uint64(entry[nk*8:])
  976. if lf {
  977. ie.P = binary.BigEndian.Uint32(entry[el-4:])
  978. }
  979. case 5:
  980. if lf {
  981. kl := el - 8
  982. if kl == 8 {
  983. ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8]))
  984. } else {
  985. ie.K = fmt.Sprintf("%x", entry[:kl])
  986. }
  987. ie.DP = binary.BigEndian.Uint64(entry[kl:])
  988. ie.P = binary.BigEndian.Uint32(entry[el-4:])
  989. } else {
  990. kl := el - 4
  991. if kl == 8 {
  992. ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8]))
  993. } else {
  994. ie.K = fmt.Sprintf("%x", entry[:kl])
  995. }
  996. ie.P = binary.BigEndian.Uint32(entry[el-4:])
  997. }
  998. default:
  999. ie.K = fmt.Sprintf("%x", entry[:el-4])
  1000. ie.P = binary.BigEndian.Uint32(entry[el-4:])
  1001. }
  1002. ents = append(ents, ie)
  1003. }
  1004. return ents
  1005. }
  1006. func rBT(pid uint32, it byte) *IP { return rIP(pid, it) }
  1007. // ===== Header + Heap + Free =====
  1008. func rHdrInfo() *HdrInfo {
  1009. f, e := dbFile()
  1010. if e != nil {
  1011. return nil
  1012. }
  1013. b := make([]byte, HDRSZ)
  1014. f.ReadAt(b, 0)
  1015. // v11 起 HeaderPage 重排 (标量 8 字节对齐、零填充; 布局见 DB/core/header_page.go):
  1016. // 0 Magic | 4 Version | 8 PageSize | 12 NextFreePage
  1017. // 16 CreateTime | 24 ModifyTime | 32 NextTxnID | 40 GlobalFlags
  1018. // 48 TablePage | 52 TableCount | 56 FreePageStart | 60 FreePageEnd
  1019. // 64 FreePageCount | 68 FirstWALPage | 72 LastWALPage
  1020. // 76 ExtConfig[256] | 332 Comment[3764] (占满整页余下空间)
  1021. magic := string(b[:4])
  1022. ver := binary.LittleEndian.Uint32(b[4:8])
  1023. pgsz := binary.LittleEndian.Uint32(b[8:12])
  1024. nf := binary.LittleEndian.Uint32(b[12:16])
  1025. ct := int64(binary.LittleEndian.Uint64(b[16:24]))
  1026. mt := int64(binary.LittleEndian.Uint64(b[24:32]))
  1027. txn := binary.LittleEndian.Uint64(b[32:40])
  1028. gf := binary.LittleEndian.Uint64(b[40:48])
  1029. tp := binary.LittleEndian.Uint32(b[48:52])
  1030. tc := binary.LittleEndian.Uint32(b[52:56])
  1031. fs := binary.LittleEndian.Uint32(b[56:60])
  1032. fe := binary.LittleEndian.Uint32(b[HDR_FREE_END : HDR_FREE_END+4])
  1033. fc := binary.LittleEndian.Uint32(b[64:68])
  1034. wal0 := binary.LittleEndian.Uint32(b[68:72])
  1035. wal1 := binary.LittleEndian.Uint32(b[72:76])
  1036. cm := strings.TrimRight(string(b[332:844]), "\x00")
  1037. fcW := rFreeChain()
  1038. total := totalPages()
  1039. h := &HdrInfo{
  1040. Magic: magic, Version: ver, PageSize: pgsz,
  1041. NextFree: nf, TablePage: tp, TableCount: tc,
  1042. CreateTime: time.Unix(0, ct).Format("2006-01-02 15:04:05"),
  1043. ModifyTime: time.Unix(0, mt).Format("2006-01-02 15:04:05"),
  1044. FreeStart: fs, FreeEnd: fe, FreeCount: fc,
  1045. GlobalFlags: gf, WALFirst: wal0, WALLast: wal1, TxnID: txn, Comment: cm,
  1046. FreeWalked: fcW.Total, Pages: total,
  1047. FreeVerdict: fcW.Verdict, FreeDetail: fcW.Detail,
  1048. }
  1049. if nf > 0 {
  1050. h.Allocated = int(nf) - 1
  1051. }
  1052. if h.Allocated > total {
  1053. h.Allocated = total
  1054. }
  1055. if h.NeverUsed = total - h.Allocated; h.NeverUsed < 0 {
  1056. h.NeverUsed = 0
  1057. }
  1058. return h
  1059. }
  1060. func rHeapChain(hp0 uint32) *HeapChain {
  1061. var pages []HeapPageInfo
  1062. for pid := hp0; pid != 0; {
  1063. b, e := rp(pid)
  1064. if e != nil || b[0] != PG_HEAP {
  1065. break
  1066. }
  1067. nx := binary.LittleEndian.Uint32(b[PH_NEXT : PH_NEXT+4])
  1068. pv := binary.LittleEndian.Uint32(b[PH_PREV : PH_PREV+4])
  1069. use := binary.LittleEndian.Uint32(b[PH_USE : PH_USE+4])
  1070. us := float64(use) / float64(PGSZ) * 100
  1071. ref := binary.LittleEndian.Uint16(b[PH_REF : PH_REF+2])
  1072. pages = append(pages, HeapPageInfo{pid, nx, pv, use, us, ref})
  1073. if nx == pid || nx == 0 {
  1074. break
  1075. }
  1076. pid = nx
  1077. }
  1078. return &HeapChain{pages, len(pages)}
  1079. }
  1080. // rFreePages 空闲链 —— 直接用审计里那份带自检的版本。
  1081. // 旧实现读的是偏移 0 的大端值, 而空闲链指针其实写在页头 NextPage (偏移 12);
  1082. // 偏移 0 是 Type(空闲页=0) 那一格, 恒等于 0, 所以链永远只走出第 1 页。
  1083. func rFreePages() *FreeChainInfo { return rFreeChain() }
  1084. // ===== Page visualization =====
  1085. type PageItem struct {
  1086. ID uint32 `json:"id"`
  1087. Type int `json:"t"`
  1088. TS string `json:"ts"`
  1089. Us float64 `json:"us"`
  1090. }
  1091. type PagesResult struct {
  1092. Pages []PageItem `json:"pages"`
  1093. Total int `json:"total"`
  1094. Counts map[string]int `json:"counts"`
  1095. Allocated int `json:"allocated"`
  1096. NeverUsed int `json:"neverUsed"`
  1097. Role map[string]string `json:"role"`
  1098. Owner map[string]string `json:"owner"`
  1099. }
  1100. // rPages 页映射的**一个分片** —— 类型/使用率取自缓存的紧凑索引 (pagemap.go),
  1101. // 角色/属主只为这一段页查询。
  1102. //
  1103. // 旧实现每次调用都要全量重算 19.5 万页, 并把整张页表 + 两张 19.5 万键的归属
  1104. // map 一次性序列化 (单次响应几十 MB), 且被概览/堆链/空闲页三个视图各调一遍 ——
  1105. // 前端必然卡死。新的页图走 /api/pagemap 分块拉取, 这里只保留分页语义。
  1106. func rPages(start, count int) *PagesResult {
  1107. idx := pagemapCached()
  1108. res := &PagesResult{
  1109. Total: idx.total, Counts: idx.counts, Allocated: idx.allocated,
  1110. NeverUsed: idx.neverUsed, Role: map[string]string{}, Owner: map[string]string{},
  1111. }
  1112. if idx.total <= 0 {
  1113. return res
  1114. }
  1115. if start < 1 {
  1116. start = 1
  1117. }
  1118. if count <= 0 || count > pmChunkMax {
  1119. count = pmChunkDefault
  1120. }
  1121. if start > idx.total {
  1122. start = idx.total + 1
  1123. }
  1124. end := start + count
  1125. if end > idx.total+1 {
  1126. end = idx.total + 1
  1127. }
  1128. w := peekWalk()
  1129. for pid := start; pid < end; pid++ {
  1130. pt, tn := pmKind(int(idx.typ[pid]))
  1131. res.Pages = append(res.Pages, PageItem{uint32(pid), pt, tn, float64(idx.usg[pid])})
  1132. if w == nil {
  1133. continue
  1134. }
  1135. k := strconv.Itoa(pid)
  1136. if v, ok := w.role[uint32(pid)]; ok {
  1137. res.Role[k] = v
  1138. }
  1139. if v, ok := w.owner[uint32(pid)]; ok {
  1140. res.Owner[k] = v
  1141. }
  1142. }
  1143. return res
  1144. }
  1145. // pagesH 分页返回页映射: ?start=<首页号>&count=<页数> (默认第一块)。
  1146. func pagesH(w http.ResponseWriter, r *http.Request) {
  1147. if fp == "" {
  1148. jw(w, map[string]any{"e": "no db"})
  1149. return
  1150. }
  1151. start, _ := strconv.Atoi(r.URL.Query().Get("start"))
  1152. count, _ := strconv.Atoi(r.URL.Query().Get("count"))
  1153. jw(w, rPages(start, count))
  1154. }
  1155. // findPkH 通过主键 B+ 树查找 PKID 对应的 DataPage
  1156. func findPkH(w http.ResponseWriter, r *http.Request) {
  1157. if fp == "" {
  1158. jw(w, map[string]any{"e": "no db"})
  1159. return
  1160. }
  1161. pkStr := r.URL.Query().Get("pk")
  1162. rootStr := r.URL.Query().Get("root")
  1163. var pk, root uint32
  1164. fmt.Sscanf(pkStr, "%d", &pk)
  1165. fmt.Sscanf(rootStr, "%d", &root)
  1166. if root == 0 {
  1167. jw(w, map[string]any{"e": "no root"})
  1168. return
  1169. }
  1170. dp := findDPByPK(root, uint64(pk))
  1171. if dp == 0 {
  1172. jw(w, map[string]any{"e": "not found"})
  1173. return
  1174. }
  1175. jw(w, map[string]any{"dp": dp, "pk": pk})
  1176. }
  1177. // findDPByPK 在主键 B+ 树中查找 PKID 对应的 DataPage ID
  1178. func findDPByPK(rootPID uint32, targetPK uint64) uint32 {
  1179. cur := rootPID
  1180. for {
  1181. b, e := rp(cur)
  1182. if e != nil || b[0] != PG_IDATA {
  1183. return 0
  1184. }
  1185. lf := b[28] == 1
  1186. cn := int(binary.LittleEndian.Uint32(b[24:28]))
  1187. el := int(binary.LittleEndian.Uint16(b[22:24]))
  1188. if el <= 4 {
  1189. el = 12
  1190. }
  1191. if lf {
  1192. // 叶子节点: 查找匹配的 PK
  1193. for i := 0; i < cn; i++ {
  1194. o := IDX_HDR + i*el
  1195. if o+el > len(b) {
  1196. break
  1197. }
  1198. key := binary.BigEndian.Uint64(b[o : o+8])
  1199. if key == targetPK {
  1200. return binary.BigEndian.Uint32(b[o+8 : o+12])
  1201. }
  1202. }
  1203. return 0
  1204. }
  1205. // 内部节点: 二分查找最后一个 key <= targetPK
  1206. lo, hi := 0, cn-1
  1207. pos := 0
  1208. for lo <= hi {
  1209. mid := (lo + hi) / 2
  1210. o := IDX_HDR + mid*el
  1211. if o+8 > len(b) {
  1212. break
  1213. }
  1214. key := binary.BigEndian.Uint64(b[o : o+8])
  1215. if key <= targetPK {
  1216. pos = mid
  1217. lo = mid + 1
  1218. } else {
  1219. hi = mid - 1
  1220. }
  1221. }
  1222. o := IDX_HDR + pos*el
  1223. childPID := binary.BigEndian.Uint32(b[o+el-4 : o+el])
  1224. if childPID == 0 {
  1225. return 0
  1226. }
  1227. cur = childPID
  1228. }
  1229. }
  1230. // ===== Utility =====
  1231. func min(a, b int) int {
  1232. if a < b {
  1233. return a
  1234. }
  1235. return b
  1236. }
  1237. func fB(n int64) string {
  1238. if n < 1024 {
  1239. return fmt.Sprintf("%d B", n)
  1240. }
  1241. if n < 1048576 {
  1242. return fmt.Sprintf("%.1f KB", float64(n)/1024)
  1243. }
  1244. if n < 1073741824 {
  1245. return fmt.Sprintf("%.2f MB", float64(n)/1048576)
  1246. }
  1247. return fmt.Sprintf("%.2f GB", float64(n)/1073741824)
  1248. }
  1249. func fE(p string) bool { _, e := os.Stat(p); return e == nil }
  1250. func jw(w http.ResponseWriter, v any) {
  1251. w.Header().Set("Content-Type", "application/json")
  1252. json.NewEncoder(w).Encode(v)
  1253. }
  1254. // ===== HTTP Handlers =====
  1255. func ldH(w http.ResponseWriter, r *http.Request) {
  1256. p := r.URL.Query().Get("path")
  1257. if p == "" {
  1258. p, _ = os.Getwd()
  1259. }
  1260. if !filepath.IsAbs(p) {
  1261. c, _ := os.Getwd()
  1262. p = filepath.Join(c, p)
  1263. }
  1264. es, e := os.ReadDir(p)
  1265. if e != nil {
  1266. jw(w, map[string]any{"err": e.Error()})
  1267. return
  1268. }
  1269. type F struct {
  1270. N string `json:"n"`
  1271. D bool `json:"d"`
  1272. S int64 `json:"s"`
  1273. }
  1274. var ds, fs []F
  1275. for _, x := range es {
  1276. i, _ := x.Info()
  1277. s := int64(0)
  1278. if i != nil {
  1279. s = i.Size()
  1280. }
  1281. if x.IsDir() {
  1282. ds = append(ds, F{x.Name(), true, s})
  1283. } else {
  1284. fs = append(fs, F{x.Name(), false, s})
  1285. }
  1286. }
  1287. jw(w, map[string]any{"p": p, "pp": filepath.Dir(p), "d": ds, "f": fs})
  1288. }
  1289. func openH(w http.ResponseWriter, r *http.Request) {
  1290. if r.Method != "POST" {
  1291. http.Error(w, "no", 405)
  1292. return
  1293. }
  1294. var q struct {
  1295. P string `json:"p"`
  1296. }
  1297. json.NewDecoder(r.Body).Decode(&q)
  1298. if !fE(q.P) {
  1299. c, _ := os.Getwd()
  1300. q2 := filepath.Join(c, q.P)
  1301. if fE(q2) {
  1302. q.P = q2
  1303. } else {
  1304. jw(w, map[string]any{"ok": false, "e": "not found"})
  1305. return
  1306. }
  1307. }
  1308. // 切库: 换掉共享句柄, 作废审计缓存
  1309. if e := useDB(q.P); e != nil {
  1310. jw(w, map[string]any{"ok": false, "e": "open failed: " + e.Error()})
  1311. return
  1312. }
  1313. invalidateAudit()
  1314. mu.Lock()
  1315. fp = q.P
  1316. rHdr()
  1317. tabs = pTabs()
  1318. mu.Unlock()
  1319. fi, _ := os.Stat(fp)
  1320. dbF = fp
  1321. setCacheFP(fingerprint())
  1322. // 全库审计可能很慢 (要扫上千页), 放到后台预热, 让打开请求立即返回。
  1323. // 前端先显示基础信息与"审计中", 就绪后轮询 /api/stats 填充汇总。
  1324. startAuditAsync()
  1325. // 页映射索引同样在后台建好 (排在审计之后), 免得用户点开概览时再等一次全库扫描。
  1326. startPagemapAsync()
  1327. jw(w, map[string]any{"ok": true, "s": st(fi)})
  1328. }
  1329. func st(fi os.FileInfo) map[string]any {
  1330. s := int64(0)
  1331. if fi != nil {
  1332. s = fi.Size()
  1333. }
  1334. m := map[string]any{"f": dbF, "s": s, "fs": fB(s), "pg": totalPages(), "tc": len(tabs)}
  1335. // 概览页要用到审计汇总 (空闲页数 / 堆页数 / 行数 / 总判决 / 孤儿页)。
  1336. // 这里只读已算好的缓存, 不触发计算: 未就绪时返回 auditReady=false,
  1337. // 由前端显示"审计中"并轮询本接口, 避免打开库时长时间卡死。
  1338. //
  1339. // 增量审计: 结构结果先就绪, 索引内容一致性随后补全。总判决里含索引内容,
  1340. // 所以只有索引内容也补全了 (IdxPending=false) 才算真正就绪。
  1341. if a := peekAudit(); a != nil && !a.IdxPending {
  1342. heap, rows := 0, uint64(0)
  1343. for _, t := range a.Tables {
  1344. heap += t.HeapActual
  1345. rows += t.Rows
  1346. }
  1347. m["free"] = a.Free.Total
  1348. m["heap"] = heap
  1349. m["rows"] = rows
  1350. m["verdict"] = a.Verdict
  1351. m["unacc"] = a.Reach.Unaccounted
  1352. m["reserved"] = a.Reserved
  1353. m["never"] = a.NeverUsed
  1354. m["auditReady"] = true
  1355. } else {
  1356. m["auditReady"] = false
  1357. if failed, msg := peekAuditError(); failed {
  1358. m["auditFailed"] = true
  1359. m["auditError"] = msg
  1360. }
  1361. }
  1362. return m
  1363. }
  1364. func stH(w http.ResponseWriter, r *http.Request) {
  1365. if dbF == "" {
  1366. jw(w, map[string]any{"e": "no db"})
  1367. return
  1368. }
  1369. fi, _ := os.Stat(fp)
  1370. jw(w, st(fi))
  1371. }
  1372. // memH 报告 Go 运行时内存与映射区大小, 用来回答"内存到底花在哪"。
  1373. // ?gc=1 强制一次 GC 并归还内存, 便于区分"活对象"与"待回收/未归还"。
  1374. func memH(w http.ResponseWriter, r *http.Request) {
  1375. if r.URL.Query().Get("gc") == "1" {
  1376. runtime.GC()
  1377. debug.FreeOSMemory()
  1378. }
  1379. var m runtime.MemStats
  1380. runtime.ReadMemStats(&m)
  1381. // 库文件大小: 现在按页 ReadAt 读取, 页留在系统文件缓存里 (可回收),
  1382. // 不再整文件映射进进程地址空间, 因此这里不再有 mappedDBSZ 这一项。
  1383. var fileMB int64
  1384. if fi, e := os.Stat(fp); e == nil {
  1385. fileMB = fi.Size() >> 20
  1386. }
  1387. jw(w, map[string]any{
  1388. "heapAllocMB": m.HeapAlloc >> 20, // 活对象
  1389. "heapInuseMB": m.HeapInuse >> 20, // 已用 span
  1390. "heapIdleMB": m.HeapIdle >> 20, // 空闲 span (可归还)
  1391. "heapSysMB": m.HeapSys >> 20, // 向 OS 要的堆
  1392. "stackMB": m.StackInuse >> 20, // goroutine 栈
  1393. "otherSysMB": m.OtherSys >> 20,
  1394. "sysMB": m.Sys >> 20, // Go 全部向 OS 要的内存
  1395. "dbFileMB": fileMB, // 库文件大小 (按页 ReadAt, 不进进程工作集)
  1396. "numGC": m.NumGC,
  1397. "goroutines": runtime.NumGoroutine(),
  1398. })
  1399. }
  1400. func taH(w http.ResponseWriter, r *http.Request) { jw(w, tabs) }
  1401. func tdH(w http.ResponseWriter, r *http.Request) {
  1402. n := r.URL.Path[len("/api/table/"):]
  1403. for _, t := range tabs {
  1404. if t.Name == n {
  1405. jw(w, t)
  1406. return
  1407. }
  1408. }
  1409. jw(w, map[string]any{"e": "not found"})
  1410. }
  1411. func dpH(w http.ResponseWriter, r *http.Request) {
  1412. var pid uint32
  1413. fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid)
  1414. d := rDP(pid)
  1415. if d == nil {
  1416. jw(w, map[string]any{"e": "err"})
  1417. return
  1418. }
  1419. jw(w, d)
  1420. }
  1421. func ipH(w http.ResponseWriter, r *http.Request) {
  1422. var pid uint32
  1423. fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid)
  1424. it, _ := strconv.Atoi(r.URL.Query().Get("t"))
  1425. d := rIP(pid, byte(it))
  1426. if d == nil {
  1427. jw(w, map[string]any{"e": "err"})
  1428. return
  1429. }
  1430. jw(w, d)
  1431. }
  1432. func btH(w http.ResponseWriter, r *http.Request) {
  1433. var pid uint32
  1434. fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid)
  1435. it, _ := strconv.Atoi(r.URL.Query().Get("t"))
  1436. d := rBT(pid, byte(it))
  1437. if d == nil {
  1438. jw(w, map[string]any{"e": "err"})
  1439. return
  1440. }
  1441. jw(w, d)
  1442. }
  1443. func hdrH(w http.ResponseWriter, r *http.Request) {
  1444. d := rHdrInfo()
  1445. if d == nil {
  1446. jw(w, map[string]any{"e": "err"})
  1447. return
  1448. }
  1449. jw(w, d)
  1450. }
  1451. func heapH(w http.ResponseWriter, r *http.Request) {
  1452. var hp0 uint32
  1453. fmt.Sscanf(r.URL.Query().Get("id"), "%d", &hp0)
  1454. jw(w, rHeapChain(hp0))
  1455. }
  1456. func freeH(w http.ResponseWriter, r *http.Request) { jw(w, rFreePages()) }
  1457. // ===== 审计 / 归属 / 单页检查 =====
  1458. // auditH 全库审计: 页账 + 空闲链自检 + 每表堆链交叉校验 + 未归属页。
  1459. func auditH(w http.ResponseWriter, r *http.Request) {
  1460. if dbF == "" {
  1461. jw(w, map[string]any{"e": "no db"})
  1462. return
  1463. }
  1464. jw(w, auditCached())
  1465. }
  1466. // pageArgs 解析分页参数: offset 默认 0, limit 默认 100 (上限 500)。
  1467. func pageArgs(r *http.Request) (int, int) {
  1468. off, _ := strconv.Atoi(r.URL.Query().Get("offset"))
  1469. lim, _ := strconv.Atoi(r.URL.Query().Get("limit"))
  1470. if off < 0 {
  1471. off = 0
  1472. }
  1473. if lim <= 0 || lim > 500 {
  1474. lim = 100
  1475. }
  1476. return off, lim
  1477. }
  1478. // auditIssuesH 分页取索引问题明细 —— 从落盘缓存库里按页取, 不整份读进内存。
  1479. func auditIssuesH(w http.ResponseWriter, r *http.Request) {
  1480. off, lim := pageArgs(r)
  1481. rows, total, err := cachePageIssues(off, lim)
  1482. if err != nil {
  1483. jw(w, map[string]any{"e": err.Error()})
  1484. return
  1485. }
  1486. jw(w, map[string]any{"rows": rows, "total": total, "offset": off, "limit": lim})
  1487. }
  1488. // auditUnaccH 分页取孤儿页明细 —— 同上, 从缓存库按页取。
  1489. func auditUnaccH(w http.ResponseWriter, r *http.Request) {
  1490. off, lim := pageArgs(r)
  1491. rows, total, err := cachePageUnacc(off, lim)
  1492. if err != nil {
  1493. jw(w, map[string]any{"e": err.Error()})
  1494. return
  1495. }
  1496. jw(w, map[string]any{"rows": rows, "total": total, "offset": off, "limit": lim})
  1497. }
  1498. // heapChainsH 所有表的堆链汇总 (声明值 / 实走值 / 引用数), 供"堆链总览"页。
  1499. func heapChainsH(w http.ResponseWriter, r *http.Request) {
  1500. if dbF == "" {
  1501. jw(w, map[string]any{"e": "no db"})
  1502. return
  1503. }
  1504. a := auditCached()
  1505. jw(w, map[string]any{"tables": a.Tables, "free": a.Free, "reach": a.Reach, "verdict": a.Verdict})
  1506. }
  1507. // pageInfoH 单页检查: 原始页头 + 归属 + 是否空闲 / 从未分配。
  1508. func pageInfoH(w http.ResponseWriter, r *http.Request) {
  1509. if dbF == "" {
  1510. jw(w, map[string]any{"e": "no db"})
  1511. return
  1512. }
  1513. var pid uint32
  1514. fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid)
  1515. if pid == 0 {
  1516. jw(w, map[string]any{"e": "bad id"})
  1517. return
  1518. }
  1519. jw(w, rPageInfo(pid))
  1520. }
  1521. var _ = unsafe.Sizeof(0)