You've already forked node-redis
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:
@@ -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,
|
||||||
|
@@ -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);
|
||||||
});
|
});
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
78
packages/client/lib/commands/CLIENT_LIST.spec.ts
Normal file
78
packages/client/lib/commands/CLIENT_LIST.spec.ts
Normal 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);
|
||||||
|
});
|
43
packages/client/lib/commands/CLIENT_LIST.ts
Normal file
43
packages/client/lib/commands/CLIENT_LIST.ts
Normal 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;
|
||||||
|
}
|
Reference in New Issue
Block a user