Skip to content

Commit 9f85b6c

Browse files
committed
corrections
1 parent 8f2f4b5 commit 9f85b6c

File tree

3 files changed

+247
-1
lines changed

3 files changed

+247
-1
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { strict as assert } from 'node:assert';
2+
import testUtils, { GLOBAL } from '../test-utils';
3+
import LATENCY_HISTOGRAM, { LatencyHistogramBucket, LatencyHistogramOptions } from './LATENCY_HISTOGRAM';
4+
import { LATENCY_EVENTS } from './LATENCY_GRAPH';
5+
import { parseArgs } from './generic-transformers';
6+
7+
describe('LATENCY HISTOGRAM', function () {
8+
9+
10+
11+
it('transformArguments with no options (event only)', () => {
12+
assert.deepEqual(
13+
parseArgs(LATENCY_HISTOGRAM, LATENCY_EVENTS.COMMAND),
14+
['LATENCY', 'HISTOGRAM', 'command']
15+
);
16+
});
17+
18+
it('transformArguments with buckets option', () => {
19+
const options: LatencyHistogramOptions = {
20+
buckets: [0, 10, 100, 1000]
21+
};
22+
assert.deepEqual(
23+
parseArgs(LATENCY_HISTOGRAM, LATENCY_EVENTS.COMMAND, options),
24+
['LATENCY', 'HISTOGRAM', 'command', 'BUCKETS', '0', '10', '100', '1000']
25+
);
26+
});
27+
28+
it('transformArguments with empty buckets array', () => {
29+
const options: LatencyHistogramOptions = {
30+
buckets: []
31+
};
32+
33+
assert.deepEqual(
34+
parseArgs(LATENCY_HISTOGRAM, LATENCY_EVENTS.COMMAND, options),
35+
['LATENCY', 'HISTOGRAM', 'command']
36+
);
37+
});
38+
39+
40+
it('transformReply with a typical histogram reply', () => {
41+
42+
const rawReply: Array<[number, number]> = [
43+
[0, 10],
44+
[1, 20],
45+
[2, 30],
46+
[4, 40],
47+
[8, 50],
48+
[16, 60],
49+
[32, 70],
50+
[64, 80],
51+
[128, 90],
52+
[256, 100],
53+
[512, 110],
54+
[1024, 120]
55+
];
56+
57+
const expected: LatencyHistogramBucket[] = [
58+
{ min: 0, max: 1, count: 10 },
59+
{ min: 1, max: 2, count: 20 },
60+
{ min: 2, max: 4, count: 30 },
61+
{ min: 4, max: 8, count: 40 },
62+
{ min: 8, max: 16, count: 50 },
63+
{ min: 16, max: 32, count: 60 },
64+
{ min: 32, max: 64, count: 70 },
65+
{ min: 64, max: 128, count: 80 },
66+
{ min: 128, max: 256, count: 90 },
67+
{ min: 256, max: 512, count: 100 },
68+
{ min: 512, max: 1024, count: 110 },
69+
{ min: 1024, max: '+inf', count: 120 }
70+
];
71+
72+
assert.deepEqual(
73+
LATENCY_HISTOGRAM.transformReply(rawReply),
74+
expected
75+
);
76+
});
77+
78+
it('transformReply with an empty reply', () => {
79+
assert.deepEqual(
80+
LATENCY_HISTOGRAM.transformReply([]),
81+
[]
82+
);
83+
});
84+
85+
it('transformReply with a single bucket reply', () => {
86+
const rawReply: Array<[number, number]> = [[0, 5]];
87+
const expected: LatencyHistogramBucket[] = [{ min: 0, max: '+inf', count: 5 }];
88+
assert.deepEqual(
89+
LATENCY_HISTOGRAM.transformReply(rawReply),
90+
expected
91+
);
92+
});
93+
94+
it('transformReply with malformed buckets (should warn and skip)', () => {
95+
96+
const malformedReply: Array<any> = [
97+
[0, 100],
98+
[100, 'invalid_count'],
99+
[200],
100+
[300, 50, 'extra'],
101+
'not_a_bucket',
102+
[400, 200]
103+
];
104+
105+
const expected: LatencyHistogramBucket[] = [
106+
{ min: 0, max: 100, count: 100 },
107+
{ min: 400, max: '+inf', count: 200 }
108+
];
109+
110+
assert.deepEqual(
111+
LATENCY_HISTOGRAM.transformReply(malformedReply),
112+
expected
113+
);
114+
});
115+
116+
it('transformReply with non-array reply (should throw)', () => {
117+
assert.throws(
118+
() => LATENCY_HISTOGRAM.transformReply('not an array' as any),
119+
(err: Error) => {
120+
assert.ok(err instanceof Error);
121+
assert.equal(err.message, 'Unexpected reply type for LATENCY HISTOGRAM: expected array.');
122+
return true;
123+
},
124+
'Expected specific error for non-array reply'
125+
);
126+
});
127+
128+
testUtils.testWithClient('client.latencyHistogram', async client => {
129+
130+
await client.configSet('latency-monitor-threshold', '1');
131+
132+
133+
await client.sendCommand(['DEBUG', 'SLEEP', '0.1']);
134+
await client.sendCommand(['DEBUG', 'SLEEP', '0.02']);
135+
await client.sendCommand(['DEBUG', 'SLEEP', '0.05']);
136+
137+
const histogram: Array<LatencyHistogramBucket> = await client.latencyHistogram(LATENCY_EVENTS.COMMAND);
138+
139+
140+
assert.ok(Array.isArray(histogram), 'Expected histogram to be an array.');
141+
assert.ok(histogram.length > 0, 'Expected histogram to contain buckets.');
142+
143+
const firstBucket = histogram[0];
144+
assert.equal(typeof firstBucket.min, 'number', 'min should be a number.');
145+
assert.equal(typeof firstBucket.count, 'number', 'count should be a number.');
146+
147+
assert.ok(typeof firstBucket.max === 'number' || firstBucket.max === '+inf', 'max should be a number or "+inf".');
148+
149+
150+
const totalCount = histogram.reduce((sum, b) => sum + b.count, 0);
151+
assert.ok(totalCount >= 3, 'Expected at least 3 events captured in the histogram.');
152+
153+
154+
await client.latencyReset();
155+
const latestAfterReset = await client.latencyLatest();
156+
assert.deepEqual(latestAfterReset, [], 'Expected latency events to be cleared after reset.');
157+
158+
}, {
159+
serverArguments: ['--enable-debug-command', 'yes'],
160+
clientOptions: {
161+
socket: {
162+
connectTimeout: 300000
163+
}
164+
}
165+
});
166+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { CommandParser } from '../client/parser';
2+
import { Command } from '../RESP/types';
3+
import { LATENCY_EVENTS, LatencyEvent } from './LATENCY_GRAPH';
4+
5+
6+
export { LATENCY_EVENTS, LatencyEvent };
7+
8+
9+
export interface LatencyHistogramOptions {
10+
11+
buckets?: number[];
12+
}
13+
14+
15+
export interface LatencyHistogramBucket {
16+
17+
min: number;
18+
19+
max: number | '+inf';
20+
21+
count: number;
22+
}
23+
24+
25+
export default {
26+
27+
NOT_KEYED_COMMAND: true,
28+
29+
IS_READ_ONLY: true,
30+
31+
32+
parseCommand(parser: CommandParser, event: LatencyEvent, options?: LatencyHistogramOptions) {
33+
34+
const args: Array<string> = ['LATENCY', 'HISTOGRAM', event];
35+
36+
if (options?.buckets && options.buckets.length > 0) {
37+
args.push('BUCKETS');
38+
39+
args.push(...options.buckets.map(String));
40+
}
41+
42+
parser.push(...args);
43+
},
44+
45+
46+
transformReply(reply: Array<[number, number]>): LatencyHistogramBucket[] {
47+
if (!Array.isArray(reply)) {
48+
throw new Error('Unexpected reply type for LATENCY HISTOGRAM: expected array.');
49+
}
50+
51+
const histogram: LatencyHistogramBucket[] = [];
52+
for (let i = 0; i < reply.length; i++) {
53+
const bucket = reply[i];
54+
if (!Array.isArray(bucket) || bucket.length !== 2 || typeof bucket[0] !== 'number' || typeof bucket[1] !== 'number') {
55+
console.warn('Skipping malformed latency histogram bucket:', bucket);
56+
continue;
57+
}
58+
59+
const min = bucket[0];
60+
const count = bucket[1];
61+
let max: number | '+inf';
62+
63+
64+
if (i < reply.length - 1) {
65+
66+
max = reply[i + 1][0];
67+
} else {
68+
69+
max = '+inf';
70+
}
71+
72+
histogram.push({ min, max, count });
73+
}
74+
75+
return histogram;
76+
}
77+
} as const satisfies Command;

packages/client/lib/commands/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ import KEYS from './KEYS';
169169
import LASTSAVE from './LASTSAVE';
170170
import LATENCY_DOCTOR from './LATENCY_DOCTOR';
171171
import LATENCY_GRAPH from './LATENCY_GRAPH';
172+
import LATENCY_HISTOGRAM from './LATENCY_HISTOGRAM';
172173
import LATENCY_HISTORY from './LATENCY_HISTORY';
173174
import LATENCY_LATEST from './LATENCY_LATEST';
174175
import LATENCY_RESET from './LATENCY_RESET';
@@ -703,12 +704,14 @@ export default {
703704
latencyDoctor: LATENCY_DOCTOR,
704705
LATENCY_GRAPH,
705706
latencyGraph: LATENCY_GRAPH,
707+
LATENCY_HISTOGRAM,
708+
latencyHistogram: LATENCY_HISTOGRAM,
706709
LATENCY_HISTORY,
707710
latencyHistory: LATENCY_HISTORY,
708711
LATENCY_LATEST,
709712
latencyLatest: LATENCY_LATEST,
710713
LATENCY_RESET,
711-
latencyReset: LATENCY_RESET,
714+
latencyReset: LATENCY_RESET,
712715
LCS_IDX_WITHMATCHLEN,
713716
lcsIdxWithMatchLen: LCS_IDX_WITHMATCHLEN,
714717
LCS_IDX,

0 commit comments

Comments
 (0)