Files
bybit_btcusdt_ingest/utils/stats/main.go
T

152 lines
4.3 KiB
Go

package main
import (
"database/sql"
"flag"
"fmt"
"os"
"path/filepath"
"time"
_ "modernc.org/sqlite"
)
// ANSI Color Codes
const (
ColorReset = "\033[0m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorCyan = "\033[36m"
ColorRed = "\033[31m"
ColorGray = "\033[90m"
ColorBold = "\033[1m"
)
func main() {
dataDir := flag.String("data", "./data", "Path to the data directory")
noColor := flag.Bool("no-color", false, "Disable colored output")
flag.Parse()
// Disable colors if requested or if not a TTY (simplified check)
if *noColor {
disableColors()
}
fmt.Printf("%s%s--- Database Dashboard (%s) ---%s\n", ColorBold, ColorGreen, time.Now().Format(time.RFC822), ColorReset)
// 1. Hot Ticks Stats
hotPath := filepath.Join(*dataDir, "hot_ticks.db")
printTableStats("HOT STORAGE (RECENT TICKS)", hotPath, "btc_ticks", "trade_ts")
// 2. Feature Stats
featPath := filepath.Join(*dataDir, "features.db")
printTableStats("FEATURE STORAGE (ANALYTICS)", featPath, "five_second_features", "timestamp")
// 3. Archive Stats
archiveDir := filepath.Join(*dataDir, "archive")
files, _ := filepath.Glob(filepath.Join(archiveDir, "*.db"))
if len(files) > 0 {
fmt.Printf("\n%s%s[ ARCHIVE DIRECTORY: %d FILES ]%s\n", ColorBold, ColorCyan, len(files), ColorReset)
var totalArchived int64
var totalSize int64
for _, f := range files {
count, minTs, maxTs, err := getBasicStats(f, "btc_ticks", "trade_ts")
size := getFullDBSize(f)
totalSize += size
if err != nil {
fmt.Printf(" %s!%s %-25s: Error reading (%v)\n", ColorRed, ColorReset, filepath.Base(f), err)
continue
}
totalArchived += count
fmt.Printf(" %s•%s %-25s %s%10s%s | %s%12d rows%s | %s to %s\n",
ColorCyan, ColorReset, filepath.Base(f),
ColorYellow, formatBytes(size), ColorReset,
ColorGray, count, ColorReset,
formatMs(minTs), formatMs(maxTs))
}
fmt.Printf("%sTotal Archived: %d rows across %s%s\n", ColorBold, totalArchived, formatBytes(totalSize), ColorReset)
} else {
fmt.Printf("\n%s[ ARCHIVE ] No weekly archive files found.%s\n", ColorGray, ColorReset)
}
}
func printTableStats(label, dbPath, table, tsCol string) {
fmt.Printf("\n%s%s[ %s ]%s\n", ColorBold, ColorCyan, label, ColorReset)
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
fmt.Printf(" %sFile not found: %s%s\n", ColorRed, dbPath, ColorReset)
return
}
size := getFullDBSize(dbPath)
count, minTs, maxTs, err := getBasicStats(dbPath, table, tsCol)
if err != nil {
fmt.Printf(" %sDatabase Error: %v%s\n", ColorRed, err, ColorReset)
return
}
fmt.Printf(" %-12s %s%s%s\n", "Disk Usage:", ColorYellow, formatBytes(size), ColorReset)
fmt.Printf(" %-12s %s%d%s\n", "Row Count:", ColorYellow, count, ColorReset)
if count > 0 {
fmt.Printf(" %-12s %s\n", "Time Range:", formatMs(minTs))
fmt.Printf(" %-12s %s\n", "", formatMs(maxTs))
span := time.UnixMilli(maxTs).Sub(time.UnixMilli(minTs)).Round(time.Second)
fmt.Printf(" %-12s %s%v%s\n", "Data Span:", ColorGreen, span, ColorReset)
}
}
func getBasicStats(dbPath, table, tsCol string) (count int64, minTs int64, maxTs int64, err error) {
// mode=ro allows reading while the heavy writer is working
// nolock=1 is an alternative, but mode=ro + WAL is safer for stats
dsn := fmt.Sprintf("file:%s?mode=ro&_journal_mode=WAL", dbPath)
db, err := sql.Open("sqlite", dsn)
if err != nil {
return 0, 0, 0, err
}
defer db.Close()
query := fmt.Sprintf("SELECT COUNT(*), COALESCE(MIN(%s),0), COALESCE(MAX(%s),0) FROM %s", tsCol, tsCol, table)
err = db.QueryRow(query).Scan(&count, &minTs, &maxTs)
return count, minTs, maxTs, err
}
// getFullDBSize sums the .db, .db-wal, and .db-shm files
func getFullDBSize(path string) int64 {
var total int64
extensions := []string{"", "-wal", "-shm"}
for _, ext := range extensions {
if info, err := os.Stat(path + ext); err == nil {
total += info.Size()
}
}
return total
}
func formatBytes(b int64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.2f %cB", float64(b)/float64(div), "KMGTPE"[exp])
}
func formatMs(ms int64) string {
if ms == 0 {
return "N/A"
}
return time.UnixMilli(ms).UTC().Format("2006-01-02 15:04:05 MST")
}
func disableColors() {
}