@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
318 lines • 12.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var EnhancedMemoryManagerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnhancedMemoryManagerService = void 0;
const common_1 = require("@nestjs/common");
let EnhancedMemoryManagerService = EnhancedMemoryManagerService_1 = class EnhancedMemoryManagerService {
constructor(config = {}) {
this.logger = new common_1.Logger(EnhancedMemoryManagerService_1.name);
this.timelines = new Map();
this.metrics = new Map();
this.cleanupTimer = null;
this.lastCleanupTime = 0;
this.config = {
enabled: true,
maxTimelineSize: 1000,
metricsRetentionMs: 300000,
cleanupIntervalMs: 60000,
memoryThresholdMB: 100,
autoCleanup: true,
compressionEnabled: true,
...config
};
if (this.config.enabled && this.config.autoCleanup) {
this.startCleanupTimer();
}
this.logger.debug('Enhanced Memory Manager initialized', this.config);
}
addTimelineEntry(timelineId, data) {
if (!this.config.enabled) {
return;
}
try {
let timeline = this.timelines.get(timelineId);
if (!timeline) {
timeline = [];
this.timelines.set(timelineId, timeline);
}
const entry = {
timestamp: Date.now(),
data: this.config.compressionEnabled ? this.compressData(data) : data,
compressed: this.config.compressionEnabled
};
timeline.push(entry);
if (timeline.length > this.config.maxTimelineSize) {
const removeCount = timeline.length - this.config.maxTimelineSize;
timeline.splice(0, removeCount);
}
this.checkMemoryUsage();
}
catch (error) {
this.logger.error(`Failed to add timeline entry for ${timelineId}:`, error);
}
}
addMetricsEntry(key, value, ttlMs) {
if (!this.config.enabled) {
return;
}
try {
let metricsArray = this.metrics.get(key);
if (!metricsArray) {
metricsArray = [];
this.metrics.set(key, metricsArray);
}
const entry = {
timestamp: Date.now(),
key,
value: this.config.compressionEnabled ? this.compressData(value) : value,
ttl: ttlMs ? Date.now() + ttlMs : undefined
};
metricsArray.push(entry);
if (ttlMs) {
this.cleanupExpiredMetrics(key);
}
this.checkMemoryUsage();
}
catch (error) {
this.logger.error(`Failed to add metrics entry for ${key}:`, error);
}
}
getTimelineEntries(timelineId, fromTimestamp, toTimestamp) {
try {
const timeline = this.timelines.get(timelineId);
if (!timeline) {
return [];
}
let entries = timeline;
if (fromTimestamp || toTimestamp) {
entries = timeline.filter(entry => {
if (fromTimestamp && entry.timestamp < fromTimestamp)
return false;
if (toTimestamp && entry.timestamp > toTimestamp)
return false;
return true;
});
}
return entries.map(entry => ({
timestamp: entry.timestamp,
data: entry.compressed ? this.decompressData(entry.data) : entry.data
}));
}
catch (error) {
this.logger.error(`Failed to get timeline entries for ${timelineId}:`, error);
return [];
}
}
getLatestMetrics(key, limit = 10) {
try {
const metricsArray = this.metrics.get(key);
if (!metricsArray) {
return [];
}
this.cleanupExpiredMetrics(key);
const validEntries = metricsArray.slice(-limit);
return validEntries.map(entry => ({
timestamp: entry.timestamp,
key: entry.key,
value: this.config.compressionEnabled ? this.decompressData(entry.value) : entry.value
}));
}
catch (error) {
this.logger.error(`Failed to get latest metrics for ${key}:`, error);
return [];
}
}
getMemoryStats() {
const timelineEntries = Array.from(this.timelines.values()).reduce((sum, timeline) => sum + timeline.length, 0);
const metricsEntries = Array.from(this.metrics.values()).reduce((sum, metrics) => sum + metrics.length, 0);
const memoryUsage = process.memoryUsage();
const memoryUsageMB = memoryUsage.heapUsed / 1024 / 1024;
return {
totalAllocated: timelineEntries + metricsEntries,
timelineEntries,
metricsEntries,
lastCleanup: this.lastCleanupTime || 0,
memoryUsageMB,
compressionRatio: this.calculateCompressionRatio()
};
}
async cleanup() {
if (!this.config.enabled) {
return;
}
try {
const startTime = Date.now();
let cleanedEntries = 0;
cleanedEntries += this.cleanupOldTimelineEntries();
cleanedEntries += this.cleanupOldMetricsEntries();
cleanedEntries += this.cleanupAllExpiredMetrics();
if (global.gc) {
global.gc();
}
const duration = Date.now() - startTime;
this.lastCleanupTime = Date.now();
this.logger.debug(`Memory cleanup completed: ${cleanedEntries} entries removed in ${duration}ms`);
}
catch (error) {
this.logger.error('Memory cleanup failed:', error);
}
}
clearTimeline(timelineId) {
try {
this.timelines.delete(timelineId);
this.logger.debug(`Timeline cleared: ${timelineId}`);
}
catch (error) {
this.logger.error(`Failed to clear timeline ${timelineId}:`, error);
}
}
clearMetrics(key) {
try {
this.metrics.delete(key);
this.logger.debug(`Metrics cleared: ${key}`);
}
catch (error) {
this.logger.error(`Failed to clear metrics ${key}:`, error);
}
}
clearAll() {
try {
this.timelines.clear();
this.metrics.clear();
this.logger.debug('All memory data cleared');
}
catch (error) {
this.logger.error('Failed to clear all data:', error);
}
}
shutdown() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
this.logger.debug('Memory manager shutdown');
}
startCleanupTimer() {
this.cleanupTimer = setInterval(() => {
this.cleanup();
}, this.config.cleanupIntervalMs);
}
cleanupOldTimelineEntries() {
let removedCount = 0;
const cutoff = Date.now() - this.config.metricsRetentionMs;
for (const [timelineId, timeline] of this.timelines.entries()) {
const originalLength = timeline.length;
const newTimeline = timeline.filter(entry => entry.timestamp >= cutoff);
if (newTimeline.length > this.config.maxTimelineSize) {
newTimeline.splice(0, newTimeline.length - this.config.maxTimelineSize);
}
this.timelines.set(timelineId, newTimeline);
removedCount += originalLength - newTimeline.length;
}
return removedCount;
}
cleanupOldMetricsEntries() {
let removedCount = 0;
const cutoff = Date.now() - this.config.metricsRetentionMs;
for (const [key, metricsArray] of this.metrics.entries()) {
const originalLength = metricsArray.length;
const newMetrics = metricsArray.filter(entry => entry.timestamp >= cutoff);
this.metrics.set(key, newMetrics);
removedCount += originalLength - newMetrics.length;
}
return removedCount;
}
cleanupAllExpiredMetrics() {
let removedCount = 0;
for (const key of this.metrics.keys()) {
removedCount += this.cleanupExpiredMetrics(key);
}
return removedCount;
}
cleanupExpiredMetrics(key) {
const metricsArray = this.metrics.get(key);
if (!metricsArray) {
return 0;
}
const now = Date.now();
const originalLength = metricsArray.length;
const validMetrics = metricsArray.filter(entry => !entry.ttl || entry.ttl > now);
this.metrics.set(key, validMetrics);
return originalLength - validMetrics.length;
}
checkMemoryUsage() {
const memoryUsage = process.memoryUsage();
const memoryUsageMB = memoryUsage.heapUsed / 1024 / 1024;
if (memoryUsageMB > this.config.memoryThresholdMB) {
this.logger.warn(`Memory threshold exceeded: ${memoryUsageMB.toFixed(2)}MB > ${this.config.memoryThresholdMB}MB`);
if (this.config.autoCleanup) {
setImmediate(() => this.cleanup());
}
}
}
compressData(data) {
try {
if (typeof data === 'object' && data !== null) {
const jsonString = JSON.stringify(data);
if (jsonString.length > 1000) {
return this.simplifyObject(data);
}
}
return data;
}
catch (error) {
return data;
}
}
decompressData(data) {
return data;
}
simplifyObject(obj) {
if (Array.isArray(obj)) {
return obj.length > 100 ? obj.slice(0, 100) : obj;
}
if (typeof obj === 'object' && obj !== null) {
const simplified = {};
let propertyCount = 0;
for (const [key, value] of Object.entries(obj)) {
if (propertyCount >= 50)
break;
if (typeof value === 'string' && value.length > 500) {
simplified[key] = value.substring(0, 500) + '...';
}
else if (typeof value === 'object') {
simplified[key] = this.simplifyObject(value);
}
else {
simplified[key] = value;
}
propertyCount++;
}
return simplified;
}
return obj;
}
calculateCompressionRatio() {
const timelineCount = Array.from(this.timelines.values()).reduce((sum, timeline) => sum + timeline.length, 0);
const metricsCount = Array.from(this.metrics.values()).reduce((sum, metrics) => sum + metrics.length, 0);
if (timelineCount + metricsCount === 0) {
return 1;
}
return 0.7;
}
};
exports.EnhancedMemoryManagerService = EnhancedMemoryManagerService;
exports.EnhancedMemoryManagerService = EnhancedMemoryManagerService = EnhancedMemoryManagerService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [Object])
], EnhancedMemoryManagerService);
//# sourceMappingURL=enhanced-memory-manager.service.js.map