performance-monitor-logger
Version:
## Instalação
1,690 lines • 76.7 kB
JavaScript
import { Component, NgModule, ɵɵdefineInjectable, Injectable } from '@angular/core';
import { __awaiter } from 'tslib';
import { HttpResponse } from '@angular/common/http';
import { tap } from 'rxjs/operators';
class PerformanceMonitorComponent {
constructor() { }
ngOnInit() { }
}
PerformanceMonitorComponent.decorators = [
{ type: Component, args: [{
selector: 'lib-performanceMonitorLogger',
template: ` <p>performance-monitor-logger works!</p> `
},] }
];
PerformanceMonitorComponent.ctorParameters = () => [];
// performance-monitor.module.ts
class PerformanceMonitorModule {
}
PerformanceMonitorModule.decorators = [
{ type: NgModule, args: [{
declarations: [PerformanceMonitorComponent],
imports: [],
exports: [PerformanceMonitorComponent],
},] }
];
class PerformanceMonitorService {
constructor() { }
}
PerformanceMonitorService.ɵprov = ɵɵdefineInjectable({ factory: function PerformanceMonitorService_Factory() { return new PerformanceMonitorService(); }, token: PerformanceMonitorService, providedIn: "root" });
PerformanceMonitorService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] }
];
PerformanceMonitorService.ctorParameters = () => [];
/**
* 📊 SISTEMA DE LOGS DE PERFORMANCE
*
* Sistema completo de monitoramento de performance para aplicações Angular
* com feedback visual através de emojis e relatórios automáticos.
*
* Características:
* - 📈 Monitoramento em Tempo Real
* - 🟢🟡🔴 Classificação Visual por Performance
* - ⏰ Relatórios Automáticos após Inatividade
* - 📊 Análise Detalhada com Recomendações
* - 🛠️ API Pública para Controle Manual
*
* @author Pedro Henrique dos Santos Leuchs
* @version 1.0.23
* @since 2025-07-11
*/
class LogsPerformatico {
constructor(serviceName = 'Service') {
this.monitoramentoAtivo = true; // Começa ativo na primeira vez
// 📊 Propriedades do Sistema
this.performanceTimers = new Map();
this.performanceSummary = new Map();
this.httpRequests = new Map();
// 🆕 Stack para contexto de função monitorada
this.functionContextStack = [];
this.eventContextStack = [];
this.timerStack = [];
// 🆕 Estado de atividade para fechamento inteligente de contexto de UI
this.lastTimerActivityAt = null;
this.firstTimerInEventAt = null;
this.currentEventBeginAt = null;
// 🔒 Controle de funções já instrumentadas
this.instrumentedFns = new WeakMap();
this.serviceName = serviceName;
}
/**
* Registra requisição HTTP associando à função monitorada ativa (se houver)
* @param payload Dados da requisição
*/
registerHttpRequest(authReq, tempo, error = false) {
var _a, _b;
// Nome robusto da operação HTTP
let nome = '(desconhecido)';
try {
const byBodyName = (_a = authReq === null || authReq === void 0 ? void 0 : authReq.body) === null || _a === void 0 ? void 0 : _a.name;
const bySql = this.extrairNomeDeSQL((_b = authReq === null || authReq === void 0 ? void 0 : authReq.body) === null || _b === void 0 ? void 0 : _b.sqlInstruction);
const method = (authReq === null || authReq === void 0 ? void 0 : authReq.method) || 'HTTP';
const url = authReq === null || authReq === void 0 ? void 0 : authReq.url;
// Usa nome planejado (body/sql), senão fallback para método + último segmento da URL
if (byBodyName) {
nome = byBodyName;
}
else if (bySql) {
nome = bySql;
}
else if (url) {
const cleanUrl = String(url).split('?')[0];
const segs = cleanUrl.split('/').filter(Boolean);
const tail = segs[segs.length - 1] || cleanUrl;
nome = `${method} ${tail}`;
}
else {
nome = method;
}
}
catch (_c) { }
const currentEvent = this.eventContextStack.length
? this.eventContextStack[this.eventContextStack.length - 1]
: undefined;
const key = currentEvent ? `${currentEvent} | ${nome}` : nome;
const atual = this.httpRequests.get(key) || {
tempoTotal: 0,
execucoes: 0,
errors: 0,
};
atual.tempoTotal += Number.isFinite(tempo) ? tempo : 0;
atual.execucoes += 1;
if (error)
atual.errors += 1;
this.httpRequests.set(key, atual);
}
// Função auxiliar para extrair nome da função de um SQL, ex: PACK_PARMGEN.CONSULTA_PARAMETRO(...)
extrairNomeDeSQL(sql) {
if (!sql)
return null;
const match = sql.match(/([A-Z0-9_]+\.[A-Z0-9_]+)/i);
return match ? match[1] : null;
}
/**
* 🚀 Mensagem de boas-vindas do sistema
*/
showWelcomeMessage() {
console.log(`%cSISTEMA DE MONITORAMENTO DE PERFORMANCE`, 'color: #bbc7c8ff; font-size: 18px; font-weight: bold; text-shadow: 1px 1px #000; background-color: #43248bff; border-radius: 5px; padding: 10px 20px;');
}
/**
* ⏱️ Inicia cronômetro para uma operação
* @param operationName Nome da operação a ser monitorada
*/
startTimer(operationName) {
if (!this.monitoramentoAtivo)
return;
// Se for a primeira operação, abre borda visual
if (this.performanceTimers.size === 0 &&
this.performanceSummary.size === 0) {
const border = '═'.repeat(56);
console.log(`%cINÍCIO DO MONITORAMENTO DE PERFORMANCE`, 'color: #bbc7c8ff; font-size: 18px; font-weight: bold; text-shadow: 1px 1px #000; background-color: #43248bff; border-radius: 5px; padding: 10px 20px;');
}
this.performanceTimers.set(operationName, Date.now());
// Empilha contexto de função monitorada
this.functionContextStack.push(operationName);
// Adiciona contexto ao stack para medir tempo exclusivo
const currentEvent = this.eventContextStack.length
? this.eventContextStack[0] // usa rótulo raiz do fluxo do usuário
: undefined;
// Se a operação vier como pack.metodo, usamos pack como evento e metodo como nome
const parts = String(operationName).split('.');
let displayEvent;
let displayName;
if (parts.length >= 2) {
displayEvent = parts[0];
displayName = parts[parts.length - 1];
}
else {
// Sem prefixo (pack) no nome — exibe apenas o método
displayEvent = undefined;
displayName = operationName;
}
const fullName = displayEvent
? `${displayEvent} | ${displayName}`
: displayName;
// Registra atividade de timer para controle de janela do evento
const nowStart = Date.now();
if (currentEvent && this.firstTimerInEventAt === null) {
this.firstTimerInEventAt = nowStart;
}
this.lastTimerActivityAt = nowStart;
this.timerStack.push({
name: operationName,
start: nowStart,
childElapsed: 0,
event: currentEvent,
fullName,
});
console.log(`║ [INÍCIO] ${fullName} - ${new Date().toLocaleTimeString()}`);
}
/**
* 🏁 Finaliza cronômetro e exibe resultado com emoji
* @param operationName Nome da operação
* @param customMessage Mensagem customizada opcional (ex: para erros)
* @param customThresholds Thresholds customizados para esta operação específica
*/
endTimer(operationName, customMessage, customThresholds) {
var _a, _b, _c, _d, _e, _f;
if (!this.monitoramentoAtivo)
return;
// Usa pilha de contexto para calcular tempo exclusivo (exclui filhos)
const now = Date.now();
let ctx = this.timerStack.length
? this.timerStack[this.timerStack.length - 1]
: undefined;
if (!ctx || ctx.name !== operationName) {
// Busca robusta caso encerramento fora de ordem
let foundIndex = -1;
for (let i = this.timerStack.length - 1; i >= 0; i--) {
if (this.timerStack[i].name === operationName) {
foundIndex = i;
break;
}
}
if (foundIndex === -1) {
console.warn(`⚠️ Timer não encontrado para: ${operationName}`);
return;
}
ctx = this.timerStack[foundIndex];
this.timerStack.splice(foundIndex, 1);
}
else {
this.timerStack.pop();
}
// Atualiza atividade de timers (para lógica de fechamento por silêncio)
this.lastTimerActivityAt = now;
const inclusiveElapsed = now - ctx.start;
const exclusiveElapsed = Math.max(0, inclusiveElapsed - ctx.childElapsed);
// Propaga tempo do filho para o pai (para excluir na contagem do pai)
const parent = this.timerStack[this.timerStack.length - 1];
if (parent) {
parent.childElapsed += inclusiveElapsed;
}
// Remove referência antiga baseada em Map (mantida para compatibilidade)
this.performanceTimers.delete(operationName);
// Desempilha contexto de função monitorada (somente se topo)
if (this.functionContextStack[this.functionContextStack.length - 1] ===
operationName) {
this.functionContextStack.pop();
}
// Determina status baseado no tempo exclusivo
const { emoji, status, color } = this.getPerformanceStatus(exclusiveElapsed, customThresholds, (_a = ctx.fullName) !== null && _a !== void 0 ? _a : (ctx.event ? `${ctx.event} | ${operationName}` : operationName));
// Log com emoji e cor (com mensagem customizada se fornecida)
const message = customMessage
? `${(_b = ctx.fullName) !== null && _b !== void 0 ? _b : (ctx.event ? `${ctx.event} | ${operationName}` : operationName)} - ${customMessage}`
: `${(_c = ctx.fullName) !== null && _c !== void 0 ? _c : (ctx.event ? `${ctx.event} | ${operationName}` : operationName)}`;
const fixedStatus = `[${status}]`.padEnd(5);
const fixedMessage = message.padEnd(10);
const fixedTime = exclusiveElapsed.toString().padStart(5);
console.log(`║ %c${emoji} ${fixedStatus} ${fixedMessage} - ${fixedTime}ms `, color);
// Mostra thresholds customizados se estiverem sendo usados
if (customThresholds &&
(customThresholds.ok ||
customThresholds.lento ||
customThresholds.critico)) {
console.log(`⚙️ Thresholds customizados: OK<${(_d = customThresholds.ok) !== null && _d !== void 0 ? _d : 500}ms, LENTO<${(_e = customThresholds.lento) !== null && _e !== void 0 ? _e : 2000}ms`);
}
// Atualiza sumário com tempo exclusivo
this.updatePerformanceSummary((_f = ctx.fullName) !== null && _f !== void 0 ? _f : (ctx.event ? `${ctx.event} | ${operationName}` : operationName), exclusiveElapsed, customThresholds);
}
/**
* 🎨 Determina status de performance baseado no tempo
* @param elapsedTime Tempo decorrido em milissegundos
* @param customThresholds Thresholds customizados para esta operação específica
* @param operationName Nome da operação (para calcular thresholds dinâmicos)
* @returns Objeto com emoji, status e cor
*/
getPerformanceStatus(elapsedTime, customThresholds, operationName) {
var _a, _b, _c;
let okThreshold;
let lentoThreshold;
let criticoThreshold;
if (customThresholds &&
(customThresholds.ok ||
customThresholds.lento ||
customThresholds.critico)) {
// Usa thresholds customizados se fornecidos
okThreshold = (_a = customThresholds.ok) !== null && _a !== void 0 ? _a : 500;
lentoThreshold = (_b = customThresholds.lento) !== null && _b !== void 0 ? _b : 2000;
criticoThreshold = (_c = customThresholds.critico) !== null && _c !== void 0 ? _c : Number.MAX_SAFE_INTEGER;
}
else if (operationName) {
// Calcula thresholds dinâmicos baseados na quantidade de execuções
const stats = this.performanceSummary.get(operationName);
// Corrige: soma +1 para considerar a execução atual
const executionCount = stats ? stats.count + 1 : 1;
const dynamicThresholds = this.calculateDynamicThresholds(executionCount);
okThreshold = dynamicThresholds.ok;
lentoThreshold = dynamicThresholds.lento;
criticoThreshold = dynamicThresholds.critico;
}
else {
// Usa thresholds padrões
okThreshold = 500;
lentoThreshold = 2000;
criticoThreshold = Number.MAX_SAFE_INTEGER;
}
if (elapsedTime < okThreshold) {
return {
emoji: '🟢',
status: 'OK',
color: 'color: green; font-weight: bold;',
};
}
else if (elapsedTime < lentoThreshold) {
return {
emoji: '🟡',
status: 'LENTO',
color: 'color: orange; font-weight: bold;',
};
}
else {
return {
emoji: '🔴',
status: 'CRÍTICO',
color: 'color: red; font-weight: bold;',
};
}
}
/**
* 📈 Atualiza estatísticas de performance para o sumário
* @param operationName Nome da operação
* @param elapsedTime Tempo decorrido em milissegundos
* @param customThresholds Thresholds customizados para armazenar (opcional)
*/
updatePerformanceSummary(operationName, elapsedTime, customThresholds) {
const existing = this.performanceSummary.get(operationName);
if (existing) {
existing.count++;
existing.totalTime += elapsedTime;
existing.minTime = Math.min(existing.minTime, elapsedTime);
existing.maxTime = Math.max(existing.maxTime, elapsedTime);
// Atualiza thresholds customizados se fornecidos
if (customThresholds &&
(customThresholds.ok ||
customThresholds.lento ||
customThresholds.critico)) {
existing.customThresholds = customThresholds;
}
}
else {
this.performanceSummary.set(operationName, {
count: 1,
totalTime: elapsedTime,
minTime: elapsedTime,
maxTime: elapsedTime,
customThresholds: customThresholds,
});
}
// Não faz mais nada aqui, relatório só manual
}
/**
* 📊 Exibe relatório resumido de todas as operações (compactado)
*/
showPerformanceSummary(showFullDetails = false) {
// Fecha borda do monitoramento ao gerar relatório
console.log('%c RELATÓRIO DE PERFORMANCE', 'color: #bbc7c8ff; font-size: 18px; font-weight: bold; text-shadow: 1px 1px #000; background-color: #43248bff; border-radius: 5px; padding: 10px 20px;');
if (this.performanceSummary.size === 0) {
console.log('%c Nenhuma operação monitorada ainda.', 'color: gray; font-style: italic;');
return;
}
const sortedEntries = Array.from(this.performanceSummary.entries()).sort((a, b) => b[1].maxTime - a[1].maxTime);
const table = Object.fromEntries(sortedEntries.map(([operation, stats]) => {
const avgTime = Math.round(stats.totalTime / stats.count);
const { emoji, status } = this.getPerformanceStatus(avgTime, stats.customThresholds, operation);
return [
operation,
{
Execuções: stats.count,
'Média(ms)': `${avgTime}ms (${status}) ${emoji}`.trim(),
'Mínimo(ms)': stats.minTime,
'Máximo(ms)': stats.maxTime,
'Total(ms)': stats.totalTime,
},
];
}));
console.table(table);
// Removido: agora a análise detalhada é chamada apenas pelo botão
}
/**
* 📑 Exibe relatório detalhado de requisições HTTP agrupadas por função
*/
showHttpRequestsReport() {
console.log(`%cRELATÓRIO DETALHADO DE REQUISIÇÕES HTTP`, 'color: #bbc7c8ff; font-size: 18px; font-weight: bold; text-shadow: 1px 1px #000; background-color: #43248bff; border-radius: 5px; padding: 10px 20px;');
if (this.httpRequests.size === 0) {
console.log('Nenhuma requisição HTTP registrada.');
return;
}
const tabela = Object.fromEntries(Array.from(this.httpRequests.entries())
.sort((a, b) => b[1].tempoTotal - a[1].tempoTotal)
.map(([nome, dados]) => [
nome,
{
Execuções: dados.execucoes,
Média: `${Math.round(dados.tempoTotal / dados.execucoes)}ms`,
'Tempo Total': `${Math.round(dados.tempoTotal)}ms`,
Erros: dados.errors,
},
]));
console.table(tabela);
}
/**
* 🔍 Gera análise detalhada de performance
*/
generateDetailedAnalysis() {
// Título discreto
console.log('%cANÁLISE DETALHADA DE PERFORMANCE', 'color: #bbc7c8ff; font-size: 18px; font-weight: bold; text-shadow: 1px 1px #000; background-color: #43248bff; border-radius: 5px; padding: 10px 20px;');
console.log('');
if (this.performanceSummary.size === 0) {
console.log('%cNenhuma operação registrada para análise.', 'color: #bbb; font-style: italic; font-size: 14px; padding: 4px 0 4px 10px;');
return;
}
const { criticalOperations, slowOperations, fastOperations } = this.categorizeOperations();
// Helper para bloco discreto com texto claro
const printBlock = (color, emoji, operation, avg, extra) => {
const style = `border-left: 4px solid ${color}; padding: 2px 0 2px 10px; margin: 2px 0 2px 0; font-size: 14px; color: #fff; background: none; font-weight: normal;`;
console.log(`%c${emoji} ${operation} - ${avg}ms (média) ${extra}`.trim(), style);
};
if (criticalOperations.length > 0) {
console.log('%c🔴 CRÍTICAS ( > 2000ms)', 'color: #fff; font-size: 14px; font-weight: bold; margin-top: 10px;');
criticalOperations.forEach(([operation, stats]) => {
const avg = Math.round(stats.totalTime / stats.count);
const { emoji } = this.getPerformanceStatus(avg, stats.customThresholds, operation);
printBlock('#c53030', emoji, operation, avg, '| otimizar urgente');
});
}
if (slowOperations.length > 0) {
console.log('%c🟡 LENTAS (500ms - 2000ms)', 'color: #fff; font-size: 14px; font-weight: bold; margin-top: 10px;');
slowOperations.forEach(([operation, stats]) => {
const avg = Math.round(stats.totalTime / stats.count);
const { emoji } = this.getPerformanceStatus(avg, stats.customThresholds, operation);
printBlock('#b7791f', emoji, operation, avg, '| pode ser melhorada');
});
}
if (fastOperations.length > 0) {
console.log('%c🟢 RÁPIDAS ( < 500ms)', 'color: #fff; font-size: 14px; font-weight: bold; margin-top: 10px;');
fastOperations.forEach(([operation, stats]) => {
const avg = Math.round(stats.totalTime / stats.count);
const { emoji } = this.getPerformanceStatus(avg, stats.customThresholds, operation);
printBlock('#38a169', emoji, operation, avg, '| excelente');
});
}
// Recomendações
console.log('%cRecomendações:', 'color: #fff; font-size: 13px; font-weight: bold; margin-top: 14px; padding: 2px 0 2px 0;');
if (criticalOperations.length > 0) {
console.log('%c• Priorize a otimização das operações críticas.', 'color: #fff; font-size: 13px; font-weight: normal;');
console.log('%c• Considere implementar cache ou otimizar consultas SQL para essas operações.', 'color: #fff; font-size: 13px; font-weight: normal;');
}
if (slowOperations.length > 0) {
console.log('%c• Operações lentas podem se beneficiar de índices no banco de dados.', 'color: #fff; font-size: 13px; font-weight: normal;');
}
if (criticalOperations.length === 0 && slowOperations.length === 0) {
console.log('%c• Todas as operações estão com performance excelente!', 'color: #fff; font-size: 13px; font-weight: normal;');
}
// Score
const totalOperations = this.performanceSummary.size;
const fastCount = fastOperations.length;
const performanceScore = totalOperations > 0
? Math.round((fastCount / totalOperations) * 100)
: 100;
let scoreColor = performanceScore >= 80
? '#38a169'
: performanceScore >= 60
? '#f6e05e'
: '#c53030';
let scoreMsg = performanceScore >= 80
? 'Excelente! A maioria das operações está performando bem.'
: performanceScore >= 60
? 'Médio. Há espaço para melhorias de performance.'
: 'Crítico. Muitas operações precisam de otimização urgente.';
console.log(`%cScore de performance: ${performanceScore}%`, `color: #fff; background: ${scoreColor}; font-size: 14px; font-weight: bold; margin-top: 10px; padding: 2px 8px; border-radius: 3px;`);
console.log(`%c${scoreMsg}`, `color: #fff; font-size: 13px; font-weight: normal; margin-bottom: 8px;`);
console.log('');
}
/**
* 📋 Categoriza operações por performance
* @returns Objeto com arrays de operações categorizadas
*/
categorizeOperations() {
const criticalOperations = Array.from(this.performanceSummary.entries()).filter(([_, stats]) => stats.totalTime / stats.count > 2000);
const slowOperations = Array.from(this.performanceSummary.entries()).filter(([_, stats]) => {
const avg = stats.totalTime / stats.count;
return avg >= 500 && avg <= 2000;
});
const fastOperations = Array.from(this.performanceSummary.entries()).filter(([_, stats]) => stats.totalTime / stats.count < 500);
return { criticalOperations, slowOperations, fastOperations };
}
/**
* 💡 Exibe recomendações baseadas na análise
* @param criticalOperations Array de operações críticas
* @param slowOperations Array de operações lentas
*/
showRecommendations(criticalOperations, slowOperations) {
console.log('RECOMENDAÇÕES:');
if (criticalOperations.length > 0) {
console.log(' Priorize a otimização das operações críticas.');
console.log(' Considere implementar cache ou otimizar consultas SQL para essas operações.');
}
if (slowOperations.length > 0) {
console.log(' Operações lentas podem se beneficiar de índices no banco de dados.');
}
if (criticalOperations.length === 0 && slowOperations.length === 0) {
console.log(' Todas as operações estão com performance excelente!');
}
}
/**
* 📊 Exibe score de performance
* @param fastOperationsCount Número de operações rápidas
*/
showPerformanceScore(fastOperationsCount) {
const totalOperations = this.performanceSummary.size;
const performanceScore = Math.round((fastOperationsCount / totalOperations) * 100);
console.log(`SCORE DE PERFORMANCE: ${performanceScore}%`);
if (performanceScore >= 80) {
console.log('Excelente! A maioria das operações está performando bem.');
}
else if (performanceScore >= 60) {
console.log('Médio. Há espaço para melhorias de performance.');
}
else {
console.log('Crítico. Muitas operações precisam de otimização urgente.');
}
}
// ===== API PÚBLICA =====
/**
* 📊 Método público para exibir relatório de performance
* Pode ser chamado no console do navegador para análise
*/
getPerformanceReport() {
this.showPerformanceSummary();
}
/**
* 🏷️ Controla contexto de evento atual (ex.: AppInit, Click)
*/
beginEventContext(label) {
if (label) {
// Se não há contexto aberto, inicia uma nova janela raiz
if (this.eventContextStack.length === 0) {
this.currentEventBeginAt = Date.now();
this.firstTimerInEventAt = null;
this.lastTimerActivityAt = null;
}
this.eventContextStack.push(label);
}
}
endEventContext() {
if (this.eventContextStack.length > 0) {
this.eventContextStack.pop();
}
}
runInEventContext(label, fn) {
return __awaiter(this, void 0, void 0, function* () {
this.beginEventContext(label);
try {
const result = fn();
return yield Promise.resolve(result);
}
finally {
this.endEventContext();
}
});
}
/**
* 🧹 Método público para limpar dados de performance
* Útil para resetar estatísticas e começar nova análise
*/
resetPerformanceData() {
this.performanceTimers.clear();
this.performanceSummary.clear();
this.httpRequests.clear();
// Mensagem estilizada de limpeza
console.log('%c🧹 [CLEANUP] Dados de performance limpos', 'color: #fff; background: #43248bff; font-size: 13px; font-weight: bold; border-radius: 4px; padding: 4px 12px; margin: 6px 0;');
}
/**
* ⏰ Configura o delay para relatório por inatividade
* @param seconds Número de segundos de inatividade para mostrar relatório
*/
setInactivityDelay(seconds) {
// Desabilitado
console.log('⏰ Relatório por inatividade está desabilitado.');
}
/**
* 🔇 Desabilita relatório automático por inatividade
*/
disableInactivityReport() {
// Desabilitado
console.log('🔇 Relatório por inatividade já está desabilitado');
}
/**
* ⚙️ Configura o threshold para relatório automático
* @param threshold Número de operações para mostrar relatório automaticamente
*/
setAutoReportThreshold(threshold) {
// Desabilitado
console.log('⚙️ Threshold para relatório automático está desabilitado.');
}
/**
* 🔇 Desabilita relatório automático
*/
disableAutoReport() {
// Desabilitado
console.log('🔇 Relatório automático desabilitado');
}
/**
* 📈 Obtém estatísticas resumidas sem exibir no console
* @returns Objeto com estatísticas de performance
*/
getStatistics() {
const { criticalOperations, slowOperations, fastOperations } = this.categorizeOperations();
const totalOperations = this.performanceSummary.size;
let totalTime = 0;
let totalCount = 0;
this.performanceSummary.forEach((stats) => {
totalTime += stats.totalTime;
totalCount += stats.count;
});
const averageTime = totalCount > 0 ? Math.round(totalTime / totalCount) : 0;
const performanceScore = totalOperations > 0
? Math.round((fastOperations.length / totalOperations) * 100)
: 100;
return {
totalOperations,
averageTime,
fastOperations: fastOperations.length,
slowOperations: slowOperations.length,
criticalOperations: criticalOperations.length,
performanceScore,
};
}
/**
* 🎯 Obtém dados de uma operação específica
* @param operationName Nome da operação
* @returns Estatísticas da operação ou null se não encontrada
*/
getOperationStats(operationName) {
const baseStats = this.performanceSummary.get(operationName);
if (!baseStats)
return null;
// Extrai nomes das requisições HTTP como array de strings
const httpRequestEntry = this.httpRequests.get(operationName);
const httpCalls = httpRequestEntry ? [operationName] : [];
return Object.assign(Object.assign({}, baseStats), { httpCalls });
}
/**
* 📋 Lista todas as operações monitoradas
* @returns Array com nomes das operações
*/
getMonitoredOperations() {
return Array.from(this.performanceSummary.keys());
}
/**
* 🔄 Verifica se há timers ativos
* @returns true se há timers em execução
*/
hasActiveTimers() {
return this.timerStack.length > 0;
}
/**
* 🗑️ Limpa apenas os timers ativos (não as estatísticas)
*/
clearActiveTimers() {
this.performanceTimers.clear();
this.timerStack = [];
this.functionContextStack = [];
console.log('%c🗑️ [CLEANUP] Timers ativos limpos', 'color: #fff; background: #23272f; font-size: 13px; font-weight: bold; border-radius: 4px; padding: 4px 12px; margin: 6px 0;');
}
// ===== AUTO‑INSTRUMENTAÇÃO (sem decorators) =====
/**
* 🔌 Instrumenta todas as funções públicas de um objeto/instância sem alterar assinatura
* @param obj Objeto ou instância a instrumentar
* @param options Filtros e ajustes de nome
*/
instrumentObject(obj, options) {
if (!obj || typeof obj !== 'object')
return;
const opt = Object.assign({ preserveToString: true }, options);
const shouldInclude = (name) => {
const inc = opt.include;
const exc = opt.exclude;
const match = (p) => typeof p === 'string' ? name.includes(p) : p.test(name);
const okInc = !inc || inc.some(match);
const okExc = exc ? !exc.some(match) : true;
return okInc && okExc;
};
const instrumentProp = (target, key, path) => {
try {
const value = target[key];
if (typeof value !== 'function')
return;
const original = value;
if (original.__pm_instrumented)
return;
if (!shouldInclude(key))
return;
const opBase = key;
const opName = opt.nameTransform
? opt.nameTransform(opBase, path)
: opBase;
const wrapped = this.createWrapper(original, opName, opt.preserveToString);
// Sombra no objeto (não altera prototype global)
Object.defineProperty(target, key, {
value: wrapped,
writable: true,
configurable: true,
});
}
catch (_a) { }
};
// Instrumenta próprias propriedades
Object.getOwnPropertyNames(obj).forEach((k) => instrumentProp(obj, k, [k]));
// Instrumenta métodos do prototype (sobrescrevendo no instance)
const proto = Object.getPrototypeOf(obj);
if (proto && proto !== Object.prototype) {
Object.getOwnPropertyNames(proto)
.filter((k) => k !== 'constructor')
.forEach((k) => instrumentProp(obj, k, [k]));
}
}
/**
* 🌐 Instrumenta funções em um namespace (ex.: window) com filtros e profundidade
* @param root Objeto raiz (ex.: window)
* @param options include/exclude, nameTransform, preserveToString, maxDepth
*/
instrumentNamespace(root, options) {
if (!root || typeof root !== 'object')
return;
const opt = Object.assign({ preserveToString: true, maxDepth: 2 }, options);
const shouldInclude = (name) => {
const inc = opt.include;
const exc = opt.exclude;
const match = (p) => typeof p === 'string' ? name.includes(p) : p.test(name);
const okInc = !inc || inc.some(match);
const okExc = exc ? !exc.some(match) : true;
return okInc && okExc;
};
const visited = new WeakSet();
const walk = (node, depth, path) => {
if (!node || typeof node !== 'object')
return;
if (visited.has(node))
return;
visited.add(node);
if (depth > (opt.maxDepth || 2))
return;
const keys = [];
try {
keys.push(...Object.getOwnPropertyNames(node));
}
catch (_a) { }
for (const key of keys) {
let value;
try {
value = node[key];
}
catch (_b) {
continue;
}
if (typeof value === 'function') {
const original = value;
if (original.__pm_instrumented)
continue;
if (!shouldInclude(key))
continue;
const opBase = key;
const opName = opt.nameTransform
? opt.nameTransform(opBase, path.concat(key))
: opBase;
const wrapped = this.createWrapper(original, opName, opt.preserveToString);
// Tenta sobrescrever função no namespace
try {
Object.defineProperty(node, key, {
value: wrapped,
writable: true,
configurable: true,
});
}
catch (_c) { }
}
else if (typeof value === 'object' && value) {
// Recurse
walk(value, depth + 1, path.concat(key));
}
}
};
walk(root, 0, []);
}
/**
* 🪝 Envolve a função setService(target.setService) para instrumentar o serviço passado
* @param target Objeto que possui setService(service)
* @param label Rótulo usado no nome das operações (ex.: "packTela")
* @param options Opções de instrumentação (nameTransform, exclude, etc.)
*/
hookSetService(target, label, options) {
var _a;
if (!target || typeof target !== 'object')
return;
const fn = target.setService;
if (typeof fn !== 'function')
return;
const alreadyHooked = fn.__pm_setservice_hooked;
if (alreadyHooked)
return;
const opt = Object.assign({ preserveToString: true, exclude: ['constructor'] }, options);
const labelFinal = label ||
(((_a = target === null || target === void 0 ? void 0 : target.constructor) === null || _a === void 0 ? void 0 : _a.name) ? String(target.constructor.name) : 'service');
// Garante que o label (pack) entre na path[0] quando houver nameTransform customizado
const nameTransform = opt.nameTransform
? (n, path) => opt.nameTransform(n, [labelFinal].concat(path || []))
: (n) => `${labelFinal}.${n}`;
const original = fn;
const logger = this;
const wrapped = function (serviceInstance, ...rest) {
try {
// Instrumenta o próprio objeto alvo (pack), pois são seus métodos que queremos medir
const packObj = this || target;
if (packObj && typeof packObj === 'object') {
const alreadyInstrumentedPack = packObj
.__pm_pack_instrumented;
if (!alreadyInstrumentedPack) {
logger.instrumentObject(packObj, Object.assign(Object.assign({}, opt), { nameTransform }));
try {
Object.defineProperty(packObj, '__pm_pack_instrumented', {
value: true,
configurable: true,
enumerable: false,
writable: false,
});
}
catch (_a) { }
}
}
}
catch (_b) { }
return original.apply(this, [serviceInstance, ...rest]);
};
// marca como hookado para evitar duplicação
wrapped.__pm_setservice_hooked = true;
try {
Object.defineProperty(target, 'setService', {
value: wrapped,
writable: true,
configurable: true,
});
}
catch (_b) { }
// Instrumenta imediatamente o pack (cobre casos onde setService já foi chamado antes do hook)
try {
const packObj = target;
const alreadyInstrumentedPack = packObj.__pm_pack_instrumented;
if (!alreadyInstrumentedPack) {
this.instrumentObject(packObj, Object.assign(Object.assign({}, opt), { nameTransform }));
Object.defineProperty(packObj, '__pm_pack_instrumented', {
value: true,
configurable: true,
enumerable: false,
writable: false,
});
}
}
catch (_c) { }
}
/**
* 🪝 Hook de setService para vários alvos em uma única chamada
* @param targets Mapa de label -> objeto que contém setService
* @param options Opções de instrumentação
*/
hookSetServiceBulk(targets, options) {
if (!targets || typeof targets !== 'object')
return;
for (const [label, obj] of Object.entries(targets)) {
const hasSetService = !!(obj && typeof obj.setService === 'function');
if (hasSetService) {
this.hookSetService(obj, label, options);
}
else if (obj && typeof obj === 'object') {
// Instrumenta objetos sem setService usando o label como prefixo
const opt = Object.assign({}, options);
const nameTransform = opt.nameTransform
? (n, path) => opt.nameTransform(n, [String(label)].concat(path || []))
: (n) => `${String(label)}.${n}`;
this.instrumentObject(obj, Object.assign(Object.assign({}, opt), { nameTransform, exclude: ['constructor', ...(opt.exclude || [])] }));
}
}
}
/**
* 🌐 Varre um namespace e aplica hook em qualquer função setService encontrada
* @param root Objeto raiz (ex.: window, module scope wrapper, etc.)
* @param options Filtros e regras de nomeação
*/
hookSetServiceNamespace(root, options) {
if (!root || typeof root !== 'object')
return;
const opt = Object.assign({ preserveToString: true, exclude: ['constructor'], maxWalkDepth: 2 }, options);
const shouldIncludeObj = (pathStr) => {
const inc = opt.includeObjects;
const exc = opt.excludeObjects;
const match = (p) => typeof p === 'string' ? pathStr.includes(p) : p.test(pathStr);
const okInc = !inc || inc.some(match);
const okExc = exc ? !exc.some(match) : true;
return okInc && okExc;
};
const visited = new WeakSet();
const walk = (node, depth, path) => {
if (!node || typeof node !== 'object')
return;
if (visited.has(node))
return;
visited.add(node);
if (depth > (opt.maxWalkDepth || 2))
return;
const keys = [];
try {
keys.push(...Object.getOwnPropertyNames(node));
}
catch (_a) { }
for (const key of keys) {
let value;
try {
value = node[key];
}
catch (_b) {
continue;
}
const pathNow = path.concat(key);
const pathStr = pathNow.join('.');
if (value && typeof value === 'object') {
// Se o objeto tiver setService, aplica hook
const fn = value.setService;
if (typeof fn === 'function' && shouldIncludeObj(pathStr)) {
const labelFromPath = opt.labelFromPath || ((p) => p.join('.'));
this.hookSetService(value, labelFromPath(pathNow), opt);
}
// Continua a varredura
walk(value, depth + 1, pathNow);
}
}
};
walk(root, 0, []);
}
// ===== Helpers =====
/**
* Cria wrapper que inicia/encerra timer preservando contexto e toString()
*/
createWrapper(original, opName, preserveToString) {
// Idempotência: reutiliza wrapper se já existir
const existing = this.instrumentedFns.get(original);
if (existing)
return existing;
const logger = this;
const wrapped = function (...args) {
// Determina nome final com fallback ao nome do arquivo (pack) via stacktrace
let finalName = opName;
try {
const parts = String(opName).split('.');
const methodName = parts[parts.length - 1];
const prefix = parts.length > 1 ? parts[0] : undefined;
if (!prefix || prefix.toLowerCase() === methodName.toLowerCase()) {
const fileLabel = logger.guessFileLabelFromStack();
if (fileLabel) {
finalName = `${fileLabel}.${methodName}`;
}
}
}
catch (_a) { }
// Herda label do evento ativo (já garantido pelo logger)
logger.startTimer(finalName);
try {
const result = original.apply(this, args);
if (result && typeof result.then === 'function') {
// Promises/async
return result.finally(() => {
logger.endTimer(finalName);
});
}
else {
logger.endTimer(finalName);
return result;
}
}
catch (e) {
logger.endTimer(finalName, 'erro');
throw e;
}
};
// Marca instrumentado
original.__pm_instrumented = true;
wrapped.__pm_instrumented = true;
// Preserva toString() para compatibilidade com regex
if (preserveToString) {
try {
Object.defineProperty(wrapped, 'toString', {
value: function () {
return original.toString();
},
writable: false,
configurable: true,
});
}
catch (_a) { }
}
// Tenta igualar o nome (nem sempre é possível)
try {
Object.defineProperty(wrapped, 'name', {
value: original.name || opName,
configurable: true,
});
}
catch (_b) { }
this.instrumentedFns.set(original, wrapped);
return wrapped;
}
/**
* 🔎 Tenta inferir o nome do arquivo (pack) a partir do stacktrace
* Preferência por nomes que começam com "pack"
*/
guessFileLabelFromStack() {
try {
const err = new Error();
const stack = String(err.stack || '');
const lines = stack.split('\n');
const candidates = [];
for (const line of lines) {
const m = line.match(/[\\\/]([A-Za-z0-9_-]+)\.(?:ts|js)(?::\d+:\d+|\?|$)/);
if (m && m[1]) {
const base = m[1];
if (/^pack/i.test(base)) {
return base;
}
candidates.push(base);
}
}
return candidates[0];
}
catch (_a) {
return undefined;
}
}
/**
* 💾 Exporta dados de performance para JSON
* @returns String JSON com todos os dados
*/
exportData() {
const data = {
timestamp: new Date().toISOString(),
serviceName: this.serviceName,
statistics: this.getStatistics(),
operations: Array.from(this.performanceSummary.entries()).map(([name, stats]) => (Object.assign(Object.assign({ name }, stats), { averageTime: Math.round(stats.totalTime / stats.count) }))),
};
return JSON.stringify(data, null, 2);
}
/**
* 🎯 Calcula thresholds dinâmicos baseados na quantidade de execuções
* @param executionCount Número de execuções da operação
* @returns Objeto com thresholds calculados dinamicamente
*/
calculateDynamicThresholds(executionCount) {
if (executionCount === 1) {
// Primeira execução - expectativas mais relaxadas
return {
ok: 1000,
lento: 2000,
critico: 2000,
};
}
else if (executionCount <= 5) {
// Até 5 execuções - sistema ainda aquecendo
return {
ok: 500,
lento: 1000,
critico: 1000,
};
}
else if (executionCount <= 10) {
// Até 10 execuções - sistema otimizado
return {
ok: 100,
lento: 250,
critico: 250,
};
}
else {
// Acima de 10 execuções - expectativas altas
return {
ok: 20,
lento: 60,
critico: 60,
};
}
}
injectMonitorButton() {
if (document.getElementById('btn-monitorar-performance'))
return;
// ====== ESTILO MODERNO E BOTÃO DE OLHO ======
const BUTTON_COLOR = '#23272f'; // cinza escuro elegante
const BUTTON_TEXT_COLOR = '#f3f4f6';
const BUTTON_RADIUS = '8px';
const BUTTON_FONT_SIZE = '13px';
const BUTTON_PADDING = '10px 5px';
const BUTTON_WIDTH = '210px';
const BUTTON_HEIGHT = '44px';
const BUTTON_BOX_SHADOW = '0 2px 12px #0002';
const BUTTON_BORDER = '1px solid #353a40';
// Container para animação
const container = document.createElement('div');
container.id = 'performance-monitor-container';
Object.assign(container.style, {
position: 'fixed',
bottom: '5px',
left: '50px',
zIndex: '9999',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-end',
transition: 'transform 0.5s cubic-bezier(.4,2,.6,1)',
gap: '10px',
});
// Função para criar botões padronizados
const criarBotao = (id, texto) => {
const btn = document.createElement('button');
btn.id = id;
btn.innerText = texto;
Object.assign(btn.style, {
width: BUTTON_WIDTH,
height: BUTTON_HEIGHT,
background: BUTTON_COLOR,
color: BUTTON_TEXT_COLOR,
borderRadius: BUTTON_RADIUS,
border: BUTTON_BORDER,
padding: BUTTON_PADDING,
fontSize: BUTTON_FONT_SIZE,
fontWeight: '600',
cursor: 'pointer',
boxShadow: BUTTON_BOX_SHADOW,
letterSpacing: '0.01em',
textAlign: 'center',
transition: 'background 0.2s, color 0.2s, transform 0.5s cubic-bezier(.4,2,.6,1)',
});
btn.onmouseenter = () => {
btn.style.background = '#353a40';
btn.style.color = '#fff';
};
btn.onmouseleave = () => {
btn.style.background = BUTTON_COLOR;
btn.style.color = BUTTON_TEXT_COLOR;
};
return btn;
};
// Botões principais
const btnMain = criarBotao('btn-monitorar-performance', 'Parar monitoramento');
const btnFinalizar = criarBotao('btn-finalizar-monitoramento', 'Finalizar monitoramento');
const btnRequisicoesDetalhadas = criarBotao('btn-req-detalhadas-performance', 'Requisições detalhadas');
const btnAnaliseDetalhada = criarBotao('btn-analise-detalhada-performance', 'Análise detalhada');
const btnRelatorioEvento = criarBotao('btn-relatorio-evento-performance', 'Relatório separado');
// Inicialmente escondidos
btnRequisicoesDetalhadas.style.display = 'none';
btnAnaliseDetalhada.style.display = 'none';
btnFinalizar.style.display = 'none';
btnRelatorioEvento.style.display = 'none';
// Botão de esconder/mostrar com ícone de olho (fora do container)
const btnToggle = document.createElement('button');
btnToggle.id = 'btn-toggle-performance';
btnToggle.innerHTML = `
<img
src="https://cdn-icons-png.flaticon.com/512/565/565655.png"
alt="esconder"
style="width:15px; height:15px; vertical-align:middle; filter: invert(1);"
/>
`;
Object.assign(btnToggle.style, {
width: '38px',
height: '38px',
background: '#23272f',
color: '#f3f4f6',
borderRadius: '50%',
border: BUTTON_BORDER,
fontSize: '20px',
fontWeight: 'bold',
cursor: 'pointer',
boxShadow: BUTTON_BOX_SHADOW,
position: 'fixed',
left: '5px',
bottom: '5px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: '10000',
});
btnToggle.onmouseenter = () => {
btnToggle.style.background = '#353a40';
btnToggle.style.color = '#fff';
};
btnToggle.onmouseleave = () => {
btnToggle.style.background = '#23272f';
btnToggle.style.color = '#f3f4f6';
};
let hidden = false;
btnToggle.onclick = () => {
hidden = !hidden;
if (hidden) {
container.style.display = 'none';
btnToggle.innerHTML = `
<img
src="https://cdn-icons-png.flaticon.com/512/159/159604.png"
alt="mostrar"
style="width:15px; height:15px; vertical-align:middle; filter: invert(1);"
/>
`;
}
else {
container.style.display = 'flex';
btnToggle.innerHTML = `
<img
src="https://cdn-icons-png.flaticon.com/512/565/565655.png"
alt="esconder"
style="width:15px; height:15px; vertical-align:middle; filter: invert(1);"
/>
`;
}
};
// Estado dos botões
let estado = 'monitorando';
const atualizarBotoes = (novoEstado) => {
estado = novoEstado;
switch (estado) {
case 'inativo':
btnMain.innerText = 'Iniciar monitoramento';
this.monitoramentoAtivo = false;
btnFinalizar.style.display = 'none';
btnRequisicoesDetalhadas.style.display = 'none';
btnAnaliseDetalhada.style.display = 'none';
btnRelatorioEvento.style.display = 'none';
break;
case 'monitorando':
btnMain.innerText = 'Parar monitoramento';
this.monitoramentoAtivo = true;
btnFinalizar.style.display = 'none';
btnRequisicoesDetalhadas.style.display = 'none';
btnAnaliseDetalhada.style.display = 'none';
btnRelatorioEvento.style.display = 'none';
break;
case 'relatorio':
btnMain.innerText = 'Gerar relatório';
this.monitoramentoAtivo = false;
btnFinalizar.style.display = 'none';
btnRequisicoesDetalhadas.style.display = 'block';
btnAnaliseDetalhada.style.display = 'block';
btnRelatorioEvento.style.display = 'block';
break;
case 'relatorio_gerado':
btnMain.innerText = 'Gerar relatório';
this.monitoramentoAtivo = false;
btnFinalizar.style.display = 'block';
btnRequisicoesDetalhadas.style.display = 'block';
btnAnaliseDetalhada.style.display = 'block';
btnRelatorioEvento.style.display = 'block';
break;
}
};
// Ações dos botões
btnMain.onclick = () => {
if (estado === 'inativo') {
this.resetPerformanceData();
this.showWelcomeMessage();
atualizarBotoes('monitorando');
}
else if (estado === 'monitorando') {
atualizarBotoes('relatorio');
}
else if (estado === 'relatorio' || estado === 'relatorio_gerado') {
this.showPerformanceSummary(false);
atualizarBotoes('relatorio_gerado');
}
};
btnFinalizar.onclick = () => {
this.resetPerformanceData();
atualizarBotoes('inativo');
};
// Apêndice
this.showWelcomeMessage();
container.appendChild(btnMain);
container.appendChild(btnRequisicoesDetalhadas);
container.appendChild(btnRelatorioEvento);
container.appendChild(btnAnaliseDetalhada);
container.appendChild(btnFinalizar);
document.body.appendChild(container);
document.body.appendChild(btnToggle);
// Ação do botão de requisições detalhadas
btnRequisicoesDetalhadas.onclick = () => {
this.showHttpRequestsReport();
};
// Ação do botão de análise detalhada
btnAnaliseDetalhada.onclick = () => {
this.generateDetailedAnalysis();
};
// Ação do relatório por evento
btnRelatorioEvento.onclick = () => {
this.showEventReport(true);
};
// ====== Captura automática de eventos de UI para separar por evento ======
try {
// Evita adicionar múltiplos listeners
const anyWindow = window;
if (!anyWindow.__pm_auto_ui_listeners_added) {
anyWindow.__pm_auto_ui_listeners_added = true;
const describeTarget = (el, ev) => {
try {
const isKeyboard = typeof KeyboardEvent !== 'undefined' &&
ev instanceof KeyboardEvent;
const keyPart = isKeyboard && ev.key
? `[${ev.key}]`
: '';
if (!el || typeof el !== 'object')
return `ui${keyPart}`;
const tag = (el.tagName || el.nodeName || '').toString().toLowerCase() ||
'element';
const id = el.id ? `#${el.id}` : '';
const rawClass = typeof el.className === 'string'
? el.className
: el.classList &&
typeof el.classList === 'object' &&
typeof el.classList.toString === 'function'
? el.classList.toString()
: '';
const filtered = rawClass
.split(' ')
.filter((c) => c && !c.startsWith('ng-') && !c.startsWith('cdk-'))
.slice(0, 2)
.join('.');
const cls = filtered ? `.${filtered}` : '';
const includeText = tag === 'button' || tag === 'a';
const txt = includeText && typeof el.innerText === 'string'
? el.innerText.trim().slice(0, 30)
: '';
return `${tag}${id}${cls}${keyPart}${txt ? `:"${txt}"` : ''}`;
}
catch (_a) {
return 'ui';
}
};
let uiEventEndTimer = null;
let uiEventEndInterval = null;
const startUiEventContext = (label) => {
if (uiEventEndTimer) {
clearTimeout(uiEventEndTimer);
uiEventEndTimer = null;
}
if (uiEventEndInterval) {
clearInterval(uiEventEndInterval);
uiEventEndInterval = null;
}
this.beginEventContext(label);
// Se já há um contexto aberto, não cria nova janela de encerramento (evento aninhado)
if (this.eventContextStack.length > 1) {
return;
}
// Janela inteligente: espera primeiro timer e fecha após período de silêncio
const MAX_WINDOW_MS = 60000;
const GRACE_MS = 2000; // tempo para permitir timers que começam depois
const QUIET_MS = 600; // tempo sem atividade para considerar fluxo encerrado
uiEventEndInterval = setInterval(() => {
var _a, _b;
const now = Date.now();
const active = this.hasActiveTimers();
const beginAt = (_a = this.currentEventBeginAt) !== null && _a !== void 0 ? _a : now;
const hasStarted = this.firstTimerInEventAt !== null;
const graceExceeded = now - beginAt > GRACE_MS;
const lastActivity = (_b = this.lastTimerActivityAt) !== null && _b !== void 0 ? _b : beginAt;
const quietEnough = hasStarted && !active && now - lastActivity > QUIET_MS;
const exceededMax = now - beginAt > MAX_WINDOW_MS;
if (quietEnough || (!hasStarted && graceExceeded) || exceededMax) {
this.endEventContext();
clearInterval(uiEventEndInterval);
uiEventEndInterval = null;
}
}, 250);
// Fallback: fecha no máximo após MAX_WINDOW_MS
uiEventEndTimer = setTimeout(() => {
if (uiEventEndInterval) {
clearInterval(uiEventEndInterval);
uiEventEndInterval = null;
}
this.endEventContext();
uiEventEndTimer = null;
}, MAX_WINDOW_MS);
};
['click', 'submit', 'change', 'keydown'].forEach((type) => {
document.addEventListener(type, (ev) => {
const target = ev.target;
const label = `UserInput:${type} ${describeTarget(target, ev)}`;
startUiEventContext(label);
}, true // captura cedo na fase de captura
);
});
// Contexto inicial para operações de inicialização da tela
this.beginEventContext('AppInit');
setTimeout(() => this.endEventContext(), 3000);
}
}
catch (e) {
// Evita quebrar caso ambiente não permita acesso ao document
console.warn('[PerformanceMonitor] Falha ao ativar captura automática de UI:', e);
}
}
showEventReport(includeHttp = true) {
console.log('%cRELATÓRIO POR EVENTO', 'color: #bbc7c8ff; font-size: 18px; font-weight: bold; text-shadow: 1px 1px #000; background-color: #43248bff; border-radius: 5px; padding: 10px 20px;');
if (this.performanceSummary.size === 0) {
console.log('Nenhuma operação monitorada ainda.');
return;
}
const events = new Map();
this.performanceSummary.forEach((stats, fullName) => {
const parts = fullName.split(' | ');
const event = parts.length > 1 ? parts[0] : 'SemEvento';
const opName = parts.length > 1 ? parts.slice(1).join(' | ') : fullName;
if (!events.has(event)) {
events.set(event, { totalTime: 0, totalExec: 0, operations: [] });
}
const group = events.get(event);
group.totalTime += stats.totalTime;
group.totalExec += stats.count;
group.operations.push({ name: opName, stats });
});
let overallTime = 0;
let overallExec = 0;
Array.from(events.entries())
.sort((a, b) => b[1].totalTime - a[1].totalTime)
.forEach(([event, group]) => {
overallTime += group.totalTime;
overallExec += group.totalExec;
console.log(`%cEVENTO: ${event} | Tempo: ${Math.round(group.totalTime)}ms | Execuções: ${group.totalExec}`, 'color: #fff; background: #28647b; font-size: 13px; font-weight: bold; border-radius: 4px; padding: 3px 8px;');
const table = Object.fromEntries(group.operations
.sort((a, b) => b.stats.totalTime - a.stats.totalTime)
.map(({ name, stats }) => {
const avgTime = Math.round(stats.totalTime / stats.count);
const { emoji, status } = this.getPerformanceStatus(avgTime, stats.customThresholds, name);
return [
name,
{
Execuções: stats.count,
'Média(ms)': `${avgTime}ms (${status}) ${emoji}`.trim(),
'Mínimo(ms)': stats.minTime,
'Máximo(ms)': stats.maxTime,
'Total(ms)': Math.round(stats.totalTime),
},
];
}));
console.table(table);
if (includeHttp) {
const httpTable = {};
this.httpRequests.forEach((dados, key) => {
const parts = key.split(' | ');
const httpEvent = parts.length > 1 ? parts[0] : 'SemEvento';
const httpName = parts.length > 1 ? parts.slice(1).join(' | ') : key;
if (httpEvent === event) {
httpTable[httpName] = {
Execuções: dados.execucoes,
Média: `${Math.round(dados.tempoTotal / Math.max(dados.execucoes, 1))}ms`,
'Tempo Total': `${Math.round(dados.tempoTotal)}ms`,
Erros: dados.errors,
};
}
});
const hasHttpRows = Object.keys(httpTable).length > 0;
if (hasHttpRows) {
console.log(`%cHTTP (${event})`, 'color: #fff; background: #28336b; font-size: 12px; font-weight: bold; border-radius: 4px; padding: 2px 8px;');
console.table(httpTable);
}
}
});
console.log(`%cTOTAL GERAL: ${Math.round(overallTime)}ms | Execuções: ${overallExec}`, 'color: #fff; background: #43248bff; font-size: 14px; font-weight: bold; border-radius: 4px; padding: 4px 12px; margin-top: 8px;');
}
}
/**
* 🏭 Factory function para criar instância do LogsPerformatico
* @param serviceName Nome do serviço (para logs personalizados)
* @returns Nova instância de LogsPerformatico
*/
function createPerformanceLogger(serviceName = 'Service') {
return new LogsPerformatico(serviceName);
}
// shared/performanceLoggerGlobal.ts
const GlobalPerformanceLogger = createPerformanceLogger('GLOBAL');
// Implementação unificada
function PerformanceMonitor(options) {
return function (target, propertyKey, descriptor) {
// --- CASO 1: Class Decorator ---
// Se propertyKey e descriptor forem undefined, estamos decorando uma classe
if (!propertyKey && !descriptor) {
const constructor = target;
const opts = options;
const excludeList = (typeof opts === 'object' && (opts === null || opts === void 0 ? void 0 : opts.exclude)) || [];
const prefix = (typeof opts === 'object' && (opts === null || opts === void 0 ? void 0 : opts.prefix)) || '';
// Itera sobre todas as propriedades do prototype da classe
for (const prop of Object.getOwnPropertyNames(constructor.prototype)) {
// Ignora construtor e itens excluídos
if (prop === 'constructor' || excludeList.includes(prop)) {
continue;
}
const propDesc = Object.getOwnPropertyDescriptor(constructor.prototype, prop);
// Verifica se é método
if (propDesc && typeof propDesc.value === 'function') {
const originalMethod = propDesc.value;
const opName = prefix ? `${prefix}.${prop}` : prop;
// Substitui o método original pelo wrapper
propDesc.value = function (...args) {
const instanceLogger = this
.performanceLogger;
const logger = instanceLogger || GlobalPerformanceLogger;
if (!logger) {
return originalMethod.apply(this, args);
}
logger.startTimer(opName);
let result;
try {
result = originalMethod.apply(this, args);
}
catch (err) {
// Em caso de erro síncrono, finaliza timer e re-lança
logger.endTimer(opName);
throw err;
}
// Verifica se é Promise (assíncrono)
if (result && typeof result.then === 'function') {
return result
.then((res) => {
logger.endTimer(opName);
return res;
})
.catch((err) => {
logger.endTimer(opName);
throw err;
});
}
else {
// Síncrono
logger.endTimer(opName);
return result;
}
};
// Redefine a propriedade no prototype
Object.defineProperty(constructor.prototype, prop, propDesc);
}
}
return; // Retorna void para Class Decorator
}
// --- CASO 2: Method Decorator ---
if (!descriptor)
return; // Segurança
const originalMethod = descriptor.value;
let opName;
let customMethod;
let customThresholds;
let eventLabel;
// Parsing simples para method options
if (typeof options === 'object' &&
options !== null &&
!('exclude' in options) &&
!('prefix' in options)) {
const opts = options;
opName = opts.name || propertyKey;
customMethod = opts.method;
customThresholds = {
ok: opts.ok,
lento: opts.lento,
critico: opts.critico,
};
eventLabel = opts.event;
}
else if (typeof options === 'function') {
opName = options.name || propertyKey;
customMethod = options;
}
else if (typeof options === 'string') {
opName = options;
}
else {
opName = propertyKey;
}
descriptor.value = function (...args) {
const instanceLogger = this
.performanceLogger;
const logger = instanceLogger || GlobalPerformanceLogger;
if (logger) {
if (eventLabel) {
logger.beginEventContext(eventLabel);
}
logger.startTimer(opName);
let result;
try {
const methodToCall = customMethod || originalMethod;
result = methodToCall.apply(this, args);
}
catch (err) {
logger.endTimer(opName, undefined, customThresholds);
if (eventLabel)
logger.endEventContext();
throw err;
}
if (result && typeof result.then === 'function') {
return result
.then((res) => {
logger.endTimer(opName, undefined, customThresholds);
if (eventLabel)
logger.endEventContext();
return res;
})
.catch((err) => {
logger.endTimer(opName, undefined, customThresholds);
if (eventLabel)
logger.endEventContext();
throw err;
});
}
else {
logger.endTimer(opName, undefined, customThresholds);
if (eventLabel)
logger.endEventContext();
return result;
}
}
else {
const methodToCall = customMethod || originalMethod;
return methodToCall.apply(this, args);
}
};
return descriptor;
};
}
class PerformanceMonitorInterceptor {
intercept(req, next) {
var _a;
// Ignora arquivos de assets
if (!req.url.startsWith('./assets')) {
// 🔹 Recupera dados da sessão
let tela = sessionStorage.getItem('idTela');
if (!tela) {
tela = 'SEL041';
sessionStorage.setItem('idTela', tela);
}
const cdUsuario = (_a = sessionStorage.getItem('cdUsuario')) !== null && _a !== void 0 ? _a : '';
// 🔹 Clona a requisição adicionando headers
const authReq = req.clone({
headers: req.headers
.set('tela_id', tela)
.set('tela', 'SEL041')
.set('user', cdUsuario),
});
const start = performance.now();
return next.handle(authReq).pipe(tap({
next: (event) => {
var _a, _b;
if (event instanceof HttpResponse) {
const tempo = performance.now() - start;
// ✅ Verificação segura do body e suas propriedades
const operationType = ((_a = authReq.body) === null || _a === void 0 ? void 0 : _a.operationType) || 'UNKNOWN';
const operationName = ((_b = authReq.body) === null || _b === void 0 ? void 0 : _b.name) || '';
console.log(`%c🟢 [HTTP] ${operationType} ${operationType === 'SELECT_FULL' ? '' : operationName} - ${tempo.toFixed(1)}ms`, 'color: #7048cfff; font-weight: bold;');
GlobalPerformanceLogger.registerHttpRequest(authReq, tempo, false);
}
},
error: (error) => {
var _a, _b;
const tempo = performance.now() - start;
// ✅ Verificação segura do body e suas propriedades
const operationType = ((_a = authReq.body) === null || _a === void 0 ? void 0 : _a.operationType) || 'UNKNOWN';
const operationName = ((_b = authReq.body) === null || _b === void 0 ? void 0 : _b.name) || '';
console.groupCollapsed(`%c🔴 [HTTP] ${operationType} ${operationName} - ${tempo.toFixed(1)}ms`, 'color: #7048cfff; font-weight: bold;');
console.error(error);
console.groupEnd();
GlobalPerformanceLogger.registerHttpRequest(authReq, tempo, true);
},
}));
}
// Requisição de asset, passa direto
return next.handle(req);
}
}
PerformanceMonitorInterceptor.decorators = [
{ type: Injectable }
];
/*
* Public API Surface of performance-monitor
*/
/**
* Generated bundle index. Do not edit.
*/
export { GlobalPerformanceLogger, PerformanceMonitor, PerformanceMonitorComponent, PerformanceMonitorInterceptor, PerformanceMonitorModule, PerformanceMonitorService, LogsPerformatico as ɵa, createPerformanceLogger as ɵb };
//# sourceMappingURL=performance-monitor-logger.js.map