cli.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package common
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "strings"
  8. "text/tabwriter"
  9. "time"
  10. "igit.com/xbase/raft"
  11. )
  12. const (
  13. ColorReset = "\033[0m"
  14. ColorDim = "\033[90m" // Dark Gray
  15. ColorRed = "\033[31m"
  16. ColorGreen = "\033[32m"
  17. ColorYellow = "\033[33m"
  18. ColorBlue = "\033[34m"
  19. ColorCyan = "\033[36m"
  20. )
  21. func StartCLI(server *raft.KVServer, nodeID string) {
  22. fmt.Printf("Node %s%s%s CLI Started\n", ColorGreen, nodeID, ColorReset)
  23. fmt.Println("Type 'help' for commands.")
  24. // State Monitor Loop
  25. go func() {
  26. var lastState string
  27. var lastTerm uint64
  28. stats := server.GetStats()
  29. lastState = stats.State
  30. lastTerm = stats.Term
  31. ticker := time.NewTicker(100 * time.Millisecond)
  32. defer ticker.Stop()
  33. for range ticker.C {
  34. stats := server.GetStats()
  35. if stats.State != lastState || stats.Term != lastTerm {
  36. fmt.Printf("\n%s[State Change] %s (Term %d) -> %s (Term %d)%s\n> ",
  37. ColorYellow, lastState, lastTerm, stats.State, stats.Term, ColorReset)
  38. lastState = stats.State
  39. lastTerm = stats.Term
  40. }
  41. }
  42. }()
  43. scanner := bufio.NewScanner(os.Stdin)
  44. fmt.Print("> ")
  45. for scanner.Scan() {
  46. text := strings.TrimSpace(scanner.Text())
  47. if text == "" {
  48. fmt.Print("> ")
  49. continue
  50. }
  51. parts := strings.Fields(text)
  52. cmd := strings.ToLower(parts[0])
  53. switch cmd {
  54. case "set":
  55. if len(parts) != 3 {
  56. fmt.Println("Usage: set <key> <value>")
  57. break
  58. }
  59. key, val := parts[1], parts[2]
  60. if err := server.Set(key, val); err != nil {
  61. fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
  62. } else {
  63. fmt.Printf("%sOK%s\n", ColorGreen, ColorReset)
  64. }
  65. case "get":
  66. if len(parts) != 2 {
  67. fmt.Println("Usage: get <key>")
  68. break
  69. }
  70. key := parts[1]
  71. if val, ok, err := server.GetLinear(key); err != nil {
  72. fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
  73. } else if !ok {
  74. fmt.Printf("%sNot Found%s\n", ColorYellow, ColorReset)
  75. } else {
  76. fmt.Printf("%s%s%s = %s%s%s\n", ColorCyan, key, ColorReset, ColorYellow, val, ColorReset)
  77. }
  78. case "del", "delete":
  79. if len(parts) != 2 {
  80. fmt.Println("Usage: del <key>")
  81. break
  82. }
  83. key := parts[1]
  84. if err := server.Del(key); err != nil {
  85. fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
  86. } else {
  87. fmt.Printf("%sDeleted%s\n", ColorGreen, ColorReset)
  88. }
  89. case "demodata":
  90. if len(parts) != 3 {
  91. fmt.Println("Usage: demodata <count> <pattern> (e.g. demodata 100 user.*)")
  92. break
  93. }
  94. count, err := strconv.Atoi(parts[1])
  95. if err != nil {
  96. fmt.Printf("Invalid count: %v\n", err)
  97. break
  98. }
  99. pattern := parts[2]
  100. fmt.Printf("Generating %d items with pattern '%s'...\n", count, pattern)
  101. start := time.Now()
  102. success := 0
  103. for i := 1; i <= count; i++ {
  104. key := strings.Replace(pattern, "*", strconv.Itoa(i), -1)
  105. val := fmt.Sprintf("val-%s", key) // Simple value derivation
  106. if err := server.Set(key, val); err != nil {
  107. fmt.Printf("Failed at %d: %v\n", i, err)
  108. } else {
  109. success++
  110. }
  111. if i%100 == 0 {
  112. fmt.Printf("Progress: %d/%d\r", i, count)
  113. }
  114. }
  115. duration := time.Since(start)
  116. fmt.Printf("\n%sDone!%s inserted %d/%d items in %v (Avg: %v/op)\n",
  117. ColorGreen, ColorReset, success, count, duration, duration/time.Duration(count))
  118. case "search":
  119. // search <pattern> [limit] [offset]
  120. // Internally constructs: key like "<pattern>" LIMIT <limit> OFFSET <offset>
  121. if len(parts) < 2 {
  122. fmt.Println("Usage: search <pattern> [limit] [offset]")
  123. break
  124. }
  125. pattern := parts[1]
  126. limit := 20 // Default limit
  127. offset := 0
  128. if len(parts) >= 3 {
  129. if l, err := strconv.Atoi(parts[2]); err == nil {
  130. limit = l
  131. }
  132. }
  133. if len(parts) >= 4 {
  134. if o, err := strconv.Atoi(parts[3]); err == nil {
  135. offset = o
  136. }
  137. }
  138. // Construct SQL for DB Engine
  139. sql := fmt.Sprintf("key like \"%s\" LIMIT %d OFFSET %d", pattern, limit, offset)
  140. results, err := server.DB.Query(sql)
  141. if err != nil {
  142. fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
  143. break
  144. }
  145. // Print Table
  146. w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
  147. fmt.Fprintln(w, "Key\tValue\tCommitIndex")
  148. fmt.Fprintln(w, "---\t-----\t-----------")
  149. for _, r := range results {
  150. fmt.Fprintf(w, "%s\t%s\t%d\n", r.Key, r.Value, r.CommitIndex)
  151. }
  152. w.Flush()
  153. fmt.Printf("%sFound %d records (showing max %d)%s\n", ColorDim, len(results), limit, ColorReset)
  154. case "binlog":
  155. // Show Raft Log Stats
  156. stats := server.GetStats() // Basic stats
  157. fmt.Printf("Raft Log CommitIndex: %s%d%s\n", ColorGreen, stats.CommitIndex, ColorReset)
  158. fmt.Printf("Raft Log AppliedIndex: %s%d%s\n", ColorGreen, stats.LastApplied, ColorReset)
  159. fmt.Printf("Raft LastLogIndex: %s%d%s\n", ColorGreen, stats.LastLogIndex, ColorReset)
  160. case "db":
  161. // Show DB Stats
  162. idx := server.DB.GetLastAppliedIndex()
  163. fmt.Printf("DB LastAppliedIndex: %s%d%s\n", ColorGreen, idx, ColorReset)
  164. case "stats":
  165. stats := server.GetStats()
  166. health := server.HealthCheck()
  167. fmt.Printf("Node: %s\nState: %s\nLeader: %s\nTerm: %d\nHealthy: %v\n",
  168. health.NodeID, health.State, health.LeaderID, health.Term, health.IsHealthy)
  169. fmt.Printf("CommitIndex: %d, Applied: %d\n", stats.CommitIndex, stats.LastApplied)
  170. fmt.Printf("Cluster Size: %d\n", stats.ClusterSize)
  171. fmt.Println("Cluster Nodes:")
  172. for id, addr := range stats.ClusterNodes {
  173. fmt.Printf(" - %s: %s\n", id, addr)
  174. }
  175. case "join":
  176. if len(parts) != 3 {
  177. fmt.Println("Usage: join <nodeID> <addr>")
  178. break
  179. }
  180. if err := server.Join(parts[1], parts[2]); err != nil {
  181. fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
  182. } else {
  183. fmt.Printf("%sJoined node %s%s\n", ColorGreen, parts[1], ColorReset)
  184. }
  185. case "leave":
  186. if len(parts) != 2 {
  187. fmt.Println("Usage: leave <nodeID>")
  188. break
  189. }
  190. if err := server.Leave(parts[1]); err != nil {
  191. fmt.Printf("%sError:%s %v\n", ColorRed, ColorReset, err)
  192. } else {
  193. fmt.Printf("%sRemoved node %s%s\n", ColorGreen, parts[1], ColorReset)
  194. }
  195. case "help":
  196. fmt.Println("Commands:")
  197. fmt.Println(" set <key> <val> Set value")
  198. fmt.Println(" get <key> Get value")
  199. fmt.Println(" del <key> Delete key")
  200. fmt.Println(" demodata <n> <pat> Generate n items (e.g. 'demodata 100 user.*')")
  201. fmt.Println(" search <pat> [lim] [off] Search keys (e.g. 'search user.* 10 0')")
  202. fmt.Println(" binlog Show Raft log indices")
  203. fmt.Println(" db Show DB applied index")
  204. fmt.Println(" stats Show node stats")
  205. fmt.Println(" join <id> <addr> Add node to cluster")
  206. fmt.Println(" leave <id> Remove node from cluster")
  207. default:
  208. fmt.Println("Unknown command. Type 'help'.")
  209. }
  210. fmt.Print("> ")
  211. }
  212. }