Skip to content

Commit e75a5db

Browse files
K4ST0Rleibale
andauthored
Add CLIENT LIST command and fix CLIENT INFO (#2368)
* fix client info * add client list * fix key validation in transformClientInfoReply * fix issue with field in CLIENT LIST reply * clean code * fix multimem * fix qbufFree argvMem totMem multiMem Co-authored-by: Leibale <[email protected]>
1 parent 2287efd commit e75a5db

File tree

5 files changed

+223
-87
lines changed

5 files changed

+223
-87
lines changed

packages/client/lib/client/commands.ts

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import * as CLIENT_GETNAME from '../commands/CLIENT_GETNAME';
2121
import * as CLIENT_GETREDIR from '../commands/CLIENT_GETREDIR';
2222
import * as CLIENT_ID from '../commands/CLIENT_ID';
2323
import * as CLIENT_KILL from '../commands/CLIENT_KILL';
24+
import * as CLIENT_LIST from '../commands/CLIENT_LIST';
2425
import * as CLIENT_NO_EVICT from '../commands/CLIENT_NO-EVICT';
2526
import * as CLIENT_PAUSE from '../commands/CLIENT_PAUSE';
2627
import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME';
@@ -164,6 +165,8 @@ export default {
164165
clientKill: CLIENT_KILL,
165166
'CLIENT_NO-EVICT': CLIENT_NO_EVICT,
166167
clientNoEvict: CLIENT_NO_EVICT,
168+
CLIENT_LIST,
169+
clientList: CLIENT_LIST,
167170
CLIENT_PAUSE,
168171
clientPause: CLIENT_PAUSE,
169172
CLIENT_SETNAME,
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,50 @@
11
import { strict as assert } from 'assert';
22
import { transformArguments, transformReply } from './CLIENT_INFO';
3+
import testUtils, { GLOBAL } from '../test-utils';
34

45
describe('CLIENT INFO', () => {
6+
testUtils.isVersionGreaterThanHook([6, 2]);
7+
58
it('transformArguments', () => {
69
assert.deepEqual(
710
transformArguments(),
811
['CLIENT', 'INFO']
912
);
1013
});
1114

12-
it('transformReply', () => {
13-
assert.deepEqual(
14-
transformReply('id=526512 addr=127.0.0.1:36244 laddr=127.0.0.1:6379 fd=8 name= age=11213 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=40928 argv-mem=10 obl=0 oll=0 omem=0 tot-mem=61466 events=r cmd=client user=default redir=-1\n'),
15-
{
16-
id: 526512,
17-
addr: '127.0.0.1:36244',
18-
laddr: '127.0.0.1:6379',
19-
fd: 8,
20-
name: '',
21-
age: 11213,
22-
idle: 0,
23-
flags: 'N',
24-
db: 0,
25-
sub: 0,
26-
psub: 0,
27-
multi: -1,
28-
qbuf: 26,
29-
qbufFree: 40928,
30-
argvMem: 10,
31-
obl: 0,
32-
oll: 0,
33-
omem: 0,
34-
totMem: 61466,
35-
events: 'r',
36-
cmd: 'client',
37-
user: 'default',
38-
redir: -1
39-
}
40-
);
41-
});
15+
testUtils.testWithClient('client.clientInfo', async client => {
16+
const reply = await client.clientInfo();
17+
assert.equal(typeof reply.id, 'number');
18+
assert.equal(typeof reply.addr, 'string');
19+
assert.equal(typeof reply.laddr, 'string');
20+
assert.equal(typeof reply.fd, 'number');
21+
assert.equal(typeof reply.name, 'string');
22+
assert.equal(typeof reply.age, 'number');
23+
assert.equal(typeof reply.idle, 'number');
24+
assert.equal(typeof reply.flags, 'string');
25+
assert.equal(typeof reply.db, 'number');
26+
assert.equal(typeof reply.sub, 'number');
27+
assert.equal(typeof reply.psub, 'number');
28+
assert.equal(typeof reply.multi, 'number');
29+
assert.equal(typeof reply.qbuf, 'number');
30+
assert.equal(typeof reply.qbufFree, 'number');
31+
assert.equal(typeof reply.argvMem, 'number');
32+
assert.equal(typeof reply.obl, 'number');
33+
assert.equal(typeof reply.oll, 'number');
34+
assert.equal(typeof reply.omem, 'number');
35+
assert.equal(typeof reply.totMem, 'number');
36+
assert.equal(typeof reply.events, 'string');
37+
assert.equal(typeof reply.cmd, 'string');
38+
assert.equal(typeof reply.user, 'string');
39+
assert.equal(typeof reply.redir, 'number');
40+
41+
if (testUtils.isVersionGreaterThan([7, 0])) {
42+
assert.equal(typeof reply.multiMem, 'number');
43+
assert.equal(typeof reply.resp, 'number');
44+
}
45+
46+
if (testUtils.isVersionGreaterThan([7, 0, 3])) {
47+
assert.equal(typeof reply.ssub, 'number');
48+
}
49+
}, GLOBAL.SERVERS.OPEN);
4250
});
+61-57
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
export const IS_READ_ONLY = true;
2+
13
export function transformArguments(): Array<string> {
24
return ['CLIENT', 'INFO'];
35
}
46

5-
interface ClientInfoReply {
7+
export interface ClientInfoReply {
68
id: number;
79
addr: string;
8-
laddr: string;
10+
laddr?: string; // 6.2
911
fd: number;
1012
name: string;
1113
age: number;
@@ -14,72 +16,74 @@ interface ClientInfoReply {
1416
db: number;
1517
sub: number;
1618
psub: number;
19+
ssub?: number; // 7.0.3
1720
multi: number;
1821
qbuf: number;
1922
qbufFree: number;
20-
argvMem: number;
23+
argvMem?: number; // 6.0
24+
multiMem?: number; // 7.0
2125
obl: number;
2226
oll: number;
2327
omem: number;
24-
totMem: number;
28+
totMem?: number; // 6.0
2529
events: string;
2630
cmd: string;
27-
user: string;
28-
redir: number;
31+
user?: string; // 6.0
32+
redir?: number; // 6.2
33+
resp?: number; // 7.0
2934
}
3035

31-
const REGEX = /=([^\s]*)/g;
36+
const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g;
3237

33-
export function transformReply(reply: string): ClientInfoReply {
34-
const [
35-
[, id],
36-
[, addr],
37-
[, laddr],
38-
[, fd],
39-
[, name],
40-
[, age],
41-
[, idle],
42-
[, flags],
43-
[, db],
44-
[, sub],
45-
[, psub],
46-
[, multi],
47-
[, qbuf],
48-
[, qbufFree],
49-
[, argvMem],
50-
[, obl],
51-
[, oll],
52-
[, omem],
53-
[, totMem],
54-
[, events],
55-
[, cmd],
56-
[, user],
57-
[, redir]
58-
] = [...reply.matchAll(REGEX)];
38+
export function transformReply(rawReply: string): ClientInfoReply {
39+
const map: Record<string, string> = {};
40+
for (const item of rawReply.matchAll(CLIENT_INFO_REGEX)) {
41+
map[item[1]] = item[2];
42+
}
5943

60-
return {
61-
id: Number(id),
62-
addr,
63-
laddr,
64-
fd: Number(fd),
65-
name,
66-
age: Number(age),
67-
idle: Number(idle),
68-
flags,
69-
db: Number(db),
70-
sub: Number(sub),
71-
psub: Number(psub),
72-
multi: Number(multi),
73-
qbuf: Number(qbuf),
74-
qbufFree: Number(qbufFree),
75-
argvMem: Number(argvMem),
76-
obl: Number(obl),
77-
oll: Number(oll),
78-
omem: Number(omem),
79-
totMem: Number(totMem),
80-
events,
81-
cmd,
82-
user,
83-
redir: Number(redir)
44+
const reply: ClientInfoReply = {
45+
id: Number(map.id),
46+
addr: map.addr,
47+
fd: Number(map.fd),
48+
name: map.name,
49+
age: Number(map.age),
50+
idle: Number(map.idle),
51+
flags: map.flags,
52+
db: Number(map.db),
53+
sub: Number(map.sub),
54+
psub: Number(map.psub),
55+
multi: Number(map.multi),
56+
qbuf: Number(map.qbuf),
57+
qbufFree: Number(map['qbuf-free']),
58+
argvMem: Number(map['argv-mem']),
59+
obl: Number(map.obl),
60+
oll: Number(map.oll),
61+
omem: Number(map.omem),
62+
totMem: Number(map['tot-mem']),
63+
events: map.events,
64+
cmd: map.cmd,
65+
user: map.user
8466
};
67+
68+
if (map.laddr !== undefined) {
69+
reply.laddr = map.laddr;
70+
}
71+
72+
if (map.redir !== undefined) {
73+
reply.redir = Number(map.redir);
74+
}
75+
76+
if (map.ssub !== undefined) {
77+
reply.ssub = Number(map.ssub);
78+
}
79+
80+
if (map['multi-mem'] !== undefined) {
81+
reply.multiMem = Number(map['multi-mem']);
82+
}
83+
84+
if (map.resp !== undefined) {
85+
reply.resp = Number(map.resp);
86+
}
87+
88+
return reply;
8589
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { strict as assert } from 'assert';
2+
import { transformArguments, transformReply } from './CLIENT_LIST';
3+
import testUtils, { GLOBAL } from '../test-utils';
4+
5+
describe('CLIENT LIST', () => {
6+
describe('transformArguments', () => {
7+
it('simple', () => {
8+
assert.deepEqual(
9+
transformArguments(),
10+
['CLIENT', 'LIST']
11+
);
12+
});
13+
14+
it('with TYPE', () => {
15+
assert.deepEqual(
16+
transformArguments({
17+
TYPE: 'NORMAL'
18+
}),
19+
['CLIENT', 'LIST', 'TYPE', 'NORMAL']
20+
);
21+
});
22+
23+
it('with ID', () => {
24+
assert.deepEqual(
25+
transformArguments({
26+
ID: ['1', '2']
27+
}),
28+
['CLIENT', 'LIST', 'ID', '1', '2']
29+
);
30+
});
31+
});
32+
33+
testUtils.testWithClient('client.clientList', async client => {
34+
const reply = await client.clientList();
35+
assert.ok(Array.isArray(reply));
36+
37+
for (const item of reply) {
38+
assert.equal(typeof item.id, 'number');
39+
assert.equal(typeof item.addr, 'string');
40+
assert.equal(typeof item.fd, 'number');
41+
assert.equal(typeof item.name, 'string');
42+
assert.equal(typeof item.age, 'number');
43+
assert.equal(typeof item.idle, 'number');
44+
assert.equal(typeof item.flags, 'string');
45+
assert.equal(typeof item.db, 'number');
46+
assert.equal(typeof item.sub, 'number');
47+
assert.equal(typeof item.psub, 'number');
48+
assert.equal(typeof item.multi, 'number');
49+
assert.equal(typeof item.qbuf, 'number');
50+
assert.equal(typeof item.qbufFree, 'number');
51+
assert.equal(typeof item.obl, 'number');
52+
assert.equal(typeof item.oll, 'number');
53+
assert.equal(typeof item.omem, 'number');
54+
assert.equal(typeof item.events, 'string');
55+
assert.equal(typeof item.cmd, 'string');
56+
57+
if (testUtils.isVersionGreaterThan([6, 0])) {
58+
assert.equal(typeof item.argvMem, 'number');
59+
assert.equal(typeof item.totMem, 'number');
60+
assert.equal(typeof item.user, 'string');
61+
}
62+
63+
if (testUtils.isVersionGreaterThan([6, 2])) {
64+
assert.equal(typeof item.redir, 'number');
65+
assert.equal(typeof item.laddr, 'string');
66+
}
67+
68+
if (testUtils.isVersionGreaterThan([7, 0])) {
69+
assert.equal(typeof item.multiMem, 'number');
70+
assert.equal(typeof item.resp, 'number');
71+
}
72+
73+
if (testUtils.isVersionGreaterThan([7, 0, 3])) {
74+
assert.equal(typeof item.ssub, 'number');
75+
}
76+
}
77+
}, GLOBAL.SERVERS.OPEN);
78+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { RedisCommandArguments, RedisCommandArgument } from '.';
2+
import { pushVerdictArguments } from './generic-transformers';
3+
import { transformReply as transformClientInfoReply, ClientInfoReply } from './CLIENT_INFO';
4+
5+
interface ListFilterType {
6+
TYPE: 'NORMAL' | 'MASTER' | 'REPLICA' | 'PUBSUB';
7+
ID?: never;
8+
}
9+
10+
interface ListFilterId {
11+
ID: Array<RedisCommandArgument>;
12+
TYPE?: never;
13+
}
14+
15+
export type ListFilter = ListFilterType | ListFilterId;
16+
17+
export const IS_READ_ONLY = true;
18+
19+
export function transformArguments(filter?: ListFilter): RedisCommandArguments {
20+
let args: RedisCommandArguments = ['CLIENT', 'LIST'];
21+
22+
if (filter) {
23+
if (filter.TYPE !== undefined) {
24+
args.push('TYPE', filter.TYPE);
25+
} else {
26+
args.push('ID');
27+
args = pushVerdictArguments(args, filter.ID);
28+
}
29+
}
30+
31+
return args;
32+
}
33+
34+
export function transformReply(rawReply: string): Array<ClientInfoReply> {
35+
const split = rawReply.split('\n'),
36+
length = split.length - 1,
37+
reply: Array<ClientInfoReply> = [];
38+
for (let i = 0; i < length; i++) {
39+
reply.push(transformClientInfoReply(split[i]));
40+
}
41+
42+
return reply;
43+
}

0 commit comments

Comments
 (0)