Skip to content

Commit b8c7317

Browse files
authored
feat: add client info command (#2483)
* feat: add client info command Signed-off-by: monkey92t <[email protected]> * add lib-name, lib-ver Signed-off-by: monkey92t <[email protected]> --------- Signed-off-by: monkey92t <[email protected]>
1 parent 6ecbcf6 commit b8c7317

File tree

3 files changed

+288
-0
lines changed

3 files changed

+288
-0
lines changed

Diff for: command.go

+273
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net"
77
"strconv"
8+
"strings"
89
"time"
910

1011
"github.com/redis/go-redis/v9/internal"
@@ -4784,3 +4785,275 @@ func (cmd *RankWithScoreCmd) readReply(rd *proto.Reader) error {
47844785

47854786
return nil
47864787
}
4788+
4789+
// --------------------------------------------------------------------------------------------------
4790+
4791+
// ClientFlags is redis-server client flags, copy from redis/src/server.h (redis 7.0)
4792+
type ClientFlags uint64
4793+
4794+
const (
4795+
ClientSlave ClientFlags = 1 << 0 /* This client is a replica */
4796+
ClientMaster ClientFlags = 1 << 1 /* This client is a master */
4797+
ClientMonitor ClientFlags = 1 << 2 /* This client is a slave monitor, see MONITOR */
4798+
ClientMulti ClientFlags = 1 << 3 /* This client is in a MULTI context */
4799+
ClientBlocked ClientFlags = 1 << 4 /* The client is waiting in a blocking operation */
4800+
ClientDirtyCAS ClientFlags = 1 << 5 /* Watched keys modified. EXEC will fail. */
4801+
ClientCloseAfterReply ClientFlags = 1 << 6 /* Close after writing entire reply. */
4802+
ClientUnBlocked ClientFlags = 1 << 7 /* This client was unblocked and is stored in server.unblocked_clients */
4803+
ClientScript ClientFlags = 1 << 8 /* This is a non-connected client used by Lua */
4804+
ClientAsking ClientFlags = 1 << 9 /* Client issued the ASKING command */
4805+
ClientCloseASAP ClientFlags = 1 << 10 /* Close this client ASAP */
4806+
ClientUnixSocket ClientFlags = 1 << 11 /* Client connected via Unix domain socket */
4807+
ClientDirtyExec ClientFlags = 1 << 12 /* EXEC will fail for errors while queueing */
4808+
ClientMasterForceReply ClientFlags = 1 << 13 /* Queue replies even if is master */
4809+
ClientForceAOF ClientFlags = 1 << 14 /* Force AOF propagation of current cmd. */
4810+
ClientForceRepl ClientFlags = 1 << 15 /* Force replication of current cmd. */
4811+
ClientPrePSync ClientFlags = 1 << 16 /* Instance don't understand PSYNC. */
4812+
ClientReadOnly ClientFlags = 1 << 17 /* Cluster client is in read-only state. */
4813+
ClientPubSub ClientFlags = 1 << 18 /* Client is in Pub/Sub mode. */
4814+
ClientPreventAOFProp ClientFlags = 1 << 19 /* Don't propagate to AOF. */
4815+
ClientPreventReplProp ClientFlags = 1 << 20 /* Don't propagate to slaves. */
4816+
ClientPreventProp ClientFlags = ClientPreventAOFProp | ClientPreventReplProp
4817+
ClientPendingWrite ClientFlags = 1 << 21 /* Client has output to send but a-write handler is yet not installed. */
4818+
ClientReplyOff ClientFlags = 1 << 22 /* Don't send replies to client. */
4819+
ClientReplySkipNext ClientFlags = 1 << 23 /* Set ClientREPLY_SKIP for next cmd */
4820+
ClientReplySkip ClientFlags = 1 << 24 /* Don't send just this reply. */
4821+
ClientLuaDebug ClientFlags = 1 << 25 /* Run EVAL in debug mode. */
4822+
ClientLuaDebugSync ClientFlags = 1 << 26 /* EVAL debugging without fork() */
4823+
ClientModule ClientFlags = 1 << 27 /* Non connected client used by some module. */
4824+
ClientProtected ClientFlags = 1 << 28 /* Client should not be freed for now. */
4825+
ClientExecutingCommand ClientFlags = 1 << 29 /* Indicates that the client is currently in the process of handling
4826+
a command. usually this will be marked only during call()
4827+
however, blocked clients might have this flag kept until they
4828+
will try to reprocess the command. */
4829+
ClientPendingCommand ClientFlags = 1 << 30 /* Indicates the client has a fully * parsed command ready for execution. */
4830+
ClientTracking ClientFlags = 1 << 31 /* Client enabled keys tracking in order to perform client side caching. */
4831+
ClientTrackingBrokenRedir ClientFlags = 1 << 32 /* Target client is invalid. */
4832+
ClientTrackingBCAST ClientFlags = 1 << 33 /* Tracking in BCAST mode. */
4833+
ClientTrackingOptIn ClientFlags = 1 << 34 /* Tracking in opt-in mode. */
4834+
ClientTrackingOptOut ClientFlags = 1 << 35 /* Tracking in opt-out mode. */
4835+
ClientTrackingCaching ClientFlags = 1 << 36 /* CACHING yes/no was given, depending on optin/optout mode. */
4836+
ClientTrackingNoLoop ClientFlags = 1 << 37 /* Don't send invalidation messages about writes performed by myself.*/
4837+
ClientInTimeoutTable ClientFlags = 1 << 38 /* This client is in the timeout table. */
4838+
ClientProtocolError ClientFlags = 1 << 39 /* Protocol error chatting with it. */
4839+
ClientCloseAfterCommand ClientFlags = 1 << 40 /* Close after executing commands * and writing entire reply. */
4840+
ClientDenyBlocking ClientFlags = 1 << 41 /* Indicate that the client should not be blocked. currently, turned on inside MULTI, Lua, RM_Call, and AOF client */
4841+
ClientReplRDBOnly ClientFlags = 1 << 42 /* This client is a replica that only wants RDB without replication buffer. */
4842+
ClientNoEvict ClientFlags = 1 << 43 /* This client is protected against client memory eviction. */
4843+
ClientAllowOOM ClientFlags = 1 << 44 /* Client used by RM_Call is allowed to fully execute scripts even when in OOM */
4844+
ClientNoTouch ClientFlags = 1 << 45 /* This client will not touch LFU/LRU stats. */
4845+
ClientPushing ClientFlags = 1 << 46 /* This client is pushing notifications. */
4846+
)
4847+
4848+
// ClientInfo is redis-server ClientInfo, not go-redis *Client
4849+
type ClientInfo struct {
4850+
ID int64 // redis version 2.8.12, a unique 64-bit client ID
4851+
Addr string // address/port of the client
4852+
LAddr string // address/port of local address client connected to (bind address)
4853+
FD int64 // file descriptor corresponding to the socket
4854+
Name string // the name set by the client with CLIENT SETNAME
4855+
Age time.Duration // total duration of the connection in seconds
4856+
Idle time.Duration // idle time of the connection in seconds
4857+
Flags ClientFlags // client flags (see below)
4858+
DB int // current database ID
4859+
Sub int // number of channel subscriptions
4860+
PSub int // number of pattern matching subscriptions
4861+
SSub int // redis version 7.0.3, number of shard channel subscriptions
4862+
Multi int // number of commands in a MULTI/EXEC context
4863+
QueryBuf int // qbuf, query buffer length (0 means no query pending)
4864+
QueryBufFree int // qbuf-free, free space of the query buffer (0 means the buffer is full)
4865+
ArgvMem int // incomplete arguments for the next command (already extracted from query buffer)
4866+
MultiMem int // redis version 7.0, memory is used up by buffered multi commands
4867+
BufferSize int // rbs, usable size of buffer
4868+
BufferPeak int // rbp, peak used size of buffer in last 5 sec interval
4869+
OutputBufferLength int // obl, output buffer length
4870+
OutputListLength int // oll, output list length (replies are queued in this list when the buffer is full)
4871+
OutputMemory int // omem, output buffer memory usage
4872+
TotalMemory int // tot-mem, total memory consumed by this client in its various buffers
4873+
Events string // file descriptor events (see below)
4874+
LastCmd string // cmd, last command played
4875+
User string // the authenticated username of the client
4876+
Redir int64 // client id of current client tracking redirection
4877+
Resp int // redis version 7.0, client RESP protocol version
4878+
LibName string // redis version 7.2, client library name
4879+
LibVer string // redis version 7.2, client library version
4880+
}
4881+
4882+
type ClientInfoCmd struct {
4883+
baseCmd
4884+
4885+
val *ClientInfo
4886+
}
4887+
4888+
var _ Cmder = (*ClientInfoCmd)(nil)
4889+
4890+
func NewClientInfoCmd(ctx context.Context, args ...interface{}) *ClientInfoCmd {
4891+
return &ClientInfoCmd{
4892+
baseCmd: baseCmd{
4893+
ctx: ctx,
4894+
args: args,
4895+
},
4896+
}
4897+
}
4898+
4899+
func (cmd *ClientInfoCmd) SetVal(val *ClientInfo) {
4900+
cmd.val = val
4901+
}
4902+
4903+
func (cmd *ClientInfoCmd) String() string {
4904+
return cmdString(cmd, cmd.val)
4905+
}
4906+
4907+
func (cmd *ClientInfoCmd) Val() *ClientInfo {
4908+
return cmd.val
4909+
}
4910+
4911+
func (cmd *ClientInfoCmd) Result() (*ClientInfo, error) {
4912+
return cmd.val, cmd.err
4913+
}
4914+
4915+
func (cmd *ClientInfoCmd) readReply(rd *proto.Reader) (err error) {
4916+
txt, err := rd.ReadString()
4917+
if err != nil {
4918+
return err
4919+
}
4920+
4921+
// sds o = catClientInfoString(sdsempty(), c);
4922+
// o = sdscatlen(o,"\n",1);
4923+
// addReplyVerbatim(c,o,sdslen(o),"txt");
4924+
// sdsfree(o);
4925+
cmd.val, err = parseClientInfo(strings.TrimSpace(txt))
4926+
return err
4927+
}
4928+
4929+
// fmt.Sscanf() cannot handle null values
4930+
func parseClientInfo(txt string) (info *ClientInfo, err error) {
4931+
info = &ClientInfo{}
4932+
for _, s := range strings.Split(txt, " ") {
4933+
kv := strings.Split(s, "=")
4934+
if len(kv) != 2 {
4935+
return nil, fmt.Errorf("redis: unexpected client info data (%s)", s)
4936+
}
4937+
key, val := kv[0], kv[1]
4938+
4939+
switch key {
4940+
case "id":
4941+
info.ID, err = strconv.ParseInt(val, 10, 64)
4942+
case "addr":
4943+
info.Addr = val
4944+
case "laddr":
4945+
info.LAddr = val
4946+
case "fd":
4947+
info.FD, err = strconv.ParseInt(val, 10, 64)
4948+
case "name":
4949+
info.Name = val
4950+
case "age":
4951+
var age int
4952+
if age, err = strconv.Atoi(val); err == nil {
4953+
info.Age = time.Duration(age) * time.Second
4954+
}
4955+
case "idle":
4956+
var idle int
4957+
if idle, err = strconv.Atoi(val); err == nil {
4958+
info.Idle = time.Duration(idle) * time.Second
4959+
}
4960+
case "flags":
4961+
if val == "N" {
4962+
break
4963+
}
4964+
4965+
for i := 0; i < len(val); i++ {
4966+
switch val[i] {
4967+
case 'S':
4968+
info.Flags |= ClientSlave
4969+
case 'O':
4970+
info.Flags |= ClientSlave | ClientMonitor
4971+
case 'M':
4972+
info.Flags |= ClientMaster
4973+
case 'P':
4974+
info.Flags |= ClientPubSub
4975+
case 'x':
4976+
info.Flags |= ClientMulti
4977+
case 'b':
4978+
info.Flags |= ClientBlocked
4979+
case 't':
4980+
info.Flags |= ClientTracking
4981+
case 'R':
4982+
info.Flags |= ClientTrackingBrokenRedir
4983+
case 'B':
4984+
info.Flags |= ClientTrackingBCAST
4985+
case 'd':
4986+
info.Flags |= ClientDirtyCAS
4987+
case 'c':
4988+
info.Flags |= ClientCloseAfterCommand
4989+
case 'u':
4990+
info.Flags |= ClientUnBlocked
4991+
case 'A':
4992+
info.Flags |= ClientCloseASAP
4993+
case 'U':
4994+
info.Flags |= ClientUnixSocket
4995+
case 'r':
4996+
info.Flags |= ClientReadOnly
4997+
case 'e':
4998+
info.Flags |= ClientNoEvict
4999+
case 'T':
5000+
info.Flags |= ClientNoTouch
5001+
default:
5002+
return nil, fmt.Errorf("redis: unexpected client info flags(%s)", string(val[i]))
5003+
}
5004+
}
5005+
case "db":
5006+
info.DB, err = strconv.Atoi(val)
5007+
case "sub":
5008+
info.Sub, err = strconv.Atoi(val)
5009+
case "psub":
5010+
info.PSub, err = strconv.Atoi(val)
5011+
case "ssub":
5012+
info.SSub, err = strconv.Atoi(val)
5013+
case "multi":
5014+
info.Multi, err = strconv.Atoi(val)
5015+
case "qbuf":
5016+
info.QueryBuf, err = strconv.Atoi(val)
5017+
case "qbuf-free":
5018+
info.QueryBufFree, err = strconv.Atoi(val)
5019+
case "argv-mem":
5020+
info.ArgvMem, err = strconv.Atoi(val)
5021+
case "multi-mem":
5022+
info.MultiMem, err = strconv.Atoi(val)
5023+
case "rbs":
5024+
info.BufferSize, err = strconv.Atoi(val)
5025+
case "rbp":
5026+
info.BufferPeak, err = strconv.Atoi(val)
5027+
case "obl":
5028+
info.OutputBufferLength, err = strconv.Atoi(val)
5029+
case "oll":
5030+
info.OutputListLength, err = strconv.Atoi(val)
5031+
case "omem":
5032+
info.OutputMemory, err = strconv.Atoi(val)
5033+
case "tot-mem":
5034+
info.TotalMemory, err = strconv.Atoi(val)
5035+
case "events":
5036+
info.Events = val
5037+
case "cmd":
5038+
info.LastCmd = val
5039+
case "user":
5040+
info.User = val
5041+
case "redir":
5042+
info.Redir, err = strconv.ParseInt(val, 10, 64)
5043+
case "resp":
5044+
info.Resp, err = strconv.Atoi(val)
5045+
case "lib-name":
5046+
info.LibName = val
5047+
case "lib-ver":
5048+
info.LibVer = val
5049+
default:
5050+
return nil, fmt.Errorf("redis: unexpected client info key(%s)", key)
5051+
}
5052+
5053+
if err != nil {
5054+
return nil, err
5055+
}
5056+
}
5057+
5058+
return info, nil
5059+
}

Diff for: commands.go

+9
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ type Cmdable interface {
404404
ClientKill(ctx context.Context, ipPort string) *StatusCmd
405405
ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd
406406
ClientList(ctx context.Context) *StringCmd
407+
ClientInfo(ctx context.Context) *ClientInfoCmd
407408
ClientPause(ctx context.Context, dur time.Duration) *BoolCmd
408409
ClientUnpause(ctx context.Context) *BoolCmd
409410
ClientID(ctx context.Context) *IntCmd
@@ -3192,6 +3193,14 @@ func (c cmdable) ClientUnblockWithError(ctx context.Context, id int64) *IntCmd {
31923193
return cmd
31933194
}
31943195

3196+
func (c cmdable) ClientInfo(ctx context.Context) *ClientInfoCmd {
3197+
cmd := NewClientInfoCmd(ctx, "client", "info")
3198+
_ = c(ctx, cmd)
3199+
return cmd
3200+
}
3201+
3202+
// ------------------------------------------------------------------------------------------------
3203+
31953204
func (c cmdable) ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd {
31963205
cmd := NewMapStringStringCmd(ctx, "config", "get", parameter)
31973206
_ = c(ctx, cmd)

Diff for: commands_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ var _ = Describe("Commands", func() {
201201
Expect(r).To(Equal(int64(0)))
202202
})
203203

204+
It("should ClientInfo", func() {
205+
info, err := client.ClientInfo(ctx).Result()
206+
Expect(err).NotTo(HaveOccurred())
207+
Expect(info).NotTo(BeNil())
208+
})
209+
204210
It("should ClientPause", func() {
205211
err := client.ClientPause(ctx, time.Second).Err()
206212
Expect(err).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)