diff --git a/cluster/cluster.go b/cluster/cluster.go index 9d800384..da027809 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -136,7 +136,9 @@ func (cluster *Cluster) Exec(c redis.Connection, cmdLine [][]byte) (result redis }() cmdName := strings.ToLower(string(cmdLine[0])) if cmdName == "info" { - return database2.Info(c, cmdLine) + if ser, ok := cluster.db.(*database2.Server); ok { + return database2.Info(ser, cmdLine[1:]) + } } if cmdName == "auth" { return database2.Auth(c, cmdLine[1:]) diff --git a/database/server.go b/database/server.go index f93a9d9a..b74b94b2 100644 --- a/database/server.go +++ b/database/server.go @@ -111,7 +111,7 @@ func (server *Server) Exec(c redis.Connection, cmdLine [][]byte) (result redis.R } // info if cmdName == "info" { - return Info(c, cmdLine) + return Info(server, cmdLine[1:]) } if cmdName == "slaveof" { if c != nil && c.InMultiState() { @@ -388,3 +388,23 @@ func (server *Server) startReplCron() { } }(server) } + +// GetAvgTTL Calculate the average expiration time of keys +func (server *Server) GetAvgTTL(dbIndex, randomKeyCount int) int64 { + var ttlCount int64 + db := server.mustSelectDB(dbIndex) + keys := db.data.RandomKeys(randomKeyCount) + for _, k := range keys { + t := time.Now() + rawExpireTime, ok := db.ttlMap.Get(k) + if !ok { + continue + } + expireTime, _ := rawExpireTime.(time.Time) + // if the key has already reached its expiration time during calculation, ignore it + if expireTime.Sub(t).Microseconds() > 0 { + ttlCount += expireTime.Sub(t).Microseconds() + } + } + return ttlCount / int64(len(keys)) +} diff --git a/database/systemcmd.go b/database/systemcmd.go index bcd90548..17735c5b 100644 --- a/database/systemcmd.go +++ b/database/systemcmd.go @@ -24,30 +24,31 @@ func Ping(c redis.Connection, args [][]byte) redis.Reply { } // Info the information of the godis server returned by the INFO command -func Info(c redis.Connection, args [][]byte) redis.Reply { - if len(args) == 1 { - infoCommandList := [...]string{"server", "client", "cluster"} +func Info(db *Server, args [][]byte) redis.Reply { + if len(args) == 0 { + infoCommandList := [...]string{"server", "client", "cluster", "keyspace"} var allSection []byte for _, s := range infoCommandList { - allSection = append(allSection, GenGodisInfoString(s)...) + allSection = append(allSection, GenGodisInfoString(s, db)...) } - return protocol.MakeBulkReply(allSection) - } else if len(args) == 2 { - section := strings.ToLower(string(args[1])) + } else if len(args) == 1 { + section := strings.ToLower(string(args[0])) switch section { case "server": - reply := GenGodisInfoString("server") + reply := GenGodisInfoString("server", db) return protocol.MakeBulkReply(reply) case "client": - return protocol.MakeBulkReply(GenGodisInfoString("client")) + return protocol.MakeBulkReply(GenGodisInfoString("client", db)) case "cluster": - return protocol.MakeBulkReply(GenGodisInfoString("cluster")) + return protocol.MakeBulkReply(GenGodisInfoString("cluster", db)) + case "keyspace": + return protocol.MakeBulkReply(GenGodisInfoString("keyspace", db)) default: - return protocol.MakeNullBulkReply() + return protocol.MakeErrReply("Invalid section for 'info' command") } } - return protocol.MakeErrReply("ERR wrong number of arguments for 'info' command") + return protocol.MakeArgNumErrReply("info") } // Auth validate client's password @@ -73,8 +74,8 @@ func isAuthenticated(c redis.Connection) bool { return c.GetPassword() == config.Properties.RequirePass } -func GenGodisInfoString(section string) []byte { - startUpTimeFromNow := getGodisRunningTime() +func GenGodisInfoString(section string, db *Server) []byte { + startUpTimeFromNow := getGodisRuninngTime() switch section { case "server": s := fmt.Sprintf("# Server\r\n"+ @@ -139,8 +140,20 @@ func GenGodisInfoString(section string) []byte { ) return []byte(s) } + case "keyspace": + dbCount := config.Properties.Databases + var serv []byte + for i := 0; i < dbCount; i++ { + keys, expiresKeys := db.GetDBSize(i) + if keys != 0 { + ttlSampleAverage := db.GetAvgTTL(i, 20) + serv = append(serv, getDbSize(i, keys, expiresKeys, ttlSampleAverage)...) + } + } + prefix := []byte("# Keyspace\r\n") + keyspaceInfo := append(prefix, serv...) + return keyspaceInfo } - return []byte("") } @@ -153,7 +166,13 @@ func getGodisRunningMode() string { } } -// getGodisRunningTime return the running time of godis -func getGodisRunningTime() time.Duration { +// getGodisRuninngTime return the running time of godis +func getGodisRuninngTime() time.Duration { return time.Since(config.EachTimeServerInfo.StartUpTime) / time.Second } + +func getDbSize(dbIndex, keys, expiresKeys int, ttl int64) []byte { + s := fmt.Sprintf("db%d:keys=%d,expires=%d,avg_ttl=%d\r\n", + dbIndex, keys, expiresKeys, ttl) + return []byte(s) +} diff --git a/database/systemcmd_test.go b/database/systemcmd_test.go index a0f8c6a8..6fc08427 100644 --- a/database/systemcmd_test.go +++ b/database/systemcmd_test.go @@ -52,8 +52,10 @@ func TestInfo(t *testing.T) { asserts.AssertNotError(t, ret) ret = testServer.Exec(c, utils.ToCmdLine("iNFO", "SeRvEr")) asserts.AssertNotError(t, ret) + ret = testServer.Exec(c, utils.ToCmdLine("INFO", "Keyspace")) + asserts.AssertNotError(t, ret) ret = testServer.Exec(c, utils.ToCmdLine("iNFO", "abc", "bde")) asserts.AssertErrReply(t, ret, "ERR wrong number of arguments for 'info' command") ret = testServer.Exec(c, utils.ToCmdLine("INFO", "abc")) - asserts.AssertNullBulk(t, ret) + asserts.AssertErrReply(t, ret, "Invalid section for 'info' command") }