Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
name: Отчет об ошибке
about: Создайте отчет об ошибке, чтобы помочь улучшить продукт
about: Создайте отчет об ошибке, чтобы помочь улучшить библиотеку
title: ''
labels: ''
assignees: ''
labels: bugs
assignees: max36895
---

### Опишите ошибку
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: Запрос нового функционала
about: Предложите идею по улучшению продукта
about: Предложите идею по улучшению библиотеки
title: ''
labels: question
assignees: max36895
Expand Down
35 changes: 21 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,22 @@
Все значимые изменения в проекте umbot документируются в этом файле.
Формат основан на [Keep a CHANGELOG](http://keepachangelog.com/).

## [2.1.0] - 2025-19-05

## [2.2.x] - 2025-16-11

### Добавлено

- Возможность в logger указать метрику.

### Обновлено

- Ошибки во время работы приложения записываются как ошибки, а не как обычные логи

### Исправлено

- Архитектурная проблема, из-за которой приложение могло работать не корректно

## [2.1.0] - 2025-19-10

### Добавлено

Expand Down Expand Up @@ -32,6 +47,9 @@
- Обновился eslint до актуальной версии
- BotController не обязательно задавать, если все можно сделать за счет `bot.addCommand`
- При записи логов в файл, все секреты маскируются
- Поиск опасных регулярных выражений(ReDos) и интентах
- Сохранение логов стало асинхронной операцией
- Произведена микрооптимизация

### Исправлено

Expand All @@ -40,7 +58,8 @@
- Ошибки в cli
- Исправлена ошибка, когда поиск по регулярному выражению мог возвращать не корректный результат
- Ошибки с некорректным отображением документации
- Ошибки с некорректной отправкой запроса к платформе
- Ошибки с некорректной отправкой запроса к платформе
- Ошибка когда benchmark мог упасть, также доработан вывод результата

## [2.0.0] - 2025-05-08

Expand Down Expand Up @@ -231,27 +250,15 @@
Создание бета-версии

[master]: https://github.com/max36895/universal_bot-ts/compare/v2.1.0...master

[2.1.0]: https://github.com/max36895/universal_bot-ts/compare/v2.0.0...v2.1.0

[2.0.0]: https://github.com/max36895/universal_bot-ts/compare/v1.1.8...v2.0.0

[1.1.8]: https://github.com/max36895/universal_bot-ts/compare/v1.1.6...v1.1.8

[1.1.6]: https://github.com/max36895/universal_bot-ts/compare/v1.1.5...v1.1.6

[1.1.5]: https://github.com/max36895/universal_bot-ts/compare/v1.1.4...v1.1.5

[1.1.4]: https://github.com/max36895/universal_bot-ts/compare/v1.1.3...v1.1.4

[1.1.3]: https://github.com/max36895/universal_bot-ts/compare/v1.1.2...v1.1.3

[1.1.2]: https://github.com/max36895/universal_bot-ts/compare/v1.1.1...v1.1.2

[1.1.1]: https://github.com/max36895/universal_bot-ts/compare/v1.1.0...v1.1.1

[1.1.0]: https://github.com/max36895/universal_bot-ts/compare/v1.0.0...v1.1.0

[1.0.0]: https://github.com/max36895/universal_bot-ts/compare/v0.9.0-beta...v1.0.0

[0.9.0-beta]: https://github.com/max36895/universal_bot-ts/releases/tag/v0.9.0-beta
169 changes: 142 additions & 27 deletions benchmark/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

const { Bot, BotController, Alisa, T_ALISA } = require('./../dist/index');
const { performance } = require('perf_hooks');
const os = require('os');

function gc() {
global.gc();
}

// --------------------------------------------------
// Вывод результатов
Expand Down Expand Up @@ -30,6 +35,8 @@
const memPerCmd =
(parseFloat(rep.afterRunMemory) - parseFloat(rep.startMemory)) / rep.count;
log(` ├─ Потребление памяти на одну команду: ${memPerCmd.toFixed(4)} КБ`);
const timePerCmd = rep.duration / rep.count;
log(` ├─ Среднее время на обработку одной команды: ${timePerCmd.toFixed(7)} мс`);
}

const low = byState.low;
Expand Down Expand Up @@ -275,7 +282,7 @@
super(appContext);
}

action(intentName, isCommand) {
action(intentName, _) {
if (intentName && intentName.startsWith('cmd_')) {
this.text = `Обработана команда: ${intentName}`;
this.userData[`data_for_${intentName}`] = `value_for_${intentName}`;
Expand Down Expand Up @@ -339,23 +346,22 @@
// Сценарий когда может быть более 10_000 команд сложно представить, тем более чтобы все регулярные выражения были уникальны.
// При 20_000 командах мы все еще укладываемся в ограничение.
// Предварительный лимит на количество уникальных регулярных выражений составляет примерно 40_000 - 50_000 команд.
return `((\d+)_ref_${step % 1e3})`;

Check failure

Code scanning / CodeQL

Useless regular-expression character escape High test

The escape sequence '\d' is equivalent to just 'd', so the sequence is not a character class when it is used in a
regular expression
.
}
}

// сам тест
async function runTest(count = 1000, useReg = false, state = 'middle', regState = 'middle') {
const res = { state, regState: useReg ? regState : '', useReg, count };
global.gc();
gc();
await new Promise((resolve) => {
setTimeout(resolve, 1);
});
const startedMemory = process.memoryUsage().heapUsed;
res.startMemory = (startedMemory / 1024).toFixed(2);

const bot = new Bot();
const botController = new TestBotController(bot._appContext);
bot.initBotController(botController);
bot.initBotControllerClass(TestBotController);
bot.appType = T_ALISA;
const botClass = new Alisa(bot._appContext);
bot.setAppConfig({ isLocalStorage: true });
Expand Down Expand Up @@ -447,9 +453,12 @@
}
}

global.gc();
bot.setContent(getContent(testCommand));
global.gc();
gc();
const content = getContent(testCommand);
await new Promise((resolve) => {
setTimeout(resolve, 1);
});
gc();
await new Promise((resolve) => {
setTimeout(resolve, 1);
});
Expand All @@ -458,7 +467,7 @@

const start = performance.now();
try {
await bot.run(botClass);
await bot.run(botClass, 'alisa', content);
} catch (e) {
/* ignore */
}
Expand All @@ -472,15 +481,14 @@
/* ignore */
}
const duration2 = performance.now() - start2;
global.gc();
gc();
const afterMemory = process.memoryUsage().heapUsed;
res.afterRunMemory = (afterMemory / 1024).toFixed(2);
res.memoryIncrease = ((afterMemory - beforeMemory) / 1024).toFixed(2);
res.memoryIncreaseFromStart = ((afterMemory - startedMemory) / 1024).toFixed(2);

botController.clearStoreData();
bot.clearCommands();
global.gc();
gc();
const finalMemory = process.memoryUsage().heapUsed;
res.finalMemory = (finalMemory / 1024).toFixed(2);
res.memoryDifference = ((finalMemory - startedMemory) / 1024).toFixed(2);
Expand All @@ -490,38 +498,145 @@
status.push(res);
}

function getAvailableMemoryMB() {
//const total = process.totalmem();
const free = os.freemem();
// Оставляем 200 МБ на систему и Node.js рантайм
return Math.max(0, (free - 200 * 1024 * 1024) / (1024 * 1024));
}

function predictMemoryUsage(commandCount) {
// Базовое потребление + 0.5 КБ на команду + запас
return 15 + (commandCount * 0.5) / 1024 + 50; // в МБ
}

// --- Запуск ---
async function start() {
try {
// Количество команд
const counts = [50, 250, 500, 1000, 2e3, 2e4, 2e5, 1e6, 2e6];
/*for (let i = 1; i < 1e4; i++) {
counts.push(2e6 + i * 1e6);
}*/
// Исход поиска(требуемая команда в начале списка, требуемая команда в середине списка, требуемая команда не найдена))
const states = ['low', 'middle', 'high'];
// Сложность регулярных выражений (low — простая, middle — умеренная, high — сложная(субъективно))
const regStates = ['low', 'middle', 'high'];

console.log(
'⚠️ Этот benchmark тестирует ЭКСТРЕМАЛЬНЫЕ сценарии (до 2 млн команд).\n' +
' В реальных проектах редко используется более 10 000 команд.\n' +
' Результаты при >50 000 команд НЕ означают, что библиотека "медленная" —\n' +
' это означает, что такую логику нужно архитектурно декомпозировать.',
);
// для чистоты запускаем gc
global.gc();
for (let count of counts) {
console.log(`Запуск тестов для ${count} команд...`);
for (let state of states) {
global.gc();
await new Promise((resolve) => {
setTimeout(resolve, 1);
});
await runTest(count, false, state);
for (let regState of regStates) {
global.gc();
gc();
let cCountFErr = 0;

const printResult = () => {
console.log('Подготовка отчета...');
printSummary(status);
printFinalSummary(status);
console.log('');
console.log('🔍 АНАЛИЗ РЕЗУЛЬТАТОВ');
console.log('💡 Типичные production-проекты содержат:');
console.log(' • до 100 команд — простые навыки');
console.log(' • до 1 000 команд — сложные корпоративные боты');
console.log(' • до 10 000 команд — крайне редко (требует архитектурного пересмотра)');
console.log('');

const time250 = Math.max(
...status
.filter((item) => {
return item.count === 250;
})
.map((item) => {
return +item.duration;
}),
);

const time1k = Math.max(
...status
.filter((item) => {
return item.count === 1e3;
})
.map((item) => {
return +item.duration;
}),
);

const time20k = Math.max(
...status
.filter((item) => {
return item.count === 2e4;
})
.map((item) => {
return +item.duration;
}),
);

console.log(
'✅ Анализ производительности:\n' +
` • При 250 команд (типичный средний навык):\n` +
` — Худший сценарий: ${time250} мс\n` +
` — ${time250 <= 20 ? '🟢 Отлично: библиотека не будет узким местом' : time250 <= 150 ? '🟡 Хорошо: укладывается в гарантии платформы' : '⚠️ Внимание: время близко к лимиту. Проверьте, не связано ли это с нагрузкой на сервер (CPU, RAM, GC).'}\n` +
` • При 1 000 команд (типичный крупный навык):\n` +
` — Худший сценарий: ${time1k} мс\n` +
` — ${time1k <= 35 ? '🟢 Отлично: библиотека не будет узким местом' : time1k <= 200 ? '🟡 Хорошо: укладывается в гарантии платформы' : '⚠️ Внимание: время близко к лимиту. Проверьте, не связано ли это с нагрузкой на сервер (CPU, RAM, GC).'}\n` +
` • При 20 000 команд (экстремальный сценарий):\n` +
` — Худший сценарий: ${time20k} мс\n` +
` — ${time20k <= 50 ? '🟢 Отлично: производительность в норме' : time20k <= 300 ? '🟡 Приемлемо: библиотека укладывается в 1 сек' : '⚠️ Внимание: время обработки велико. Убедитесь, что сервер имеет достаточные ресурсы (CPU ≥2 ядра, RAM ≥2 ГБ).'}\n` +
'💡 Примечание:\n' +
' — Платформы (Алиса, Сбер и др.) дают до 3 секунд на ответ.\n' +
' — `umbot` гарантирует ≤1 сек на свою логику при количестве команд до 500 000 (оставляя 2+ сек на ваш код).\n' +
' — Всплески времени (например, 100–200 мс) могут быть вызваны сборкой мусора (GC) в Node.js — это нормально.\n' +
' — Если сервер слабый (1 ядро, 1 ГБ RAM), даже отличная библиотека не сможет компенсировать нехватку ресурсов.',
);
console.log('');
console.log('⚠️ Рекомендация:');
console.log(' Если вы планируете использовать >10 000 команд:');
console.log(' • Разбейте логику на поднавыки');
console.log(' • Используйте параметризованные интенты вместо статических команд');
console.log(' • Избегайте простых регулярных выражений в большом количестве');
console.log(
'💡 Вместо 10 000 статических команд:\n' +
" — Используйте `addCommand('search', [/^найти (.+)$/], ...)` \n" +
' — Храните данные в БД, а не в коде\n' +
' — Делегируйте логику в `action()` через NLU или внешний API',
);
};

try {
for (let count of counts) {
const predicted = predictMemoryUsage(count);
const available = getAvailableMemoryMB();
if (predicted > available * 0.9) {
console.log(`⚠️ Недостаточно памяти для теста (${count} команд).`);
break;
}

cCountFErr = count;
console.log(`Запуск тестов для ${count} команд...`);
for (let state of states) {
gc();
await new Promise((resolve) => {
setTimeout(resolve, 1);
});
await runTest(count, true, state, regState);
await runTest(count, false, state);
for (let regState of regStates) {
gc();
await new Promise((resolve) => {
setTimeout(resolve, 1);
});
await runTest(count, true, state, regState);
}
}
}
} catch (e) {
console.log(`Упал при выполнении тестов для ${cCountFErr} команд. Ошибка: ${e}`);
}
global.gc();
console.log('Подготовка отчета...');
printSummary(status);
printFinalSummary(status);
gc();
printResult();
} catch (error) {
console.error('Ошибка:', error);
}
Expand Down
Loading
Loading