| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025 |
- package main
- import (
- "encoding/binary"
- "encoding/json"
- "fmt"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "time"
- "unsafe"
- )
- const (
- PGSZ = 16384
- HDRSZ = 4096
- PHDR = 20 // PageHdr actual size (with alignment)
- DP_HDR = PHDR + 4 + 8 + 8 // DataPage header: PageHdr(20) + Count(4) + Min(8) + Max(8) = 40
- IDX_HDR = 32 // PHDR(20) + pad(1) + IndexType(1) + EntryLen(2) + Count(4) + IsLeaf(1) + pad(3)
- META_SZ = 60 // IndexMeta entry size (with Go alignment padding)
- TINFO_SZ = 80 // TableInfo struct size (with alignment)
- PG_HEAD = 0
- PG_TABLE = 1
- PG_DATA = 2
- PG_HEAP = 3
- PG_IMETA = 4
- PG_IDATA = 5
- )
- // ===== Data Structures =====
- type TInfo struct {
- Name string `json:"name"`
- RowN uint64 `json:"rowN"`
- FSz uint16 `json:"fsz"`
- DP0 uint32 `json:"dp0"`
- IP0 uint32 `json:"ip0"`
- HP0 uint32 `json:"hp0"`
- ActiveVer byte `json:"activeVer"`
- MasterFields []MFInfo `json:"masterFields"`
- Versions []VInfo `json:"versions"`
- Idxs []IInfo `json:"idxs"`
- }
- type MFInfo struct {
- Name string `json:"name"`
- Type byte `json:"type"`
- TypeName string `json:"typeName"`
- Sz int `json:"sz"`
- Dropped bool `json:"dropped"`
- }
- type VInfo struct {
- Ver byte `json:"ver"`
- Fields []string `json:"fields"`
- Indices []byte `json:"indices"`
- }
- type FInfo struct {
- Name string `json:"name"`
- Type byte `json:"type"`
- Sz int `json:"sz"`
- }
- type IInfo struct {
- Name string `json:"name"`
- Type byte `json:"type"`
- TS string `json:"ts"`
- EP uint32 `json:"ep"`
- Flds []int `json:"flds"`
- }
- type DP struct {
- ID uint32 `json:"id"`
- Nx uint32 `json:"nx"`
- Pv uint32 `json:"pv"`
- Cn uint32 `json:"cn"`
- Mx uint64 `json:"mx"`
- Mn uint64 `json:"mn"`
- Us float64 `json:"us"`
- Rs []DR `json:"rs"`
- }
- type DR struct {
- I int `json:"i"`
- PKID uint64 `json:"pk"`
- SchemaV byte `json:"sv"`
- Hex string `json:"h"`
- Decoded string `json:"decoded"`
- }
- type IP struct {
- ID uint32 `json:"id"`
- Nx uint32 `json:"nx"`
- Pv uint32 `json:"pv"`
- Lf bool `json:"lf"`
- Cn uint32 `json:"cn"`
- Us float64 `json:"us"`
- Ents []IE `json:"ents"`
- IdxType byte `json:"it"`
- }
- type IE struct {
- K string `json:"k"`
- P uint32 `json:"p"`
- DP uint64 `json:"dp"`
- }
- type HdrInfo struct {
- Magic string `json:"magic"`
- Version uint32 `json:"ver"`
- PageSize uint32 `json:"pgsz"`
- NextFree uint32 `json:"nf"`
- TablePage uint32 `json:"tp"`
- TableCount uint32 `json:"tc"`
- CreateTime string `json:"ct"`
- ModifyTime string `json:"mt"`
- FreeStart uint32 `json:"fs"`
- FreeCount uint32 `json:"fc"`
- GlobalFlags uint64 `json:"gf"`
- Comment string `json:"cm"`
- }
- type HeapChain struct {
- Pages []HeapPageInfo `json:"pages"`
- Total int `json:"total"`
- }
- type HeapPageInfo struct {
- ID uint32 `json:"id"`
- Next uint32 `json:"nx"`
- Prev uint32 `json:"pv"`
- Use uint32 `json:"use"`
- Us float64 `json:"us"`
- }
- type FreePages struct {
- Pages []uint32 `json:"pages"`
- Total int `json:"total"`
- }
- var (
- dbF string
- fp string
- hdr [HDRSZ]byte
- tabs []TInfo
- mu sync.RWMutex
- )
- func main() {
- log.SetFlags(log.LstdFlags | log.Lshortfile)
- fmt.Println("╔══════════════════════════════════════════════╗")
- fmt.Println("║ TsDB2 Analyzer v3.0 (Schema Aware) ║")
- fmt.Println("║ http://localhost:8081 ║")
- fmt.Println("╚══════════════════════════════════════════════╝")
- http.HandleFunc("/", homeH)
- http.HandleFunc("/api/list-dir", ldH)
- http.HandleFunc("/api/open-db", openH)
- http.HandleFunc("/api/stats", stH)
- http.HandleFunc("/api/tables", taH)
- http.HandleFunc("/api/table/", tdH)
- http.HandleFunc("/api/data-page", dpH)
- http.HandleFunc("/api/index-page", ipH)
- http.HandleFunc("/api/btree", btH)
- http.HandleFunc("/api/header", hdrH)
- http.HandleFunc("/api/heap-chain", heapH)
- http.HandleFunc("/api/free-pages", freeH)
- http.HandleFunc("/api/pages", pagesH)
- http.HandleFunc("/api/find-pk", findPkH)
- http.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.Dir("."))))
- log.Fatal(http.ListenAndServe(":8081", nil))
- }
- // ===== Low-level page reading =====
- func rp(pid uint32) ([]byte, error) {
- off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ)
- b := make([]byte, PGSZ)
- f, e := os.Open(fp)
- if e != nil {
- return nil, e
- }
- defer f.Close()
- _, e = f.ReadAt(b, off)
- return b, e
- }
- func rHdr() {
- f, e := os.Open(fp)
- if e != nil {
- return
- }
- defer f.Close()
- f.ReadAt(hdr[:], 0)
- }
- // ===== Parse all tables from TablePage chain =====
- func pTabs() []TInfo {
- var R []TInfo
- b := make([]byte, PGSZ)
- f, e := os.Open(fp)
- if e != nil {
- return R
- }
- defer f.Close()
- tp := binary.LittleEndian.Uint32(hdr[16:20])
- if tp == 0 {
- return R
- }
- for pid := tp; pid != 0; {
- f.ReadAt(b, int64(HDRSZ)+int64(pid-1)*int64(PGSZ))
- us := binary.LittleEndian.Uint32(b[16:20])
- if us > PGSZ {
- us = PGSZ
- }
- off := uint32(PHDR)
- for off+TINFO_SZ <= us && off+TINFO_SZ <= PGSZ {
- tb := b[off : off+TINFO_SZ]
- fl := tb[24]
- if fl&0x80 != 0 {
- off += TINFO_SZ + uint32(binary.LittleEndian.Uint16(tb[58:60]))
- continue
- }
- nm := strings.TrimRight(string(tb[:24]), "\x00")
- rn := binary.LittleEndian.Uint64(tb[32:40])
- // Parse new schema format
- fo := off + TINFO_SZ
- activeVer, masterFields, versions := parseSchemaFields(b, fo)
- // Parse indexes
- var is []IInfo
- iip := binary.LittleEndian.Uint32(tb[64:68])
- for ip := iip; ip != 0; {
- ib, e := rp(ip)
- if e != nil {
- break
- }
- if ib[0] != PG_IMETA {
- break
- }
- iu := binary.LittleEndian.Uint32(ib[16:20])
- if iu > PGSZ {
- iu = PGSZ
- }
- io := uint32(PHDR)
- for io+META_SZ <= iu && io+META_SZ <= PGSZ {
- im := ib[io : io+META_SZ]
- // IndexMeta struct: Name[24] + Type(1) + FieldCnt(1) + FieldIDs[16] + pad(2) + EntryPage(4) + Prev(4) + Next(4) + Flag(1) + pad(3) = 60
- if im[56]&0x80 == 0 { // Flag at offset 56
- in := strings.TrimRight(string(im[:24]), "\x00")
- it := im[24] // IndexType at offset 24
- ic := int(im[25]) // FieldCnt at offset 25
- ep := binary.LittleEndian.Uint32(im[44:48]) // EntryPage at offset 44 (after 2B padding)
- // 验证 EntryPage 指向有效索引页 (防止已删除表的孤立条目)
- if ep != 0 {
- pb, pe := rp(ep)
- if pe != nil || pb[0] != PG_IDATA {
- ep = 0 // 无效页, 跳过
- }
- }
- if ep != 0 {
- var fds []int
- for j := 0; j < ic; j++ {
- fds = append(fds, int(im[26+j])) // FieldIDs at offset 26
- }
- is = append(is, IInfo{in, it, tNm(it), ep, fds})
- }
- }
- io += META_SZ
- }
- ip = binary.LittleEndian.Uint32(ib[8:12])
- }
- R = append(R, TInfo{
- Name: nm, RowN: rn,
- FSz: binary.LittleEndian.Uint16(tb[56:58]),
- DP0: binary.LittleEndian.Uint32(tb[48:52]),
- IP0: binary.LittleEndian.Uint32(tb[64:68]),
- HP0: binary.LittleEndian.Uint32(tb[40:44]),
- ActiveVer: activeVer, MasterFields: masterFields, Versions: versions, Idxs: is,
- })
- off += TINFO_SZ + uint32(binary.LittleEndian.Uint16(tb[58:60]))
- }
- nx := binary.LittleEndian.Uint32(b[8:12])
- if nx == 0 || nx == pid {
- break
- }
- pid = nx
- }
- return R
- }
- // parseSchemaFields 解析新版 schema 字段定义链
- // 格式: [activeVer(1B)] [masterCount(1B)] [type+len+name]×N [verCount(1B)] [verDefs...]
- func parseSchemaFields(b []byte, off uint32) (byte, []MFInfo, []VInfo) {
- if int(off)+2 > len(b) {
- return 0, nil, nil
- }
- pos := int(off)
- activeVer := b[pos]
- pos++
- masterCount := int(b[pos])
- pos++
- var masterFields []MFInfo
- for i := 0; i < masterCount && pos+2 <= len(b); i++ {
- typ := b[pos]
- nameLen := int(b[pos+1])
- pos += 2
- if pos+nameLen > len(b) {
- break
- }
- name := strings.TrimRight(string(b[pos:pos+nameLen]), "\x00")
- pos += nameLen
- dropped := typ&0x80 != 0
- realType := typ & 0x7F
- masterFields = append(masterFields, MFInfo{
- Name: name, Type: realType, TypeName: fName(realType),
- Sz: fSz(realType), Dropped: dropped,
- })
- }
- var versions []VInfo
- if pos < len(b) {
- verCount := int(b[pos])
- pos++
- for i := 0; i < verCount && pos < len(b); i++ {
- verNo := b[pos]
- pos++
- if pos >= len(b) {
- break
- }
- fieldCount := int(b[pos])
- pos++
- indices := make([]byte, fieldCount)
- if pos+fieldCount <= len(b) {
- copy(indices, b[pos:pos+fieldCount])
- pos += fieldCount
- }
- // Build field name list for this version
- var fieldNames []string
- for _, idx := range indices {
- if int(idx) < len(masterFields) {
- mf := masterFields[idx]
- n := mf.Name
- if mf.Dropped {
- n += " (dropped)"
- }
- fieldNames = append(fieldNames, n)
- } else {
- fieldNames = append(fieldNames, "?")
- }
- }
- versions = append(versions, VInfo{Ver: verNo, Fields: fieldNames, Indices: indices})
- }
- }
- return activeVer, masterFields, versions
- }
- // ===== Field type helpers =====
- func fSz(t byte) int {
- switch t {
- case 1:
- return 1
- case 2:
- return 2
- case 3:
- return 4
- case 4:
- return 8
- case 5:
- return 16
- case 6:
- return 32
- case 7:
- return 64
- case 8:
- return 128
- case 9:
- return 256
- default:
- return 0
- }
- }
- func tNm(t byte) string {
- switch t {
- case 1:
- return "Time"
- case 2:
- return "Primary"
- case 3:
- return "String"
- case 4:
- return "Compose"
- case 5:
- return "BTree"
- default:
- return "?"
- }
- }
- func fName(t byte) string {
- switch t {
- case 1:
- return "int8"
- case 2:
- return "int16"
- case 3:
- return "int32"
- case 4:
- return "int64"
- case 5:
- return "16byte"
- case 6:
- return "32byte"
- case 7:
- return "64byte"
- case 8:
- return "128byte"
- case 9:
- return "256byte"
- default:
- return "?"
- }
- }
- // ===== Read DataPage with schema version awareness =====
- // rDP reads a DataPage (sequential row layout, no directory)
- func rDP(pid uint32) *DP {
- b, e := rp(pid)
- if e != nil {
- return nil
- }
- if b[0] != PG_DATA {
- return nil
- }
- cn := binary.LittleEndian.Uint32(b[PHDR : PHDR+4])
- mn := binary.BigEndian.Uint64(b[PHDR+4 : PHDR+12])
- mx := binary.BigEndian.Uint64(b[PHDR+12 : PHDR+20])
- us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100
- var rs []DR
- off := DP_HDR
- for i := uint32(0); i < cn && off+4 <= len(b); i++ {
- rl := int(binary.LittleEndian.Uint32(b[off : off+4]))
- off += 4
- if off+rl > len(b) || rl <= 0 {
- break
- }
- storeRow := b[off : off+rl]
- off += rl
- // storeRow = [pkID(8B BE)] + rowData
- var pkID uint64
- var rowData []byte
- if len(storeRow) >= 8 {
- pkID = binary.BigEndian.Uint64(storeRow[:8])
- rowData = storeRow[8:]
- } else {
- rowData = storeRow
- }
- schemaV := byte(0)
- if len(rowData) > 0 {
- schemaV = rowData[0]
- }
- hx := fmt.Sprintf("%x", storeRow[:min(len(storeRow), 64)])
- if len(storeRow) > 64 {
- hx += "..."
- }
- decoded := decodeRowData(rowData, schemaV)
- rs = append(rs, DR{int(i), pkID, schemaV, hx, decoded})
- }
- return &DP{pid, binary.LittleEndian.Uint32(b[8:12]), binary.LittleEndian.Uint32(b[12:16]),
- cn, mx, mn, us, rs}
- }
- // decodeRowData 尝试解码行数据 (显示 schemaVer + 各字段值)
- func decodeRowData(rowData []byte, schemaV byte) string {
- if len(rowData) < 1 {
- return ""
- }
- // 查找对应表的版本信息来解码
- // 简单解码: 显示 schemaVer + hex
- parts := []string{fmt.Sprintf("v%d", schemaV)}
- data := rowData[1:] // skip schemaVer
- if len(data) == 0 {
- return strings.Join(parts, " ")
- }
- // 尝试按字段类型解码 (需要知道该版本的字段列表)
- // 通用方式: 显示前几个字节的值
- for _, t := range tabs {
- sv := findVersion(t, schemaV)
- if sv == nil {
- continue
- }
- off := 0
- for _, idx := range sv.Indices {
- if int(idx) >= len(t.MasterFields) {
- break
- }
- mf := t.MasterFields[idx]
- if mf.Dropped {
- continue
- }
- sz := mf.Sz
- if off+sz > len(data) {
- break
- }
- val := decodeFieldValue(data[off:off+sz], mf.Type)
- parts = append(parts, mf.Name+"="+val)
- off += sz
- }
- return strings.Join(parts, ", ")
- }
- return strings.Join(parts, " ")
- }
- func findVersion(t TInfo, ver byte) *VInfo {
- for i := range t.Versions {
- if t.Versions[i].Ver == ver {
- return &t.Versions[i]
- }
- }
- return nil
- }
- func decodeFieldValue(buf []byte, typ byte) string {
- switch typ {
- case 1:
- return fmt.Sprintf("%d", int8(buf[0]))
- case 2:
- return fmt.Sprintf("%d", int16(binary.BigEndian.Uint16(buf)))
- case 3:
- return fmt.Sprintf("%d", int32(binary.BigEndian.Uint32(buf)))
- case 4:
- return fmt.Sprintf("%d", int64(binary.BigEndian.Uint64(buf)))
- default:
- s := strings.TrimRight(string(buf), "\x00")
- if len(s) == 0 {
- return "(empty)"
- }
- return s
- }
- }
- // ===== Index page reading =====
- func rIP(pid uint32, it byte) *IP {
- b, e := rp(pid)
- if e != nil {
- return nil
- }
- if b[0] != PG_IDATA {
- return nil
- }
- lf := b[28] == 1
- cn := binary.LittleEndian.Uint32(b[24:28])
- el := int(binary.LittleEndian.Uint16(b[22:24]))
- if el <= 4 {
- el = 12
- }
- ents := pEE(b, int(cn), el, it, lf)
- us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100
- return &IP{pid, binary.LittleEndian.Uint32(b[8:12]), binary.LittleEndian.Uint32(b[12:16]),
- lf, cn, us, ents, it}
- }
- func pEE(b []byte, cn, el int, it byte, lf bool) []IE {
- var ents []IE
- for i := 0; i < cn; i++ {
- o := IDX_HDR + i*el
- if o+el > len(b) {
- break
- }
- entry := b[o : o+el]
- var ie IE
- switch it {
- case 2:
- ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8]))
- ie.P = binary.BigEndian.Uint32(entry[8:12])
- case 1:
- ie.K = fmt.Sprintf("%d ~ %d", binary.BigEndian.Uint64(entry[:8]), binary.BigEndian.Uint64(entry[8:16]))
- ie.P = binary.BigEndian.Uint32(entry[16:20])
- case 3:
- ie.K = strings.TrimRight(string(entry[:16]), "\x00")
- ie.DP = binary.BigEndian.Uint64(entry[16:24])
- if lf {
- ie.P = binary.BigEndian.Uint32(entry[20:24])
- }
- case 4:
- nk := (el - 8) / 8
- parts := make([]string, nk)
- for j := 0; j < nk; j++ {
- parts[j] = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[j*8:]))
- }
- ie.K = strings.Join(parts, ", ")
- ie.DP = binary.BigEndian.Uint64(entry[nk*8:])
- if lf {
- ie.P = binary.BigEndian.Uint32(entry[el-4:])
- }
- case 5:
- if lf {
- kl := el - 8
- if kl == 8 {
- ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8]))
- } else {
- ie.K = fmt.Sprintf("%x", entry[:kl])
- }
- ie.DP = binary.BigEndian.Uint64(entry[kl:])
- ie.P = binary.BigEndian.Uint32(entry[el-4:])
- } else {
- kl := el - 4
- if kl == 8 {
- ie.K = fmt.Sprintf("%d", binary.BigEndian.Uint64(entry[:8]))
- } else {
- ie.K = fmt.Sprintf("%x", entry[:kl])
- }
- ie.P = binary.BigEndian.Uint32(entry[el-4:])
- }
- default:
- ie.K = fmt.Sprintf("%x", entry[:el-4])
- ie.P = binary.BigEndian.Uint32(entry[el-4:])
- }
- ents = append(ents, ie)
- }
- return ents
- }
- func rBT(pid uint32, it byte) *IP { return rIP(pid, it) }
- // ===== Header + Heap + Free =====
- func rHdrInfo() *HdrInfo {
- f, e := os.Open(fp)
- if e != nil {
- return nil
- }
- defer f.Close()
- b := make([]byte, 4096)
- f.ReadAt(b, 0)
- magic := string(b[:4])
- ver := binary.LittleEndian.Uint32(b[4:8])
- pgsz := binary.LittleEndian.Uint32(b[8:12])
- nf := binary.LittleEndian.Uint32(b[12:16])
- tp := binary.LittleEndian.Uint32(b[16:20])
- tc := binary.LittleEndian.Uint32(b[20:24])
- ct := int64(binary.LittleEndian.Uint64(b[24:32]))
- mt := int64(binary.LittleEndian.Uint64(b[32:40]))
- fs := binary.LittleEndian.Uint32(b[40:44])
- fc := binary.LittleEndian.Uint32(b[44:48])
- gf := binary.LittleEndian.Uint64(b[48:56])
- cm := strings.TrimRight(string(b[312:824]), "\x00")
- return &HdrInfo{magic, ver, pgsz, nf, tp, tc,
- time.Unix(0, ct).Format("2006-01-02 15:04:05"),
- time.Unix(0, mt).Format("2006-01-02 15:04:05"),
- fs, fc, gf, cm}
- }
- func rHeapChain(hp0 uint32) *HeapChain {
- var pages []HeapPageInfo
- for pid := hp0; pid != 0; {
- b, e := rp(pid)
- if e != nil || b[0] != PG_HEAP {
- break
- }
- nx := binary.LittleEndian.Uint32(b[8:12])
- pv := binary.LittleEndian.Uint32(b[12:16])
- use := binary.LittleEndian.Uint32(b[16:20])
- us := float64(use) / float64(PGSZ) * 100
- pages = append(pages, HeapPageInfo{pid, nx, pv, use, us})
- if nx == pid || nx == 0 {
- break
- }
- pid = nx
- }
- return &HeapChain{pages, len(pages)}
- }
- func rFreePages() *FreePages {
- f, e := os.Open(fp)
- if e != nil {
- return &FreePages{nil, 0}
- }
- defer f.Close()
- freeStart := binary.LittleEndian.Uint32(hdr[40:44])
- var pages []uint32
- for pid := freeStart; pid != 0; {
- pages = append(pages, pid)
- b := make([]byte, PGSZ)
- off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ)
- if _, e := f.ReadAt(b, off); e != nil {
- break
- }
- nx := binary.BigEndian.Uint32(b[:4])
- if nx == pid || nx == 0 {
- break
- }
- pid = nx
- }
- return &FreePages{pages, len(pages)}
- }
- // ===== Page visualization =====
- type PageItem struct {
- ID uint32 `json:"id"`
- Type int `json:"t"`
- TS string `json:"ts"`
- Us float64 `json:"us"`
- }
- type PagesResult struct {
- Pages []PageItem `json:"pages"`
- Total int `json:"total"`
- Counts map[string]int `json:"counts"`
- }
- func rPages() *PagesResult {
- fi, e := os.Stat(fp)
- if e != nil {
- return &PagesResult{}
- }
- totalPg := int(fi.Size() / PGSZ)
- if totalPg <= 1 {
- return &PagesResult{}
- }
- // page 0 is not used (header is separate), pages start from 1
- numPages := totalPg - 1 // subtract header page
- if numPages <= 0 {
- return &PagesResult{}
- }
- f, e := os.Open(fp)
- if e != nil {
- return &PagesResult{}
- }
- defer f.Close()
- pages := make([]PageItem, 0, numPages)
- counts := map[string]int{"hdr": 0, "table": 0, "data": 0, "heap": 0, "imeta": 0, "idata": 0, "free": 0, "other": 0}
- typeNames := map[int]string{0: "free", 1: "table", 2: "data", 3: "heap", 4: "imeta", 5: "idata"}
- // Read free page list for marking
- freeSet := make(map[uint32]bool)
- freeStart := binary.LittleEndian.Uint32(hdr[40:44])
- for pid := freeStart; pid != 0; {
- freeSet[pid] = true
- b := make([]byte, PGSZ)
- off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ)
- if _, e := f.ReadAt(b, off); e != nil {
- break
- }
- nx := binary.BigEndian.Uint32(b[:4])
- if nx == pid || nx == 0 {
- break
- }
- pid = nx
- }
- for i := 1; i <= numPages; i++ {
- pid := uint32(i)
- b := make([]byte, PGSZ)
- off := int64(HDRSZ) + int64(pid-1)*int64(PGSZ)
- if _, e := f.ReadAt(b, off); e != nil {
- break
- }
- pt := int(b[0])
- us := float64(binary.LittleEndian.Uint32(b[16:20])) / float64(PGSZ) * 100
- if us > 100 {
- us = 100
- }
- if freeSet[pid] {
- pt = 0 // mark as free
- }
- tn := typeNames[pt]
- if tn == "" {
- tn = "other"
- }
- counts[tn]++
- pages = append(pages, PageItem{pid, pt, tn, us})
- }
- return &PagesResult{pages, len(pages), counts}
- }
- func pagesH(w http.ResponseWriter, r *http.Request) {
- if fp == "" {
- jw(w, map[string]any{"e": "no db"})
- return
- }
- jw(w, rPages())
- }
- // findPkH 通过主键 B+ 树查找 PKID 对应的 DataPage
- func findPkH(w http.ResponseWriter, r *http.Request) {
- if fp == "" {
- jw(w, map[string]any{"e": "no db"})
- return
- }
- pkStr := r.URL.Query().Get("pk")
- rootStr := r.URL.Query().Get("root")
- var pk, root uint32
- fmt.Sscanf(pkStr, "%d", &pk)
- fmt.Sscanf(rootStr, "%d", &root)
- if root == 0 {
- jw(w, map[string]any{"e": "no root"})
- return
- }
- dp := findDPByPK(root, uint64(pk))
- if dp == 0 {
- jw(w, map[string]any{"e": "not found"})
- return
- }
- jw(w, map[string]any{"dp": dp, "pk": pk})
- }
- // findDPByPK 在主键 B+ 树中查找 PKID 对应的 DataPage ID
- func findDPByPK(rootPID uint32, targetPK uint64) uint32 {
- cur := rootPID
- for {
- b, e := rp(cur)
- if e != nil || b[0] != PG_IDATA {
- return 0
- }
- lf := b[28] == 1
- cn := int(binary.LittleEndian.Uint32(b[24:28]))
- el := int(binary.LittleEndian.Uint16(b[22:24]))
- if el <= 4 {
- el = 12
- }
- if lf {
- // 叶子节点: 查找匹配的 PK
- for i := 0; i < cn; i++ {
- o := IDX_HDR + i*el
- if o+el > len(b) {
- break
- }
- key := binary.BigEndian.Uint64(b[o : o+8])
- if key == targetPK {
- return binary.BigEndian.Uint32(b[o+8 : o+12])
- }
- }
- return 0
- }
- // 内部节点: 二分查找最后一个 key <= targetPK
- lo, hi := 0, cn-1
- pos := 0
- for lo <= hi {
- mid := (lo + hi) / 2
- o := IDX_HDR + mid*el
- if o+8 > len(b) {
- break
- }
- key := binary.BigEndian.Uint64(b[o : o+8])
- if key <= targetPK {
- pos = mid
- lo = mid + 1
- } else {
- hi = mid - 1
- }
- }
- o := IDX_HDR + pos*el
- childPID := binary.BigEndian.Uint32(b[o+el-4 : o+el])
- if childPID == 0 {
- return 0
- }
- cur = childPID
- }
- }
- // ===== Utility =====
- func min(a, b int) int {
- if a < b {
- return a
- }
- return b
- }
- func fB(n int64) string {
- if n < 1024 {
- return fmt.Sprintf("%d B", n)
- }
- if n < 1048576 {
- return fmt.Sprintf("%.1f KB", float64(n)/1024)
- }
- if n < 1073741824 {
- return fmt.Sprintf("%.2f MB", float64(n)/1048576)
- }
- return fmt.Sprintf("%.2f GB", float64(n)/1073741824)
- }
- func fE(p string) bool { _, e := os.Stat(p); return e == nil }
- func jw(w http.ResponseWriter, v any) {
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(v)
- }
- // ===== HTTP Handlers =====
- func homeH(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate")
- w.Write([]byte(pg))
- }
- func ldH(w http.ResponseWriter, r *http.Request) {
- p := r.URL.Query().Get("path")
- if p == "" {
- p, _ = os.Getwd()
- }
- if !filepath.IsAbs(p) {
- c, _ := os.Getwd()
- p = filepath.Join(c, p)
- }
- es, e := os.ReadDir(p)
- if e != nil {
- jw(w, map[string]any{"err": e.Error()})
- return
- }
- type F struct {
- N string `json:"n"`
- D bool `json:"d"`
- S int64 `json:"s"`
- }
- var ds, fs []F
- for _, x := range es {
- i, _ := x.Info()
- s := int64(0)
- if i != nil {
- s = i.Size()
- }
- if x.IsDir() {
- ds = append(ds, F{x.Name(), true, s})
- } else {
- fs = append(fs, F{x.Name(), false, s})
- }
- }
- jw(w, map[string]any{"p": p, "pp": filepath.Dir(p), "d": ds, "f": fs})
- }
- func openH(w http.ResponseWriter, r *http.Request) {
- if r.Method != "POST" {
- http.Error(w, "no", 405)
- return
- }
- var q struct {
- P string `json:"p"`
- }
- json.NewDecoder(r.Body).Decode(&q)
- if !fE(q.P) {
- c, _ := os.Getwd()
- q2 := filepath.Join(c, q.P)
- if fE(q2) {
- q.P = q2
- } else {
- jw(w, map[string]any{"ok": false, "e": "not found"})
- return
- }
- }
- mu.Lock()
- fp = q.P
- rHdr()
- tabs = pTabs()
- mu.Unlock()
- fi, _ := os.Stat(fp)
- dbF = fp
- jw(w, map[string]any{"ok": true, "s": st(fi)})
- }
- func st(fi os.FileInfo) map[string]any {
- s := int64(0)
- if fi != nil {
- s = fi.Size()
- }
- return map[string]any{"f": dbF, "s": s, "fs": fB(s), "pg": int(s / PGSZ), "tc": len(tabs)}
- }
- func stH(w http.ResponseWriter, r *http.Request) {
- if dbF == "" {
- jw(w, map[string]any{"e": "no db"})
- return
- }
- fi, _ := os.Stat(fp)
- jw(w, st(fi))
- }
- func taH(w http.ResponseWriter, r *http.Request) { jw(w, tabs) }
- func tdH(w http.ResponseWriter, r *http.Request) {
- n := r.URL.Path[len("/api/table/"):]
- for _, t := range tabs {
- if t.Name == n {
- jw(w, t)
- return
- }
- }
- jw(w, map[string]any{"e": "not found"})
- }
- func dpH(w http.ResponseWriter, r *http.Request) {
- var pid uint32
- fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid)
- d := rDP(pid)
- if d == nil {
- jw(w, map[string]any{"e": "err"})
- return
- }
- jw(w, d)
- }
- func ipH(w http.ResponseWriter, r *http.Request) {
- var pid uint32
- fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid)
- it, _ := strconv.Atoi(r.URL.Query().Get("t"))
- d := rIP(pid, byte(it))
- if d == nil {
- jw(w, map[string]any{"e": "err"})
- return
- }
- jw(w, d)
- }
- func btH(w http.ResponseWriter, r *http.Request) {
- var pid uint32
- fmt.Sscanf(r.URL.Query().Get("id"), "%d", &pid)
- it, _ := strconv.Atoi(r.URL.Query().Get("t"))
- d := rBT(pid, byte(it))
- if d == nil {
- jw(w, map[string]any{"e": "err"})
- return
- }
- jw(w, d)
- }
- func hdrH(w http.ResponseWriter, r *http.Request) {
- d := rHdrInfo()
- if d == nil {
- jw(w, map[string]any{"e": "err"})
- return
- }
- jw(w, d)
- }
- func heapH(w http.ResponseWriter, r *http.Request) {
- var hp0 uint32
- fmt.Sscanf(r.URL.Query().Get("id"), "%d", &hp0)
- jw(w, rHeapChain(hp0))
- }
- func freeH(w http.ResponseWriter, r *http.Request) { jw(w, rFreePages()) }
- var _ = unsafe.Sizeof(0)
|