Skip to content

Commit e2b6935

Browse files
authored
Merge pull request #82 from thib3113/perf-optimize-monitor-loop-iteration-10307753691658135575
⚡ optimize variable change detection loop in Monitor
2 parents bd47957 + 913a61f commit e2b6935

2 files changed

Lines changed: 186 additions & 5 deletions

File tree

src/Monitor.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,16 +261,22 @@ export class Monitor extends TypedEmitter<IMonitorEvents> {
261261
this.checkChangedValue(previousState, state, 'battery.runtime', (value) => this.emit('BATTERY_RUNTIME', Number(value), value));
262262

263263
let variableChanged = false;
264-
Object.keys({
265-
...previousState,
266-
...state
267-
}).forEach((k) => {
264+
const processKey = (k: string) => {
268265
const key = k as nutVariablesNames;
269266
this.checkChangedValue(previousState, state, key, () => {
270267
this.emit('VARIABLE_CHANGED', key, previousState[key] ?? '', state[key] ?? '', previousState, state);
271268
variableChanged = true;
272269
});
273-
});
270+
};
271+
272+
for (const key of Object.keys(state)) {
273+
processKey(key);
274+
}
275+
for (const key of Object.keys(previousState)) {
276+
if (!(key in state)) {
277+
processKey(key);
278+
}
279+
}
274280

275281
// one variable changed ?
276282
if (variableChanged) {

tests/Monitor.tests.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
2+
import { Monitor } from '../src/Monitor.js';
3+
import { NUTClient } from '../src/NUTClient.js';
4+
import { UPS } from '../src/UPS.js';
5+
import { ENUTStatus } from '../src/ENUTStatus.js';
6+
7+
describe('Monitor', () => {
8+
let mockClient: jest.Mocked<NUTClient>;
9+
let mockUps: jest.Mocked<UPS>;
10+
let monitor: Monitor;
11+
12+
beforeEach(() => {
13+
mockClient = {
14+
getUPS: jest.fn()
15+
} as unknown as jest.Mocked<NUTClient>;
16+
17+
mockUps = {
18+
listVariables: jest.fn()
19+
} as unknown as jest.Mocked<UPS>;
20+
21+
monitor = new Monitor(mockClient, 'testUps');
22+
// @ts-ignore
23+
monitor.ups = mockUps;
24+
});
25+
26+
it('should not emit events on the first loop', async () => {
27+
const spy = jest.spyOn(monitor, 'emit');
28+
mockUps.listVariables.mockResolvedValueOnce({
29+
'battery.charge': '100',
30+
'battery.status': ENUTStatus.OL
31+
});
32+
33+
// @ts-ignore
34+
await monitor._loopFn();
35+
36+
expect(spy).not.toHaveBeenCalledWith('VARIABLE_CHANGED', expect.anything(), expect.anything(), expect.anything(), expect.anything(), expect.anything());
37+
});
38+
39+
it('should emit VARIABLE_CHANGED when a variable is modified', async () => {
40+
const spy = jest.spyOn(monitor, 'emit');
41+
42+
// Initial state
43+
// @ts-ignore
44+
monitor.previousState = {
45+
'battery.charge': '100'
46+
};
47+
// @ts-ignore
48+
monitor.communication = true;
49+
50+
// New state
51+
mockUps.listVariables.mockResolvedValueOnce({
52+
'battery.charge': '90'
53+
});
54+
55+
// @ts-ignore
56+
await monitor._loopFn();
57+
58+
expect(spy).toHaveBeenCalledWith(
59+
'VARIABLE_CHANGED',
60+
'battery.charge',
61+
'100',
62+
'90',
63+
{ 'battery.charge': '100' },
64+
{ 'battery.charge': '90' }
65+
);
66+
expect(spy).toHaveBeenCalledWith('VARIABLES_CHANGED', { 'battery.charge': '100' }, { 'battery.charge': '90' });
67+
});
68+
69+
it('should emit VARIABLE_CHANGED when a variable is added', async () => {
70+
const spy = jest.spyOn(monitor, 'emit');
71+
72+
// Initial state
73+
// @ts-ignore
74+
monitor.previousState = {
75+
'battery.charge': '100'
76+
};
77+
// @ts-ignore
78+
monitor.communication = true;
79+
80+
// New state
81+
mockUps.listVariables.mockResolvedValueOnce({
82+
'battery.charge': '100',
83+
'ups.load': '10'
84+
});
85+
86+
// @ts-ignore
87+
await monitor._loopFn();
88+
89+
expect(spy).toHaveBeenCalledWith(
90+
'VARIABLE_CHANGED',
91+
'ups.load',
92+
'',
93+
'10',
94+
{ 'battery.charge': '100' },
95+
{ 'battery.charge': '100', 'ups.load': '10' }
96+
);
97+
});
98+
99+
it('should emit VARIABLE_CHANGED when a variable is removed', async () => {
100+
const spy = jest.spyOn(monitor, 'emit');
101+
102+
// Initial state
103+
// @ts-ignore
104+
monitor.previousState = {
105+
'battery.charge': '100',
106+
'ups.load': '10'
107+
};
108+
// @ts-ignore
109+
monitor.communication = true;
110+
111+
// New state
112+
mockUps.listVariables.mockResolvedValueOnce({
113+
'battery.charge': '100'
114+
});
115+
116+
// @ts-ignore
117+
await monitor._loopFn();
118+
119+
expect(spy).toHaveBeenCalledWith(
120+
'VARIABLE_CHANGED',
121+
'ups.load',
122+
'10',
123+
'',
124+
{ 'battery.charge': '100', 'ups.load': '10' },
125+
{ 'battery.charge': '100' }
126+
);
127+
});
128+
129+
it('should handle status changes and emit corresponding events', async () => {
130+
const spy = jest.spyOn(monitor, 'emit');
131+
132+
// Initial state
133+
// @ts-ignore
134+
monitor.previousState = {
135+
'battery.status': ENUTStatus.OL
136+
};
137+
// @ts-ignore
138+
monitor.communication = true;
139+
140+
// New state
141+
mockUps.listVariables.mockResolvedValueOnce({
142+
'battery.status': ENUTStatus.OB
143+
});
144+
145+
// @ts-ignore
146+
await monitor._loopFn();
147+
148+
expect(spy).toHaveBeenCalledWith('ONBATT');
149+
});
150+
151+
it('should handle communication loss and recovery', async () => {
152+
const spy = jest.spyOn(monitor, 'emit');
153+
154+
// Initially communicating
155+
// @ts-ignore
156+
monitor.communication = true;
157+
158+
// Communication fails
159+
mockUps.listVariables.mockRejectedValueOnce(new Error('fail'));
160+
161+
// @ts-ignore
162+
await monitor._loopFn();
163+
expect(spy).toHaveBeenCalledWith('NOCOMM');
164+
// @ts-ignore
165+
expect(monitor.communication).toBe(false);
166+
167+
// Communication recovers
168+
mockUps.listVariables.mockResolvedValueOnce({ 'battery.charge': '100' });
169+
// @ts-ignore
170+
await monitor._loopFn();
171+
expect(spy).toHaveBeenCalledWith('COMMOK');
172+
// @ts-ignore
173+
expect(monitor.communication).toBe(true);
174+
});
175+
});

0 commit comments

Comments
 (0)