Skip to content

Commit 620ac86

Browse files
foxriver76GermanBluefoxApollon77
authored
[feat]: create a warning if an instance has too many objects (#3070)
* create a warning if an instance has too many objects * fix limit * ensure hostname ist set in adapter tests too --------- Co-authored-by: Bluefox <[email protected]> Co-authored-by: Ingo Fischer <[email protected]>
1 parent 37c8b67 commit 620ac86

File tree

7 files changed

+147
-29
lines changed

7 files changed

+147
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
-->
66

77
## __WORK IN PROGRESS__
8+
* (@foxriver76) Added objects warn limit per instance
89
* (@Apollon77) Allows only numbers for `ts` and `lc` fields in state when provided for setState
910
* (@GermanBluefox) Added typing for `visIconSets` in `io-package.json`(for vis-2 SVG icon sets)
1011
* (@GermanBluefox) Added typing for `smartName` in the enum objects

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ The main configuration is stored in `iobroker-data/iobroker.json`. Normally, the
9191
- [Error Reporting via ioBroker Sentry](#error-reporting-via-iobroker-sentry)
9292
- [Notification System](#notification-system)
9393
- [Disk space warnings](#disk-space-warnings)
94+
- [Objects warn limit](#objects-warn-limit)
9495
- [Controlling and monitoring of adapter processes](#controlling-and-monitoring-of-adapter-processes)
9596
- [Multihost](#multihost)
9697
- [TIERS: Start instances in an ordered manner](#tiers-start-instances-in-an-ordered-manner)
@@ -552,6 +553,12 @@ All three are optional and can be a string or null/undefined if omitted.
552553
The js-controller will generate a notification of in the scope `system` and the category `diskSpaceIssues` on warning level, if your free disk space falls under a specified threshold.
553554
By default, this threshold is 5 % of disk space. Via the state `system.host.<hostname>.diskWarning` you can override this level to any level between `0` and `100`.
554555

556+
### Objects warn limit
557+
**Feature status:** New in 7.1.0
558+
559+
The js-controller will generate a notification of in the scope `system` and the category `numberObjectsLimitExceeded` on warning level, if your number of objects for an adapter instance exceed a specified threshold.
560+
By default, this is set to `5000` objects. Via the state `system.host.adapter.<adapter>.<instance>.objectsWarnLimit` you can override this threshold to any positive number.
561+
555562
### Logging
556563
#### Log levels
557564
**Feature status:** stable

packages/adapter/src/lib/adapter/adapter.ts

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ import type {
138138
UserInterfaceClientRemoveMessage,
139139
} from '@/lib/_Types.js';
140140
import { UserInterfaceMessagingController } from '@/lib/adapter/userInterfaceMessagingController.js';
141-
import { SYSTEM_ADAPTER_PREFIX } from '@iobroker/js-controller-common-db/constants';
141+
import { SYSTEM_ADAPTER_PREFIX, DEFAULT_OBJECTS_WARN_LIMIT } from '@iobroker/js-controller-common-db/constants';
142142
import { isLogLevel } from '@iobroker/js-controller-common-db/tools';
143143

144144
const controllerVersion = packJson.version;
@@ -11506,30 +11506,15 @@ export class AdapterClass extends EventEmitter {
1150611506

1150711507
if (!this._config.isInstall && (!process.argv || !this._config.forceIfDisabled)) {
1150811508
const id = `system.adapter.${this.namespace}`;
11509-
this.outputCount += 2;
11510-
this.#states.setState(`${id}.alive`, { val: true, ack: true, expire: 30, from: id });
11511-
let done = false;
11512-
this.#states.setState(
11513-
`${id}.connected`,
11514-
{
11515-
val: true,
11516-
ack: true,
11517-
expire: 30,
11518-
from: id,
11519-
},
11520-
() => {
11521-
if (!done) {
11522-
done = true;
11523-
this.terminate(EXIT_CODES.NO_ADAPTER_CONFIG_FOUND);
11524-
}
11525-
},
11526-
);
11527-
setTimeout(() => {
11528-
if (!done) {
11529-
done = true;
11530-
this.terminate(EXIT_CODES.NO_ADAPTER_CONFIG_FOUND);
11531-
}
11532-
}, 1_000);
11509+
await this.#setStateWithOutputCount(`${id}.alive`, { val: true, ack: true, expire: 30, from: id });
11510+
await this.#setStateWithOutputCount(`${id}.connected`, {
11511+
val: true,
11512+
ack: true,
11513+
expire: 30,
11514+
from: id,
11515+
});
11516+
11517+
this.terminate(EXIT_CODES.NO_ADAPTER_CONFIG_FOUND);
1153311518
return;
1153411519
}
1153511520
}
@@ -11590,7 +11575,7 @@ export class AdapterClass extends EventEmitter {
1159011575
// @ts-expect-error
1159111576
this.config = adapterConfig.native;
1159211577
// @ts-expect-error
11593-
this.host = adapterConfig.common.host;
11578+
this.host = adapterConfig.common.host || tools.getHostName();
1159411579
// @ts-expect-error
1159511580
this.common = adapterConfig.common;
1159611581

@@ -11635,7 +11620,7 @@ export class AdapterClass extends EventEmitter {
1163511620
this.config = adapterConfig.native || {};
1163611621
// @ts-expect-error
1163711622
this.common = adapterConfig.common || {};
11638-
this.host = this.common?.host || tools.getHostName() || os.hostname();
11623+
this.host = this.common?.host || tools.getHostName();
1163911624
}
1164011625

1164111626
this.adapterConfig = adapterConfig;
@@ -11706,13 +11691,19 @@ export class AdapterClass extends EventEmitter {
1170611691
from: `system.adapter.${this.namespace}`,
1170711692
});
1170811693

11694+
try {
11695+
await this.#checkObjectsWarnLimit();
11696+
} catch (e) {
11697+
this._logger.error(`${this.namespaceLog} Could not check objects warn limit: ${e.message}`);
11698+
}
11699+
1170911700
if (this._options.instance === undefined) {
1171011701
this.version = this.pack?.version
1171111702
? this.pack.version
1171211703
: this.ioPack?.common
1171311704
? this.ioPack.common.version
1171411705
: 'unknown';
11715-
// display if it's a non-official version - only if installedFrom is explicitly given and differs it's not npm
11706+
1171611707
// display if it's a non-official version - only if installedFrom is explicitly given and differs it's not npm
1171711708
const isNpmVersion = isInstalledFromNpm({
1171811709
adapterName: this.name,
@@ -11982,6 +11973,67 @@ export class AdapterClass extends EventEmitter {
1198211973
});
1198311974
}
1198411975

11976+
/**
11977+
* Check if the number of objects exceeds the warning limit
11978+
*/
11979+
async #checkObjectsWarnLimit(): Promise<void> {
11980+
if (!this.#objects || !this.#states) {
11981+
throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
11982+
}
11983+
11984+
const warnLimitId = `${SYSTEM_ADAPTER_PREFIX + this.namespace}.objectsWarnLimit`;
11985+
11986+
const warnLimitState = await this.#getStateWithInputCount(warnLimitId);
11987+
11988+
const objectsWarnLimit =
11989+
typeof warnLimitState?.val === 'number' ? warnLimitState.val : DEFAULT_OBJECTS_WARN_LIMIT;
11990+
11991+
if (warnLimitState?.ack === false) {
11992+
await this.#setStateWithOutputCount(warnLimitId, { val: objectsWarnLimit, ack: true });
11993+
}
11994+
11995+
const keys = await this.#objects.getKeysAsync(`${this.namespace}*`);
11996+
const objectsCount = keys?.length ?? 0;
11997+
11998+
if (objectsCount > objectsWarnLimit) {
11999+
const message = `This instance has ${objectsCount} objects, the limit for this instance is set to ${objectsWarnLimit}.`;
12000+
this._logger.warn(`${this.namespaceLog} ${message}`);
12001+
await this.registerNotification('system', 'numberObjectsLimitExceeded', message);
12002+
}
12003+
}
12004+
12005+
/**
12006+
* Get a state and automatically increase the input count
12007+
*
12008+
* @param id id of the state
12009+
*/
12010+
#getStateWithInputCount(id: string): ioBroker.GetStatePromise {
12011+
if (!this.#states) {
12012+
throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
12013+
}
12014+
12015+
this.inputCount++;
12016+
return this.#states.getState(id);
12017+
}
12018+
12019+
/**
12020+
* Set a state and automatically increase the output count
12021+
*
12022+
* @param id if of the state
12023+
* @param state state to set
12024+
*/
12025+
#setStateWithOutputCount(
12026+
id: string,
12027+
state: ioBroker.SettableState | ioBroker.StateValue,
12028+
): ioBroker.SetStatePromise {
12029+
if (!this.#states) {
12030+
throw new Error(tools.ERRORS.ERROR_DB_CLOSED);
12031+
}
12032+
12033+
this.outputCount++;
12034+
return this.#states.setState(id, state);
12035+
}
12036+
1198512037
private async _extendObjects(tasks: Record<string, any>, callback: () => void): Promise<void> {
1198612038
if (!tasks || !tasks.length) {
1198712039
return tools.maybeCallback(callback);

packages/common-db/src/lib/common/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ export const SYSTEM_REPOSITORIES_ID = 'system.repositories';
88
export const SYSTEM_CONFIG_ID = 'system.config';
99
/** Unicode symbol to be appended on endkey of getObjectView */
1010
export const HIGHEST_UNICODE_SYMBOL = '\u9999';
11+
/** Default limit for generating a warning if exceeding the number of objects per instance */
12+
export const DEFAULT_OBJECTS_WARN_LIMIT = 5_000;

packages/common-db/src/lib/common/tools.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type * as DiskUsage from 'diskusage';
3232
import * as url from 'node:url';
3333
import { createRequire } from 'node:module';
3434
import type { WithRequired } from '@iobroker/types-dev';
35+
import { DEFAULT_OBJECTS_WARN_LIMIT } from '@/lib/common/constants.js';
3536

3637
// eslint-disable-next-line unicorn/prefer-module
3738
const thisDir = url.fileURLToPath(new URL('.', import.meta.url || `file://${__filename}`));
@@ -3536,6 +3537,20 @@ export function getInstanceIndicatorObjects(namespace: string): ioBroker.StateOb
35363537
},
35373538
native: {},
35383539
},
3540+
{
3541+
_id: `${id}.objectsWarnLimit`,
3542+
type: 'state',
3543+
common: {
3544+
name: `${namespace} objects warn limit`,
3545+
type: 'number',
3546+
read: true,
3547+
write: true,
3548+
desc: 'If the number of objects of this adapter instance exceeds this limit, the user will receive a warning',
3549+
role: 'state',
3550+
def: DEFAULT_OBJECTS_WARN_LIMIT,
3551+
},
3552+
native: {},
3553+
},
35393554
];
35403555
}
35413556

packages/controller/io-package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,38 @@
13101310
},
13111311
"regex": [],
13121312
"limit": 1
1313+
},
1314+
{
1315+
"category": "numberObjectsLimitExceeded",
1316+
"name": {
1317+
"en": "The configured limit of objects for this adapter instance has been exceeded.",
1318+
"de": "Das konfigurierte Limit an Objekten für diese Adapterinstanz wurde überschritten.",
1319+
"ru": "Превышен настроенный лимит объектов для данного экземпляра адаптера.",
1320+
"pt": "O limite configurado de objectos para esta instância de adaptador foi ultrapassado.",
1321+
"nl": "De geconfigureerde limiet van objecten voor deze instantie van de adapter is overschreden.",
1322+
"fr": "La limite d'objets configurée pour cette instance d'adaptateur a été dépassée.",
1323+
"it": "È stato superato il limite di oggetti configurato per questa istanza dell'adattatore.",
1324+
"es": "Se ha superado el límite configurado de objetos para esta instancia de adaptador.",
1325+
"pl": "Skonfigurowany limit obiektów dla tej instancji adaptera został przekroczony.",
1326+
"uk": "Перевищено ліміт об'єктів для цього екземпляра адаптера.",
1327+
"zh-cn": "The configured limit of objects for this adapter instance has been exceeded."
1328+
},
1329+
"severity": "alert",
1330+
"description": {
1331+
"en": "This adapter instance has exceeded the configured limit of objects. Too many objects are often caused by misconfigurations or bugs. This can lead to massive performance issues. If this is expected, you can increase the limit for this adapter instance.",
1332+
"de": "Diese Adapterinstanz hat das konfigurierte Limit an Objekten überschritten. Zu viele Objekte werden oft durch Fehlkonfigurationen oder Bugs verursacht. Dies kann zu massiven Performance-Problemen führen. Wenn dies zu erwarten ist, können Sie das Limit für diese Adapterinstanz erhöhen.",
1333+
"ru": "Этот экземпляр адаптера превысил установленный лимит объектов. Слишком большое количество объектов часто вызвано неправильной конфигурацией или ошибками. Это может привести к серьезным проблемам с производительностью. Если это ожидаемо, вы можете увеличить лимит для этого экземпляра адаптера.",
1334+
"pt": "Esta instância do adaptador excedeu o limite configurado de objectos. A existência de demasiados objectos é frequentemente causada por configurações incorrectas ou erros. Isto pode levar a grandes problemas de desempenho. Se isto for expetável, pode aumentar o limite para esta instância do adaptador.",
1335+
"nl": "Deze instantie van de adapter heeft de geconfigureerde limiet van objecten overschreden. Te veel objecten worden vaak veroorzaakt door verkeerde configuraties of bugs. Dit kan leiden tot enorme prestatieproblemen. Als dit wordt verwacht, kun je de limiet voor deze adapterinstantie verhogen.",
1336+
"fr": "Cette instance d'adaptateur a dépassé la limite d'objets configurée. Un trop grand nombre d'objets est souvent dû à une mauvaise configuration ou à des bogues. Cela peut entraîner d'importants problèmes de performance. Si cela est prévisible, vous pouvez augmenter la limite de cette instance d'adaptateur.",
1337+
"it": "L'istanza di questo adattatore ha superato il limite di oggetti configurato. Un numero eccessivo di oggetti è spesso causato da configurazioni errate o da bug. Ciò può causare gravi problemi di prestazioni. Se questo è previsto, è possibile aumentare il limite per questa istanza dell'adattatore.",
1338+
"es": "Esta instancia de adaptador ha superado el límite configurado de objetos. El exceso de objetos suele deberse a errores o errores de configuración. Esto puede conducir a problemas de rendimiento masivos. Si esto es lo esperado, puedes aumentar el límite para esta instancia de adaptador.",
1339+
"pl": "Ta instancja adaptera przekroczyła skonfigurowany limit obiektów. Zbyt duża liczba obiektów jest często spowodowana błędną konfiguracją lub błędami. Może to prowadzić do poważnych problemów z wydajnością. Jeśli jest to oczekiwane, można zwiększyć limit dla tej instancji adaptera.",
1340+
"uk": "Цей екземпляр адаптера перевищив встановлений ліміт об'єктів. Надмірна кількість об'єктів часто спричинена неправильними конфігураціями або помилками. Це може призвести до значних проблем з продуктивністю. Якщо це очікується, ви можете збільшити ліміт для цього екземпляра адаптера.",
1341+
"zh-cn": "This adapter instance has exceeded the configured limit of objects. Too many objects are often caused by misconfigurations or bugs. This can lead to massive performance issues. If this is expected, you can increase the limit for this adapter instance."
1342+
},
1343+
"regex": [],
1344+
"limit": 1
13131345
}
13141346
]
13151347
}

packages/types-dev/index.d.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,16 @@ declare global {
376376
| 'nonExistingFileErrors'
377377
| 'remoteHostErrors'
378378
| 'restartLoop'
379-
| 'fileToJsonl';
379+
| 'fileToJsonl'
380+
| 'numberObjectsLimitExceeded'
381+
| 'systemRebootRequired'
382+
| 'dockerUpdate'
383+
| 'packageUpdates'
384+
| 'securityIssues'
385+
| 'databaseErrors'
386+
| 'blockedVersions'
387+
| 'automaticAdapterUpgradeSuccessful'
388+
| 'automaticAdapterUpgradeFailed';
380389
[other: string]: string;
381390
}
382391

0 commit comments

Comments
 (0)