1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

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 <me@leibale.com>
This commit is contained in:
Ananda
2023-01-25 16:52:59 +01:00
committed by GitHub
parent 2287efdd1e
commit e75a5db3e4
5 changed files with 223 additions and 87 deletions

View File

@@ -21,6 +21,7 @@ import * as CLIENT_GETNAME from '../commands/CLIENT_GETNAME';
import * as CLIENT_GETREDIR from '../commands/CLIENT_GETREDIR'; import * as CLIENT_GETREDIR from '../commands/CLIENT_GETREDIR';
import * as CLIENT_ID from '../commands/CLIENT_ID'; import * as CLIENT_ID from '../commands/CLIENT_ID';
import * as CLIENT_KILL from '../commands/CLIENT_KILL'; import * as CLIENT_KILL from '../commands/CLIENT_KILL';
import * as CLIENT_LIST from '../commands/CLIENT_LIST';
import * as CLIENT_NO_EVICT from '../commands/CLIENT_NO-EVICT'; import * as CLIENT_NO_EVICT from '../commands/CLIENT_NO-EVICT';
import * as CLIENT_PAUSE from '../commands/CLIENT_PAUSE'; import * as CLIENT_PAUSE from '../commands/CLIENT_PAUSE';
import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME'; import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME';
@@ -164,6 +165,8 @@ export default {
clientKill: CLIENT_KILL, clientKill: CLIENT_KILL,
'CLIENT_NO-EVICT': CLIENT_NO_EVICT, 'CLIENT_NO-EVICT': CLIENT_NO_EVICT,
clientNoEvict: CLIENT_NO_EVICT, clientNoEvict: CLIENT_NO_EVICT,
CLIENT_LIST,
clientList: CLIENT_LIST,
CLIENT_PAUSE, CLIENT_PAUSE,
clientPause: CLIENT_PAUSE, clientPause: CLIENT_PAUSE,
CLIENT_SETNAME, CLIENT_SETNAME,

View File

@@ -1,7 +1,10 @@
import { strict as assert } from 'assert'; import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLIENT_INFO'; import { transformArguments, transformReply } from './CLIENT_INFO';
import testUtils, { GLOBAL } from '../test-utils';
describe('CLIENT INFO', () => { describe('CLIENT INFO', () => {
testUtils.isVersionGreaterThanHook([6, 2]);
it('transformArguments', () => { it('transformArguments', () => {
assert.deepEqual( assert.deepEqual(
transformArguments(), transformArguments(),
@@ -9,34 +12,39 @@ describe('CLIENT INFO', () => {
); );
}); });
it('transformReply', () => { testUtils.testWithClient('client.clientInfo', async client => {
assert.deepEqual( const reply = await client.clientInfo();
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'), assert.equal(typeof reply.id, 'number');
{ assert.equal(typeof reply.addr, 'string');
id: 526512, assert.equal(typeof reply.laddr, 'string');
addr: '127.0.0.1:36244', assert.equal(typeof reply.fd, 'number');
laddr: '127.0.0.1:6379', assert.equal(typeof reply.name, 'string');
fd: 8, assert.equal(typeof reply.age, 'number');
name: '', assert.equal(typeof reply.idle, 'number');
age: 11213, assert.equal(typeof reply.flags, 'string');
idle: 0, assert.equal(typeof reply.db, 'number');
flags: 'N', assert.equal(typeof reply.sub, 'number');
db: 0, assert.equal(typeof reply.psub, 'number');
sub: 0, assert.equal(typeof reply.multi, 'number');
psub: 0, assert.equal(typeof reply.qbuf, 'number');
multi: -1, assert.equal(typeof reply.qbufFree, 'number');
qbuf: 26, assert.equal(typeof reply.argvMem, 'number');
qbufFree: 40928, assert.equal(typeof reply.obl, 'number');
argvMem: 10, assert.equal(typeof reply.oll, 'number');
obl: 0, assert.equal(typeof reply.omem, 'number');
oll: 0, assert.equal(typeof reply.totMem, 'number');
omem: 0, assert.equal(typeof reply.events, 'string');
totMem: 61466, assert.equal(typeof reply.cmd, 'string');
events: 'r', assert.equal(typeof reply.user, 'string');
cmd: 'client', assert.equal(typeof reply.redir, 'number');
user: 'default',
redir: -1 if (testUtils.isVersionGreaterThan([7, 0])) {
} assert.equal(typeof reply.multiMem, 'number');
); assert.equal(typeof reply.resp, 'number');
}); }
if (testUtils.isVersionGreaterThan([7, 0, 3])) {
assert.equal(typeof reply.ssub, 'number');
}
}, GLOBAL.SERVERS.OPEN);
}); });

View File

@@ -1,11 +1,13 @@
export const IS_READ_ONLY = true;
export function transformArguments(): Array<string> { export function transformArguments(): Array<string> {
return ['CLIENT', 'INFO']; return ['CLIENT', 'INFO'];
} }
interface ClientInfoReply { export interface ClientInfoReply {
id: number; id: number;
addr: string; addr: string;
laddr: string; laddr?: string; // 6.2
fd: number; fd: number;
name: string; name: string;
age: number; age: number;
@@ -14,72 +16,74 @@ interface ClientInfoReply {
db: number; db: number;
sub: number; sub: number;
psub: number; psub: number;
ssub?: number; // 7.0.3
multi: number; multi: number;
qbuf: number; qbuf: number;
qbufFree: number; qbufFree: number;
argvMem: number; argvMem?: number; // 6.0
multiMem?: number; // 7.0
obl: number; obl: number;
oll: number; oll: number;
omem: number; omem: number;
totMem: number; totMem?: number; // 6.0
events: string; events: string;
cmd: string; cmd: string;
user: string; user?: string; // 6.0
redir: number; redir?: number; // 6.2
resp?: number; // 7.0
} }
const REGEX = /=([^\s]*)/g; const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g;
export function transformReply(reply: string): ClientInfoReply { export function transformReply(rawReply: string): ClientInfoReply {
const [ const map: Record<string, string> = {};
[, id], for (const item of rawReply.matchAll(CLIENT_INFO_REGEX)) {
[, addr], map[item[1]] = item[2];
[, laddr], }
[, fd],
[, name],
[, age],
[, idle],
[, flags],
[, db],
[, sub],
[, psub],
[, multi],
[, qbuf],
[, qbufFree],
[, argvMem],
[, obl],
[, oll],
[, omem],
[, totMem],
[, events],
[, cmd],
[, user],
[, redir]
] = [...reply.matchAll(REGEX)];
return { const reply: ClientInfoReply = {
id: Number(id), id: Number(map.id),
addr, addr: map.addr,
laddr, fd: Number(map.fd),
fd: Number(fd), name: map.name,
name, age: Number(map.age),
age: Number(age), idle: Number(map.idle),
idle: Number(idle), flags: map.flags,
flags, db: Number(map.db),
db: Number(db), sub: Number(map.sub),
sub: Number(sub), psub: Number(map.psub),
psub: Number(psub), multi: Number(map.multi),
multi: Number(multi), qbuf: Number(map.qbuf),
qbuf: Number(qbuf), qbufFree: Number(map['qbuf-free']),
qbufFree: Number(qbufFree), argvMem: Number(map['argv-mem']),
argvMem: Number(argvMem), obl: Number(map.obl),
obl: Number(obl), oll: Number(map.oll),
oll: Number(oll), omem: Number(map.omem),
omem: Number(omem), totMem: Number(map['tot-mem']),
totMem: Number(totMem), events: map.events,
events, cmd: map.cmd,
cmd, user: map.user
user,
redir: Number(redir)
}; };
if (map.laddr !== undefined) {
reply.laddr = map.laddr;
}
if (map.redir !== undefined) {
reply.redir = Number(map.redir);
}
if (map.ssub !== undefined) {
reply.ssub = Number(map.ssub);
}
if (map['multi-mem'] !== undefined) {
reply.multiMem = Number(map['multi-mem']);
}
if (map.resp !== undefined) {
reply.resp = Number(map.resp);
}
return reply;
} }

View File

@@ -0,0 +1,78 @@
import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLIENT_LIST';
import testUtils, { GLOBAL } from '../test-utils';
describe('CLIENT LIST', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['CLIENT', 'LIST']
);
});
it('with TYPE', () => {
assert.deepEqual(
transformArguments({
TYPE: 'NORMAL'
}),
['CLIENT', 'LIST', 'TYPE', 'NORMAL']
);
});
it('with ID', () => {
assert.deepEqual(
transformArguments({
ID: ['1', '2']
}),
['CLIENT', 'LIST', 'ID', '1', '2']
);
});
});
testUtils.testWithClient('client.clientList', async client => {
const reply = await client.clientList();
assert.ok(Array.isArray(reply));
for (const item of reply) {
assert.equal(typeof item.id, 'number');
assert.equal(typeof item.addr, 'string');
assert.equal(typeof item.fd, 'number');
assert.equal(typeof item.name, 'string');
assert.equal(typeof item.age, 'number');
assert.equal(typeof item.idle, 'number');
assert.equal(typeof item.flags, 'string');
assert.equal(typeof item.db, 'number');
assert.equal(typeof item.sub, 'number');
assert.equal(typeof item.psub, 'number');
assert.equal(typeof item.multi, 'number');
assert.equal(typeof item.qbuf, 'number');
assert.equal(typeof item.qbufFree, 'number');
assert.equal(typeof item.obl, 'number');
assert.equal(typeof item.oll, 'number');
assert.equal(typeof item.omem, 'number');
assert.equal(typeof item.events, 'string');
assert.equal(typeof item.cmd, 'string');
if (testUtils.isVersionGreaterThan([6, 0])) {
assert.equal(typeof item.argvMem, 'number');
assert.equal(typeof item.totMem, 'number');
assert.equal(typeof item.user, 'string');
}
if (testUtils.isVersionGreaterThan([6, 2])) {
assert.equal(typeof item.redir, 'number');
assert.equal(typeof item.laddr, 'string');
}
if (testUtils.isVersionGreaterThan([7, 0])) {
assert.equal(typeof item.multiMem, 'number');
assert.equal(typeof item.resp, 'number');
}
if (testUtils.isVersionGreaterThan([7, 0, 3])) {
assert.equal(typeof item.ssub, 'number');
}
}
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,43 @@
import { RedisCommandArguments, RedisCommandArgument } from '.';
import { pushVerdictArguments } from './generic-transformers';
import { transformReply as transformClientInfoReply, ClientInfoReply } from './CLIENT_INFO';
interface ListFilterType {
TYPE: 'NORMAL' | 'MASTER' | 'REPLICA' | 'PUBSUB';
ID?: never;
}
interface ListFilterId {
ID: Array<RedisCommandArgument>;
TYPE?: never;
}
export type ListFilter = ListFilterType | ListFilterId;
export const IS_READ_ONLY = true;
export function transformArguments(filter?: ListFilter): RedisCommandArguments {
let args: RedisCommandArguments = ['CLIENT', 'LIST'];
if (filter) {
if (filter.TYPE !== undefined) {
args.push('TYPE', filter.TYPE);
} else {
args.push('ID');
args = pushVerdictArguments(args, filter.ID);
}
}
return args;
}
export function transformReply(rawReply: string): Array<ClientInfoReply> {
const split = rawReply.split('\n'),
length = split.length - 1,
reply: Array<ClientInfoReply> = [];
for (let i = 0; i < length; i++) {
reply.push(transformClientInfoReply(split[i]));
}
return reply;
}