Skip to content

Commit edf8d14

Browse files
committed
feat: add client info command
Signed-off-by: monkey92t <[email protected]>
1 parent 30a6f71 commit edf8d14

File tree

3 files changed

+282
-0
lines changed

3 files changed

+282
-0
lines changed

command.go

+267
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"
@@ -3992,3 +3993,269 @@ func (cmd *FunctionListCmd) readFunctions(rd *proto.Reader) ([]Function, error)
39923993
}
39933994
return functions, nil
39943995
}
3996+
3997+
// --------------------------------------------------------------------------------------------------
3998+
3999+
// ClientFlags is redis-server client flags, copy from redis/src/server.h (redis 7.0)
4000+
type ClientFlags uint64
4001+
4002+
const (
4003+
ClientSlave ClientFlags = 1 << 0 /* This client is a replica */
4004+
ClientMaster ClientFlags = 1 << 1 /* This client is a master */
4005+
ClientMonitor ClientFlags = 1 << 2 /* This client is a slave monitor, see MONITOR */
4006+
ClientMulti ClientFlags = 1 << 3 /* This client is in a MULTI context */
4007+
ClientBlocked ClientFlags = 1 << 4 /* The client is waiting in a blocking operation */
4008+
ClientDirtyCAS ClientFlags = 1 << 5 /* Watched keys modified. EXEC will fail. */
4009+
ClientCloseAfterReply ClientFlags = 1 << 6 /* Close after writing entire reply. */
4010+
ClientUnBlocked ClientFlags = 1 << 7 /* This client was unblocked and is stored in server.unblocked_clients */
4011+
ClientScript ClientFlags = 1 << 8 /* This is a non-connected client used by Lua */
4012+
ClientAsking ClientFlags = 1 << 9 /* Client issued the ASKING command */
4013+
ClientCloseASAP ClientFlags = 1 << 10 /* Close this client ASAP */
4014+
ClientUnixSocket ClientFlags = 1 << 11 /* Client connected via Unix domain socket */
4015+
ClientDirtyExec ClientFlags = 1 << 12 /* EXEC will fail for errors while queueing */
4016+
ClientMasterForceReply ClientFlags = 1 << 13 /* Queue replies even if is master */
4017+
ClientForceAOF ClientFlags = 1 << 14 /* Force AOF propagation of current cmd. */
4018+
ClientForceRepl ClientFlags = 1 << 15 /* Force replication of current cmd. */
4019+
ClientPrePSync ClientFlags = 1 << 16 /* Instance don't understand PSYNC. */
4020+
ClientReadOnly ClientFlags = 1 << 17 /* Cluster client is in read-only state. */
4021+
ClientPubSub ClientFlags = 1 << 18 /* Client is in Pub/Sub mode. */
4022+
ClientPreventAOFProp ClientFlags = 1 << 19 /* Don't propagate to AOF. */
4023+
ClientPreventReplProp ClientFlags = 1 << 20 /* Don't propagate to slaves. */
4024+
ClientPreventProp ClientFlags = ClientPreventAOFProp | ClientPreventReplProp
4025+
ClientPendingWrite ClientFlags = 1 << 21 /* Client has output to send but a-write handler is yet not installed. */
4026+
ClientReplyOff ClientFlags = 1 << 22 /* Don't send replies to client. */
4027+
ClientReplySkipNext ClientFlags = 1 << 23 /* Set ClientREPLY_SKIP for next cmd */
4028+
ClientReplySkip ClientFlags = 1 << 24 /* Don't send just this reply. */
4029+
ClientLuaDebug ClientFlags = 1 << 25 /* Run EVAL in debug mode. */
4030+
ClientLuaDebugSync ClientFlags = 1 << 26 /* EVAL debugging without fork() */
4031+
ClientModule ClientFlags = 1 << 27 /* Non connected client used by some module. */
4032+
ClientProtected ClientFlags = 1 << 28 /* Client should not be freed for now. */
4033+
ClientExecutingCommand ClientFlags = 1 << 29 /* Indicates that the client is currently in the process of handling
4034+
a command. usually this will be marked only during call()
4035+
however, blocked clients might have this flag kept until they
4036+
will try to reprocess the command. */
4037+
ClientPendingCommand ClientFlags = 1 << 30 /* Indicates the client has a fully * parsed command ready for execution. */
4038+
ClientTracking ClientFlags = 1 << 31 /* Client enabled keys tracking in order to perform client side caching. */
4039+
ClientTrackingBrokenRedir ClientFlags = 1 << 32 /* Target client is invalid. */
4040+
ClientTrackingBCAST ClientFlags = 1 << 33 /* Tracking in BCAST mode. */
4041+
ClientTrackingOptIn ClientFlags = 1 << 34 /* Tracking in opt-in mode. */
4042+
ClientTrackingOptOut ClientFlags = 1 << 35 /* Tracking in opt-out mode. */
4043+
ClientTrackingCaching ClientFlags = 1 << 36 /* CACHING yes/no was given, depending on optin/optout mode. */
4044+
ClientTrackingNoLoop ClientFlags = 1 << 37 /* Don't send invalidation messages about writes performed by myself.*/
4045+
ClientInTimeoutTable ClientFlags = 1 << 38 /* This client is in the timeout table. */
4046+
ClientProtocolError ClientFlags = 1 << 39 /* Protocol error chatting with it. */
4047+
ClientCloseAfterCommand ClientFlags = 1 << 40 /* Close after executing commands * and writing entire reply. */
4048+
ClientDenyBlocking ClientFlags = 1 << 41 /* Indicate that the client should not be blocked. currently, turned on inside MULTI, Lua, RM_Call, and AOF client */
4049+
ClientReplRDBOnly ClientFlags = 1 << 42 /* This client is a replica that only wants RDB without replication buffer. */
4050+
ClientNoEvict ClientFlags = 1 << 43 /* This client is protected against client memory eviction. */
4051+
ClientAllowOOM ClientFlags = 1 << 44 /* Client used by RM_Call is allowed to fully execute scripts even when in OOM */
4052+
ClientNoTouch ClientFlags = 1 << 45 /* This client will not touch LFU/LRU stats. */
4053+
ClientPushing ClientFlags = 1 << 46 /* This client is pushing notifications. */
4054+
)
4055+
4056+
// ClientInfo is redis-server ClientInfo, not go-redis *Client
4057+
type ClientInfo struct {
4058+
ID int64 // redis version 2.8.12, a unique 64-bit client ID
4059+
Addr string // address/port of the client
4060+
LAddr string // address/port of local address client connected to (bind address)
4061+
FD int64 // file descriptor corresponding to the socket
4062+
Name string // the name set by the client with CLIENT SETNAME
4063+
Age time.Duration // total duration of the connection in seconds
4064+
Idle time.Duration // idle time of the connection in seconds
4065+
Flags ClientFlags // client flags (see below)
4066+
DB int // current database ID
4067+
Sub int // number of channel subscriptions
4068+
PSub int // number of pattern matching subscriptions
4069+
SSub int // redis version 7.0.3, number of shard channel subscriptions
4070+
Multi int // number of commands in a MULTI/EXEC context
4071+
QueryBuf int // qbuf, query buffer length (0 means no query pending)
4072+
QueryBufFree int // qbuf-free, free space of the query buffer (0 means the buffer is full)
4073+
ArgvMem int // incomplete arguments for the next command (already extracted from query buffer)
4074+
MultiMem int // redis version 7.0, memory is used up by buffered multi commands
4075+
BufferSize int // rbs, usable size of buffer
4076+
BufferPeak int // rbp, peak used size of buffer in last 5 sec interval
4077+
OutputBufferLength int // obl, output buffer length
4078+
OutputListLength int // oll, output list length (replies are queued in this list when the buffer is full)
4079+
OutputMemory int // omem, output buffer memory usage
4080+
TotalMemory int // tot-mem, total memory consumed by this client in its various buffers
4081+
Events string // file descriptor events (see below)
4082+
LastCmd string // cmd, last command played
4083+
User string // the authenticated username of the client
4084+
Redir int64 // client id of current client tracking redirection
4085+
Resp int // redis version 7.0, client RESP protocol version
4086+
}
4087+
4088+
type ClientInfoCmd struct {
4089+
baseCmd
4090+
4091+
val *ClientInfo
4092+
}
4093+
4094+
var _ Cmder = (*ClientInfoCmd)(nil)
4095+
4096+
func NewClientInfoCmd(ctx context.Context, args ...interface{}) *ClientInfoCmd {
4097+
return &ClientInfoCmd{
4098+
baseCmd: baseCmd{
4099+
ctx: ctx,
4100+
args: args,
4101+
},
4102+
}
4103+
}
4104+
4105+
func (cmd *ClientInfoCmd) SetVal(val *ClientInfo) {
4106+
cmd.val = val
4107+
}
4108+
4109+
func (cmd *ClientInfoCmd) String() string {
4110+
return cmdString(cmd, cmd.val)
4111+
}
4112+
4113+
func (cmd *ClientInfoCmd) Val() *ClientInfo {
4114+
return cmd.val
4115+
}
4116+
4117+
func (cmd *ClientInfoCmd) Result() (*ClientInfo, error) {
4118+
return cmd.val, cmd.err
4119+
}
4120+
4121+
func (cmd *ClientInfoCmd) readReply(rd *proto.Reader) (err error) {
4122+
txt, err := rd.ReadString()
4123+
if err != nil {
4124+
return err
4125+
}
4126+
4127+
// sds o = catClientInfoString(sdsempty(), c);
4128+
// o = sdscatlen(o,"\n",1);
4129+
// addReplyVerbatim(c,o,sdslen(o),"txt");
4130+
// sdsfree(o);
4131+
cmd.val, err = parseClientInfo(strings.TrimSpace(txt))
4132+
return err
4133+
}
4134+
4135+
// fmt.Sscanf() cannot handle null values
4136+
func parseClientInfo(txt string) (info *ClientInfo, err error) {
4137+
info = &ClientInfo{}
4138+
for _, s := range strings.Split(txt, " ") {
4139+
kv := strings.Split(s, "=")
4140+
if len(kv) != 2 {
4141+
return nil, fmt.Errorf("redis: unexpected client info data (%s)", s)
4142+
}
4143+
key, val := kv[0], kv[1]
4144+
4145+
switch key {
4146+
case "id":
4147+
info.ID, err = strconv.ParseInt(val, 10, 64)
4148+
case "addr":
4149+
info.Addr = val
4150+
case "laddr":
4151+
info.LAddr = val
4152+
case "fd":
4153+
info.FD, err = strconv.ParseInt(val, 10, 64)
4154+
case "name":
4155+
info.Name = val
4156+
case "age":
4157+
var age int
4158+
if age, err = strconv.Atoi(val); err == nil {
4159+
info.Age = time.Duration(age) * time.Second
4160+
}
4161+
case "idle":
4162+
var idle int
4163+
if idle, err = strconv.Atoi(val); err == nil {
4164+
info.Idle = time.Duration(idle) * time.Second
4165+
}
4166+
case "flags":
4167+
if val == "N" {
4168+
break
4169+
}
4170+
4171+
for i := 0; i < len(val); i++ {
4172+
switch val[i] {
4173+
case 'S':
4174+
info.Flags |= ClientSlave
4175+
case 'O':
4176+
info.Flags |= ClientSlave | ClientMonitor
4177+
case 'M':
4178+
info.Flags |= ClientMaster
4179+
case 'P':
4180+
info.Flags |= ClientPubSub
4181+
case 'x':
4182+
info.Flags |= ClientMulti
4183+
case 'b':
4184+
info.Flags |= ClientBlocked
4185+
case 't':
4186+
info.Flags |= ClientTracking
4187+
case 'R':
4188+
info.Flags |= ClientTrackingBrokenRedir
4189+
case 'B':
4190+
info.Flags |= ClientTrackingBCAST
4191+
case 'd':
4192+
info.Flags |= ClientDirtyCAS
4193+
case 'c':
4194+
info.Flags |= ClientCloseAfterCommand
4195+
case 'u':
4196+
info.Flags |= ClientUnBlocked
4197+
case 'A':
4198+
info.Flags |= ClientCloseASAP
4199+
case 'U':
4200+
info.Flags |= ClientUnixSocket
4201+
case 'r':
4202+
info.Flags |= ClientReadOnly
4203+
case 'e':
4204+
info.Flags |= ClientNoEvict
4205+
case 'T':
4206+
info.Flags |= ClientNoTouch
4207+
default:
4208+
return nil, fmt.Errorf("redis: unexpected client info flags(%s)", string(val[i]))
4209+
}
4210+
}
4211+
case "db":
4212+
info.DB, err = strconv.Atoi(val)
4213+
case "sub":
4214+
info.Sub, err = strconv.Atoi(val)
4215+
case "psub":
4216+
info.PSub, err = strconv.Atoi(val)
4217+
case "ssub":
4218+
info.SSub, err = strconv.Atoi(val)
4219+
case "multi":
4220+
info.Multi, err = strconv.Atoi(val)
4221+
case "qbuf":
4222+
info.QueryBuf, err = strconv.Atoi(val)
4223+
case "qbuf-free":
4224+
info.QueryBufFree, err = strconv.Atoi(val)
4225+
case "argv-mem":
4226+
info.ArgvMem, err = strconv.Atoi(val)
4227+
case "multi-mem":
4228+
info.MultiMem, err = strconv.Atoi(val)
4229+
case "rbs":
4230+
info.BufferSize, err = strconv.Atoi(val)
4231+
case "rbp":
4232+
info.BufferPeak, err = strconv.Atoi(val)
4233+
case "obl":
4234+
info.OutputBufferLength, err = strconv.Atoi(val)
4235+
case "oll":
4236+
info.OutputListLength, err = strconv.Atoi(val)
4237+
case "omem":
4238+
info.OutputMemory, err = strconv.Atoi(val)
4239+
case "tot-mem":
4240+
info.TotalMemory, err = strconv.Atoi(val)
4241+
case "events":
4242+
info.Events = val
4243+
case "cmd":
4244+
info.LastCmd = val
4245+
case "user":
4246+
info.User = val
4247+
case "redir":
4248+
info.Redir, err = strconv.ParseInt(val, 10, 64)
4249+
case "resp":
4250+
info.Resp, err = strconv.Atoi(val)
4251+
default:
4252+
return nil, fmt.Errorf("redis: unexpected client info key(%s)", key)
4253+
}
4254+
4255+
if err != nil {
4256+
return nil, err
4257+
}
4258+
}
4259+
4260+
return info, nil
4261+
}

commands.go

+9
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ type Cmdable interface {
358358
ClientKill(ctx context.Context, ipPort string) *StatusCmd
359359
ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd
360360
ClientList(ctx context.Context) *StringCmd
361+
ClientInfo(ctx context.Context) *ClientInfoCmd
361362
ClientPause(ctx context.Context, dur time.Duration) *BoolCmd
362363
ClientUnpause(ctx context.Context) *BoolCmd
363364
ClientID(ctx context.Context) *IntCmd
@@ -3051,6 +3052,14 @@ func (c cmdable) ClientUnblockWithError(ctx context.Context, id int64) *IntCmd {
30513052
return cmd
30523053
}
30533054

3055+
func (c cmdable) ClientInfo(ctx context.Context) *ClientInfoCmd {
3056+
cmd := NewClientInfoCmd(ctx, "client", "info")
3057+
_ = c(ctx, cmd)
3058+
return cmd
3059+
}
3060+
3061+
// ------------------------------------------------------------------------------------------------
3062+
30543063
func (c cmdable) ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd {
30553064
cmd := NewMapStringStringCmd(ctx, "config", "get", parameter)
30563065
_ = c(ctx, cmd)

commands_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ var _ = Describe("Commands", func() {
164164
Expect(r).To(Equal(int64(0)))
165165
})
166166

167+
It("should ClientInfo", func() {
168+
info, err := client.ClientInfo(ctx).Result()
169+
Expect(err).NotTo(HaveOccurred())
170+
Expect(info).NotTo(BeNil())
171+
})
172+
167173
It("should ClientPause", func() {
168174
err := client.ClientPause(ctx, time.Second).Err()
169175
Expect(err).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)