implement advanced memory optimization system with configurable settings
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
// @ts-ignore
|
||||
import { Client, GatewayIntentBits, Options, Partials } from 'discord.js';
|
||||
// 1. Importa PrismaClient (singleton)
|
||||
// @ts-ignore
|
||||
import { prisma, ensurePrismaConnection } from './prisma';
|
||||
|
||||
process.loadEnvFile();
|
||||
// Verificar si process.loadEnvFile existe (Node.js 20.6+)
|
||||
if (typeof process.loadEnvFile === 'function') {
|
||||
process.loadEnvFile();
|
||||
}
|
||||
|
||||
class Amayo extends Client {
|
||||
public key: string;
|
||||
// 2. Propiedad prisma apuntando al singleton
|
||||
public prisma = prisma;
|
||||
|
||||
constructor() {
|
||||
@@ -18,11 +17,9 @@ class Amayo extends Client {
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent
|
||||
// Eliminado GuildMessageTyping para reducir tráfico/memoria si no se usa
|
||||
],
|
||||
partials: [Partials.Channel, Partials.Message], // Permite recibir eventos sin cachear todo
|
||||
partials: [Partials.Channel, Partials.Message],
|
||||
makeCache: Options.cacheWithLimits({
|
||||
// Limitar el tamaño de los managers más pesados
|
||||
MessageManager: parseInt(process.env.CACHE_MESSAGES_LIMIT || '50', 10),
|
||||
GuildMemberManager: parseInt(process.env.CACHE_MEMBERS_LIMIT || '100', 10),
|
||||
ThreadManager: 10,
|
||||
@@ -33,25 +30,24 @@ class Amayo extends Client {
|
||||
}),
|
||||
sweepers: {
|
||||
messages: {
|
||||
// Cada 5 min barrer mensajes más antiguos que 15 min (ajustable por env)
|
||||
interval: parseInt(process.env.SWEEP_MESSAGES_INTERVAL_SECONDS || '300', 10),
|
||||
lifetime: parseInt(process.env.SWEEP_MESSAGES_LIFETIME_SECONDS || '900', 10)
|
||||
},
|
||||
users: {
|
||||
interval: 60 * 30, // cada 30 minutos
|
||||
interval: 60 * 30,
|
||||
filter: () => (user) => user.bot && user.id !== this.user?.id
|
||||
}
|
||||
},
|
||||
rest: {
|
||||
retries: 5 // bajar un poco para evitar colas largas en memoria
|
||||
retries: 5
|
||||
}
|
||||
});
|
||||
|
||||
this.key = process.env.TOKEN ?? '';
|
||||
}
|
||||
|
||||
async play () {
|
||||
if(!this.key) {
|
||||
async play() {
|
||||
if (!this.key) {
|
||||
console.error('No key provided');
|
||||
throw new Error('Missing DISCORD TOKEN');
|
||||
} else {
|
||||
@@ -61,7 +57,7 @@ class Amayo extends Client {
|
||||
await this.login(this.key);
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to DB or login to Discord:', error);
|
||||
throw error; // Propaga para que withRetry en main.ts reintente
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { Collection } from "discord.js";
|
||||
import type { Button, Modal, SelectMenu, ContextMenu } from "./types/components";
|
||||
|
||||
export const buttons: Collection<string, any> = new Collection<string, any>();
|
||||
export const modals = new Collection<string, any>();
|
||||
export const selectmenus = new Collection<string, any>();
|
||||
export const contextmenus = new Collection<string, any>();
|
||||
export const buttons: Collection<string, Button> = new Collection<string, Button>();
|
||||
export const modals: Collection<string, Modal> = new Collection<string, Modal>();
|
||||
export const selectmenus: Collection<string, SelectMenu> = new Collection<string, SelectMenu>();
|
||||
export const contextmenus: Collection<string, ContextMenu> = new Collection<string, ContextMenu>();
|
||||
|
||||
export function loadComponents(dir: string = path.join(__dirname, "..", "components")) {
|
||||
const files = fs.readdirSync(dir);
|
||||
@@ -21,29 +22,33 @@ export function loadComponents(dir: string = path.join(__dirname, "..", "compone
|
||||
|
||||
if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
|
||||
|
||||
const imported = require(fullPath);
|
||||
const component = imported.default ?? imported;
|
||||
try {
|
||||
const imported = require(fullPath);
|
||||
const component = imported.default ?? imported;
|
||||
|
||||
if (!component?.customId) {
|
||||
console.warn(`⚠️ Archivo ignorado: ${file} (no tiene "customId")`);
|
||||
continue;
|
||||
}
|
||||
if (!component?.customId) {
|
||||
console.warn(`⚠️ Archivo ignorado: ${file} (no tiene "customId")`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detectamos el tipo según la carpeta en la que está
|
||||
if (fullPath.includes("buttons")) {
|
||||
buttons.set(component.customId, component);
|
||||
console.log(`🔘 Botón cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("modals")) {
|
||||
modals.set(component.customId, component);
|
||||
console.log(`📄 Modal cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("selectmenus")) {
|
||||
selectmenus.set(component.customId, component);
|
||||
console.log(`📜 SelectMenu cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("contextmenu")) {
|
||||
contextmenus.set(component.customId, component);
|
||||
console.log(`📑 ContextMenu cargado: ${component.customId}`);
|
||||
} else {
|
||||
console.log(`⚠️ Componente desconocido: ${component.customId}`);
|
||||
// Detectamos el tipo según la carpeta en la que está
|
||||
if (fullPath.includes("buttons")) {
|
||||
buttons.set(component.customId, component as Button);
|
||||
console.log(`🔘 Botón cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("modals")) {
|
||||
modals.set(component.customId, component as Modal);
|
||||
console.log(`📄 Modal cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("selectmenus")) {
|
||||
selectmenus.set(component.customId, component as SelectMenu);
|
||||
console.log(`📜 SelectMenu cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("contextmenu")) {
|
||||
contextmenus.set(component.customId, component as ContextMenu);
|
||||
console.log(`📑 ContextMenu cargado: ${component.customId}`);
|
||||
} else {
|
||||
console.log(`⚠️ Componente desconocido: ${component.customId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error cargando componente ${file}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
78
src/core/memoryOptimizer.ts
Normal file
78
src/core/memoryOptimizer.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
// Sistema adicional de optimización de memoria para complementar el monitor existente
|
||||
|
||||
export interface MemoryOptimizerOptions {
|
||||
forceGCInterval?: number; // minutos
|
||||
maxHeapUsageBeforeGC?: number; // MB
|
||||
logGCStats?: boolean;
|
||||
}
|
||||
|
||||
export class MemoryOptimizer {
|
||||
private gcTimer?: NodeJS.Timeout;
|
||||
private options: Required<MemoryOptimizerOptions>;
|
||||
|
||||
constructor(options: MemoryOptimizerOptions = {}) {
|
||||
this.options = {
|
||||
forceGCInterval: options.forceGCInterval ?? 15, // cada 15 min por defecto
|
||||
maxHeapUsageBeforeGC: options.maxHeapUsageBeforeGC ?? 200, // 200MB
|
||||
logGCStats: options.logGCStats ?? false
|
||||
};
|
||||
}
|
||||
|
||||
start() {
|
||||
// Solo habilitar si está disponible el GC manual
|
||||
if (typeof global.gc !== 'function') {
|
||||
console.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Timer para GC forzado periódico
|
||||
if (this.options.forceGCInterval > 0) {
|
||||
this.gcTimer = setInterval(() => {
|
||||
this.performGC('scheduled');
|
||||
}, this.options.forceGCInterval * 60 * 1000);
|
||||
|
||||
this.gcTimer.unref(); // No bloquear el cierre del proceso
|
||||
}
|
||||
|
||||
console.log(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.gcTimer) {
|
||||
clearInterval(this.gcTimer);
|
||||
this.gcTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Método público para forzar GC cuando sea necesario
|
||||
checkAndOptimize() {
|
||||
const memUsage = process.memoryUsage();
|
||||
const heapUsedMB = memUsage.heapUsed / 1024 / 1024;
|
||||
|
||||
if (heapUsedMB > this.options.maxHeapUsageBeforeGC) {
|
||||
this.performGC('threshold');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private performGC(reason: string) {
|
||||
if (typeof global.gc !== 'function') return;
|
||||
|
||||
const before = process.memoryUsage();
|
||||
const startTime = Date.now();
|
||||
|
||||
global.gc();
|
||||
|
||||
if (this.options.logGCStats) {
|
||||
const after = process.memoryUsage();
|
||||
const duration = Date.now() - startTime;
|
||||
const heapFreed = (before.heapUsed - after.heapUsed) / 1024 / 1024;
|
||||
|
||||
console.log(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instancia singleton exportable
|
||||
export const memoryOptimizer = new MemoryOptimizer();
|
||||
8
src/core/types/block.ts
Normal file
8
src/core/types/block.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
export interface Block {
|
||||
title?: string,
|
||||
color?: any,
|
||||
coverImage?: string,
|
||||
icon?: string,
|
||||
components?: any[],
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type {ChatInputCommandInteraction, Client, Message} from "discord.js";
|
||||
import Amayo from "../client";
|
||||
import type {ChatInputCommandInteraction, Message} from "discord.js";
|
||||
import type Amayo from "../client";
|
||||
|
||||
export interface CommandMessage {
|
||||
name: string;
|
||||
@@ -13,7 +13,7 @@ export interface CommandSlash {
|
||||
name: string;
|
||||
description: string;
|
||||
type: 'slash';
|
||||
options?: string[];
|
||||
options?: any[];
|
||||
cooldown?: number;
|
||||
run: (i: ChatInputCommandInteraction, client: Client) => Promise<void>;
|
||||
run: (i: ChatInputCommandInteraction, client: Amayo) => Promise<void>;
|
||||
}
|
||||
@@ -1,7 +1,29 @@
|
||||
import type {ButtonInteraction} from "discord.js";
|
||||
import type {
|
||||
ButtonInteraction,
|
||||
ModalSubmitInteraction,
|
||||
AnySelectMenuInteraction,
|
||||
ContextMenuCommandInteraction
|
||||
} from "discord.js";
|
||||
import type Amayo from "../client";
|
||||
|
||||
|
||||
export interface button {
|
||||
export interface Button {
|
||||
customId: string;
|
||||
run: (interaction: ButtonInteraction) => Promise<void>;
|
||||
run: (interaction: ButtonInteraction, client: Amayo) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export interface Modal {
|
||||
customId: string;
|
||||
run: (interaction: ModalSubmitInteraction, client: Amayo) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export interface SelectMenu {
|
||||
customId: string;
|
||||
run: (interaction: AnySelectMenuInteraction, client: Amayo) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export interface ContextMenu {
|
||||
name: string;
|
||||
type: 'USER' | 'MESSAGE';
|
||||
run: (interaction: ContextMenuCommandInteraction, client: Amayo) => Promise<unknown>;
|
||||
}
|
||||
Reference in New Issue
Block a user