| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- package common
- import (
- "bufio"
- "fmt"
- "os"
- "strconv"
- "strings"
- "text/tabwriter"
- "time"
- "igit.com/xbase/raft"
- )
- const (
- ColorReset = "\033[0m"
- ColorDim = "\033[90m" // Dark Gray
- ColorRed = "\033[31m"
- ColorGreen = "\033[32m"
- ColorYellow = "\033[33m"
- ColorBlue = "\033[34m"
- ColorCyan = "\033[36m"
- )
- func StartCLI(server *raft.KVServer, nodeID string) {
- fmt.Printf("Node %s%s%s CLI Started\n", ColorGreen, nodeID, ColorReset)
- fmt.Println("Type 'help' for commands.")
- // State Monitor Loop
- go func() {
- var lastState string
- var lastTerm uint64
- stats := server.GetStats()
- lastState = stats.State
- lastTerm = stats.Term
- ticker := time.NewTicker(100 * time.Millisecond)
- defer ticker.Stop()
- for range ticker.C {
- stats := server.GetStats()
- if stats.State != lastState || stats.Term != lastTerm {
- fmt.Printf("\n%s[State Change] %s (Term %d) -> %s (Term %d)%s\n> ",
- ColorYellow, lastState, lastTerm, stats.State, stats.Term, ColorReset)
- lastState = stats.State
- lastTerm = stats.Term
- }
- }
- }()
- scanner := bufio.NewScanner(os.Stdin)
- fmt.Print("> ")
- for scanner.Scan() {
- text := strings.TrimSpace(scanner.Text())
- if text == "" {
- fmt.Print("> ")
- continue
- }
- parts := strings.Fields(text)
- cmd := strings.ToLower(parts[0])
- switch cmd {
- case "set":
- if len(parts) != 3 {
- fmt.Println("Usage: set <key> <value>")
- break
- }
- key, val := parts[1], parts[2]
- if err := server.Set(key, val); err != nil {
- fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
- } else {
- fmt.Printf("%sOK%s\n", ColorGreen, ColorReset)
- }
- case "get":
- if len(parts) != 2 {
- fmt.Println("Usage: get <key>")
- break
- }
- key := parts[1]
- if val, ok, err := server.GetLinear(key); err != nil {
- fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
- } else if !ok {
- fmt.Printf("%sNot Found%s\n", ColorYellow, ColorReset)
- } else {
- fmt.Printf("%s%s%s = %s%s%s\n", ColorCyan, key, ColorReset, ColorYellow, val, ColorReset)
- }
- case "del", "delete":
- if len(parts) != 2 {
- fmt.Println("Usage: del <key>")
- break
- }
- key := parts[1]
- if err := server.Del(key); err != nil {
- fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
- } else {
- fmt.Printf("%sDeleted%s\n", ColorGreen, ColorReset)
- }
- case "demodata":
- if len(parts) != 3 {
- fmt.Println("Usage: demodata <count> <pattern> (e.g. demodata 100 user.*)")
- break
- }
- count, err := strconv.Atoi(parts[1])
- if err != nil {
- fmt.Printf("Invalid count: %v\n", err)
- break
- }
- pattern := parts[2]
- fmt.Printf("Generating %d items with pattern '%s'...\n", count, pattern)
-
- start := time.Now()
- success := 0
- for i := 1; i <= count; i++ {
- key := strings.Replace(pattern, "*", strconv.Itoa(i), -1)
- val := fmt.Sprintf("val-%s", key) // Simple value derivation
- if err := server.Set(key, val); err != nil {
- fmt.Printf("Failed at %d: %v\n", i, err)
- } else {
- success++
- }
- if i%100 == 0 {
- fmt.Printf("Progress: %d/%d\r", i, count)
- }
- }
- duration := time.Since(start)
- fmt.Printf("\n%sDone!%s inserted %d/%d items in %v (Avg: %v/op)\n",
- ColorGreen, ColorReset, success, count, duration, duration/time.Duration(count))
- case "search":
- // search <pattern> [limit] [offset]
- // Internally constructs: key like "<pattern>" LIMIT <limit> OFFSET <offset>
- if len(parts) < 2 {
- fmt.Println("Usage: search <pattern> [limit] [offset]")
- break
- }
- pattern := parts[1]
- limit := 20 // Default limit
- offset := 0
-
- if len(parts) >= 3 {
- if l, err := strconv.Atoi(parts[2]); err == nil {
- limit = l
- }
- }
- if len(parts) >= 4 {
- if o, err := strconv.Atoi(parts[3]); err == nil {
- offset = o
- }
- }
- // Construct SQL for DB Engine
- sql := fmt.Sprintf("key like \"%s\" LIMIT %d OFFSET %d", pattern, limit, offset)
- results, err := server.DB.Query(sql)
- if err != nil {
- fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
- break
- }
- // Print Table
- w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
- fmt.Fprintln(w, "Key\tValue\tCommitIndex")
- fmt.Fprintln(w, "---\t-----\t-----------")
- for _, r := range results {
- fmt.Fprintf(w, "%s\t%s\t%d\n", r.Key, r.Value, r.CommitIndex)
- }
- w.Flush()
- fmt.Printf("%sFound %d records (showing max %d)%s\n", ColorDim, len(results), limit, ColorReset)
- case "binlog":
- // Show Raft Log Stats
- stats := server.GetStats() // Basic stats
- fmt.Printf("Raft Log CommitIndex: %s%d%s\n", ColorGreen, stats.CommitIndex, ColorReset)
- fmt.Printf("Raft Log AppliedIndex: %s%d%s\n", ColorGreen, stats.LastApplied, ColorReset)
- fmt.Printf("Raft LastLogIndex: %s%d%s\n", ColorGreen, stats.LastLogIndex, ColorReset)
- case "db":
- // Show DB Stats
- idx := server.DB.GetLastAppliedIndex()
- fmt.Printf("DB LastAppliedIndex: %s%d%s\n", ColorGreen, idx, ColorReset)
- case "stats":
- stats := server.GetStats()
- health := server.HealthCheck()
- fmt.Printf("Node: %s\nState: %s\nLeader: %s\nTerm: %d\nHealthy: %v\n",
- health.NodeID, health.State, health.LeaderID, health.Term, health.IsHealthy)
- fmt.Printf("CommitIndex: %d, Applied: %d\n", stats.CommitIndex, stats.LastApplied)
- fmt.Printf("Cluster Size: %d\n", stats.ClusterSize)
- fmt.Println("Cluster Nodes:")
- for id, addr := range stats.ClusterNodes {
- fmt.Printf(" - %s: %s\n", id, addr)
- }
- case "join":
- if len(parts) != 3 {
- fmt.Println("Usage: join <nodeID> <addr>")
- break
- }
- if err := server.Join(parts[1], parts[2]); err != nil {
- fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
- } else {
- fmt.Printf("%sJoined node %s%s\n", ColorGreen, parts[1], ColorReset)
- }
- case "leave":
- if len(parts) != 2 {
- fmt.Println("Usage: leave <nodeID>")
- break
- }
- if err := server.Leave(parts[1]); err != nil {
- fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
- } else {
- fmt.Printf("%sRemoved node %s%s\n", ColorGreen, parts[1], ColorReset)
- }
- case "help":
- fmt.Println("Commands:")
- fmt.Println(" set <key> <val> Set value")
- fmt.Println(" get <key> Get value")
- fmt.Println(" del <key> Delete key")
- fmt.Println(" demodata <n> <pat> Generate n items (e.g. 'demodata 100 user.*')")
- fmt.Println(" search <pat> [lim] [off] Search keys (e.g. 'search user.* 10 0')")
- fmt.Println(" binlog Show Raft log indices")
- fmt.Println(" db Show DB applied index")
- fmt.Println(" stats Show node stats")
- fmt.Println(" join <id> <addr> Add node to cluster")
- fmt.Println(" leave <id> Remove node from cluster")
- default:
- fmt.Println("Unknown command. Type 'help'.")
- }
- fmt.Print("> ")
- }
- }
|