[251126] fix: Memory Monitoring - mediaMemory

🕐 커밋 시간: 2025. 11. 26. 09:19:24

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +464줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/utils/memoryMonitor.js

🔧 주요 변경 내용:
  • 공통 유틸리티 함수 최적화
  • 대규모 기능 개발
This commit is contained in:
2025-11-26 09:19:24 +09:00
parent a9fd3981c8
commit a539e2617e

View File

@@ -33,6 +33,253 @@ export const createMemoryMonitor = (enableInitLog = true) => {
return null; return null;
}; };
// 미디어 리소스 메모리 정보 수집
const getMediaMemoryInfo = () => {
try {
const mediaElements = document.querySelectorAll('video, audio');
let totalVideoBuffer = 0;
let totalAudioBuffer = 0;
let videoCount = 0;
let audioCount = 0;
const mediaInfo = [];
// NodeList를 배열로 변환하여 forEach 사용
Array.from(mediaElements).forEach((media, index) => {
try {
const buffered = media.buffered;
let totalDuration = 0;
if (buffered && buffered.length) {
for (let i = 0; i < buffered.length; i++) {
try {
totalDuration += buffered.end(i) - buffered.start(i);
} catch (e) {
// buffered 접근 중 오류 발생 시 무시
}
}
}
if (media.tagName === 'VIDEO') {
videoCount++;
} else if (media.tagName === 'AUDIO') {
audioCount++;
}
// 비디오 메타데이터 수집
let videoBitrate = 0;
let codecInfo = 'unknown';
if (media.tagName === 'VIDEO' && media.videoWidth && media.videoHeight) {
// 해상도 기반 비트레이트 추정 (HLS 스트리밍 기준)
const resolution = media.videoWidth * media.videoHeight;
if (resolution >= 3840 * 2160) { // 4K
videoBitrate = 15000000; // 15Mbps
codecInfo = '4K/HLS';
} else if (resolution >= 1920 * 1080) { // FHD
videoBitrate = 8000000; // 8Mbps
codecInfo = 'FHD/HLS';
} else if (resolution >= 1280 * 720) { // HD
videoBitrate = 4000000; // 4Mbps
codecInfo = 'HD/HLS';
} else { // SD
videoBitrate = 2000000; // 2Mbps
codecInfo = 'SD/HLS';
}
}
// HLS 스트리밍 정보 확인
let hlsInfo = null;
if (media.src && media.src.includes('.m3u8')) {
hlsInfo = {
isHLS: true,
playlistUrl: media.src.substring(0, 100) + '...',
estimatedSegments: Math.ceil((media.duration || 0) / 10), // 10초 세그먼트 기준
};
} else if (media.src) {
hlsInfo = {
isHLS: false,
contentType: 'progressive',
format: media.src.includes('.mp4') ? 'MP4' : 'Unknown',
};
}
const mediaData = {
index,
type: media.tagName ? media.tagName.toLowerCase() : 'unknown',
src: media.src ? (media.src.length > 50 ? media.src.substring(0, 50) + '...' : media.src) : 'N/A',
duration: media.duration || 0,
bufferedDuration: totalDuration,
currentTime: media.currentTime || 0,
readyState: media.readyState || 0,
networkState: media.networkState || 0,
videoWidth: media.videoWidth || 0,
videoHeight: media.videoHeight || 0,
// 비디오 전용 정보
bitrate: videoBitrate,
codecInfo: codecInfo,
// HLS/스트리밍 정보
hlsInfo: hlsInfo,
// 버퍼 효율성
bufferEfficiency: media.duration > 0 ? (totalDuration / media.duration * 100).toFixed(1) + '%' : '0%',
// 재생 상태
paused: media.paused,
ended: media.ended,
muted: media.muted,
volume: media.volume || 0,
};
mediaInfo.push(mediaData);
// 실제 버퍼 메모리 계산
if (media.tagName === 'VIDEO' && media.videoWidth && media.videoHeight) {
// 비디오: 실제 비트레이트 기반 계산
totalVideoBuffer += totalDuration * (videoBitrate / 8); // bytes
} else if (media.tagName === 'AUDIO') {
// 오디오: 고품질 320kbps로 추정
totalAudioBuffer += totalDuration * 320000 / 8; // bytes
}
} catch (e) {
// 개별 미디어 요소 처리 중 오류 발생 시 무시
console.warn('[Memory Monitor] Error processing media element:', e);
}
});
return {
mediaCount: mediaElements.length,
videoElements: videoCount,
audioElements: audioCount,
totalVideoBufferMB: (totalVideoBuffer / 1048576).toFixed(2),
totalAudioBufferMB: (totalAudioBuffer / 1048576).toFixed(2),
estimatedMediaMemoryMB: ((totalVideoBuffer + totalAudioBuffer) / 1048576).toFixed(2),
mediaElements: mediaInfo
};
} catch (e) {
console.warn('[Memory Monitor] Error getting media memory info:', e);
return {
mediaCount: 0,
videoElements: 0,
audioElements: 0,
totalVideoBufferMB: '0.00',
totalAudioBufferMB: '0.00',
estimatedMediaMemoryMB: '0.00',
mediaElements: []
};
}
};
// 이미지 리소스 메모리 정보 수집
const getImageMemoryInfo = () => {
try {
const images = document.querySelectorAll('img');
let totalImageMemory = 0;
const imageInfo = [];
// NodeList를 배열로 변환하여 forEach 사용
Array.from(images).forEach((img, index) => {
try {
if (img.naturalWidth && img.naturalHeight) {
// 이미지 메모리 크기 추정 (너비 * 높이 * 4바이트 RGBA)
const estimatedMemory = img.naturalWidth * img.naturalHeight * 4;
totalImageMemory += estimatedMemory;
imageInfo.push({
index,
src: img.src ? (img.src.length > 50 ? img.src.substring(0, 50) + '...' : img.src) : 'N/A',
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight,
displayWidth: img.offsetWidth || 0,
displayHeight: img.offsetHeight || 0,
estimatedMemoryMB: (estimatedMemory / 1048576).toFixed(2),
complete: img.complete || false,
loading: img.loading || 'auto'
});
}
} catch (e) {
// 개별 이미지 요소 처리 중 오류 발생 시 무시
console.warn('[Memory Monitor] Error processing image element:', e);
}
});
return {
imageCount: images.length,
totalImageMemoryMB: (totalImageMemory / 1048576).toFixed(2),
images: imageInfo
};
} catch (e) {
console.warn('[Memory Monitor] Error getting image memory info:', e);
return {
imageCount: 0,
totalImageMemoryMB: '0.00',
images: []
};
}
};
// Canvas/WebGL 리소스 메모리 정보 수집
const getCanvasMemoryInfo = () => {
try {
const canvases = document.querySelectorAll('canvas');
let totalCanvasMemory = 0;
const canvasInfo = [];
// NodeList를 배열로 변환하여 forEach 사용
Array.from(canvases).forEach((canvas, index) => {
try {
const context = canvas.getContext('2d') || canvas.getContext('webgl') || canvas.getContext('webgl2');
if (context) {
const memory = canvas.width * canvas.height * 4; // 4바이트 per 픽셀
totalCanvasMemory += memory;
canvasInfo.push({
index,
width: canvas.width || 0,
height: canvas.height || 0,
contextType: context.constructor.name || 'unknown',
estimatedMemoryMB: (memory / 1048576).toFixed(2)
});
}
} catch (e) {
// 개별 캔버스 요소 처리 중 오류 발생 시 무시
console.warn('[Memory Monitor] Error processing canvas element:', e);
}
});
return {
canvasCount: canvases.length,
totalCanvasMemoryMB: (totalCanvasMemory / 1048576).toFixed(2),
canvases: canvasInfo
};
} catch (e) {
console.warn('[Memory Monitor] Error getting canvas memory info:', e);
return {
canvasCount: 0,
totalCanvasMemoryMB: '0.00',
canvases: []
};
}
};
// 통합 미디어 메모리 정보
const getCompleteMediaMemoryInfo = () => {
const mediaMemory = getMediaMemoryInfo();
const imageMemory = getImageMemoryInfo();
const canvasMemory = getCanvasMemoryInfo();
const totalEstimatedMB = (
parseFloat(mediaMemory.estimatedMediaMemoryMB) +
parseFloat(imageMemory.totalImageMemoryMB) +
parseFloat(canvasMemory.totalCanvasMemoryMB)
).toFixed(2);
return {
totalEstimatedMediaMemoryMB: totalEstimatedMB,
media: mediaMemory,
images: imageMemory,
canvas: canvasMemory,
timestamp: new Date().toISOString()
};
};
const getDetailedMemoryInfo = () => { const getDetailedMemoryInfo = () => {
const info = getMemoryInfo(); const info = getMemoryInfo();
if (!info) return null; if (!info) return null;
@@ -46,6 +293,8 @@ export const createMemoryMonitor = (enableInitLog = true) => {
domNodeCount: document.querySelectorAll('*').length, domNodeCount: document.querySelectorAll('*').length,
// 리스너 수 (대략값) // 리스너 수 (대략값)
eventListenerEstimate: Object.keys(window).filter(key => key.startsWith('on')).length, eventListenerEstimate: Object.keys(window).filter(key => key.startsWith('on')).length,
// 미디어 리소스 정보 추가
mediaMemory: getCompleteMediaMemoryInfo(),
}; };
return detailed; return detailed;
@@ -155,7 +404,121 @@ export const createMemoryMonitor = (enableInitLog = true) => {
domNodeCount: detailed.domNodeCount, domNodeCount: detailed.domNodeCount,
eventListenerEstimate: detailed.eventListenerEstimate, eventListenerEstimate: detailed.eventListenerEstimate,
}); });
console.log(`${logMsg} | ${context} | Details: ${detailStr} ${info}`); const mediaMemory = detailed.mediaMemory;
const mediaStr = JSON.stringify({
totalMediaMemory: mediaMemory.totalEstimatedMediaMemoryMB + 'MB',
videoElements: mediaMemory.media.videoElements,
audioElements: mediaMemory.media.audioElements,
imageCount: mediaMemory.images.imageCount,
imageMemory: mediaMemory.images.totalImageMemoryMB + 'MB',
canvasCount: mediaMemory.canvas.canvasCount,
canvasMemory: mediaMemory.canvas.totalCanvasMemoryMB + 'MB'
});
const jsTotal = parseFloat(detailed.usedJSHeapSize);
const mediaTotal = parseFloat(mediaMemory.totalEstimatedMediaMemoryMB);
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
console.log(`${logMsg} | ${context} | Details: ${detailStr} | Media: ${mediaStr} | Est.Total: ${estimatedTotal}MB ${info}`);
}
},
/**
* 전체 미디어 리소스 메모리 로깅
* @param {string} context - 컨텍스트
* @param {object} additionalInfo - 추가 정보
*/
logMediaMemory: (context = '', additionalInfo = {}) => {
const jsMem = getMemoryInfo();
const mediaMem = getCompleteMediaMemoryInfo();
if (jsMem && mediaMem) {
const jsTotal = parseFloat(jsMem.usedJSHeapSize);
const mediaTotal = parseFloat(mediaMem.totalEstimatedMediaMemoryMB);
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
console.log(`${logMsg} | Media: ${context}`);
console.log(`[Media Breakdown] Images: ${mediaMem.images.totalImageMemoryMB}MB (${mediaMem.images.imageCount}개), Video: ${mediaMem.media.estimatedMediaMemoryMB}MB (${mediaMem.media.mediaCount}개), Canvas: ${mediaMem.canvas.totalCanvasMemoryMB}MB (${mediaMem.canvas.canvasCount}개)`);
console.log(`[Total Estimated] JS(${jsTotal}MB) + Media(${mediaTotal}MB) = ${estimatedTotal}MB ${info}`);
} else {
const timestamp = new Date().toISOString();
console.log(`[Media Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
}
},
/**
* 비디오 전용 상세 메모리 로깅
* @param {string} context - 컨텍스트
* @param {object} additionalInfo - 추가 정보
*/
logVideoMemory: (context = '', additionalInfo = {}) => {
const jsMem = getMemoryInfo();
const mediaMem = getMediaMemoryInfo();
if (jsMem && mediaMem) {
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
console.log(`${logMsg} | Video Memory: ${context}`);
console.log(`[Video Summary] ${mediaMem.videoElements}개 비디오, ${mediaMem.totalVideoBufferMB}MB 버퍼 메모리 사용`);
// 개별 비디오 정보 상세 출력
mediaMem.mediaElements.forEach((video, idx) => {
if (video.type === 'video') {
console.log(`[Video ${video.index}] ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Buffered: ${video.bufferedDuration.toFixed(1)}s/${video.duration.toFixed(1)}s (${video.bufferEfficiency}) | ${video.hlsInfo?.isHLS ? 'HLS' : 'Progressive'} | ${video.paused ? 'Paused' : 'Playing'} | Src: ${video.src}`);
}
});
console.log(`[Video Estimation] JS Heap: ${jsMem.usedJSHeapSize}MB + Video Buffer: ${mediaMem.totalVideoBufferMB}MB = ${(parseFloat(jsMem.usedJSHeapSize) + parseFloat(mediaMem.totalVideoBufferMB)).toFixed(2)}MB ${info}`);
} else {
const timestamp = new Date().toISOString();
console.log(`[Video Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
}
},
/**
* HLS 스트리밍 메모리 전용 로깅
* @param {string} context - 컨텍스트
* @param {object} additionalInfo - 추가 정보
*/
logHLSMemory: (context = '', additionalInfo = {}) => {
const jsMem = getMemoryInfo();
const mediaMem = getMediaMemoryInfo();
if (jsMem && mediaMem) {
const hlsVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && video.hlsInfo.isHLS);
const progressiveVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && !video.hlsInfo.isHLS);
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
console.log(`${logMsg} | HLS Streaming: ${context}`);
console.log(`[Streaming Analysis] HLS: ${hlsVideos.length}개, Progressive: ${progressiveVideos.length}개 | Total Video Memory: ${mediaMem.totalVideoBufferMB}MB`);
// HLS 비디오 상세 정보
if (hlsVideos.length > 0) {
console.log(`[HLS Videos]`);
hlsVideos.forEach(video => {
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Segments: ~${video.hlsInfo.estimatedSegments}개 | Buffer: ${video.bufferedDuration.toFixed(1)}s | Efficiency: ${video.bufferEfficiency}`);
});
}
// Progressive 비디오 상세 정보
if (progressiveVideos.length > 0) {
console.log(`[Progressive Videos]`);
progressiveVideos.forEach(video => {
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Format: ${video.hlsInfo.format} | Buffer: ${video.bufferedDuration.toFixed(1)}s`);
});
}
const streamingMemoryMB = hlsVideos.reduce((sum, video) => {
return sum + parseFloat(video.bufferedDuration) * (video.bitrate / 8 / 1048576);
}, 0).toFixed(2);
console.log(`[Streaming Memory] HLS Buffer: ${streamingMemoryMB}MB | Progressive Buffer: ${(parseFloat(mediaMem.totalVideoBufferMB) - parseFloat(streamingMemoryMB)).toFixed(2)}MB ${info}`);
} }
}, },
@@ -165,6 +528,7 @@ export const createMemoryMonitor = (enableInitLog = true) => {
*/ */
getMemory: () => getMemoryInfo(), getMemory: () => getMemoryInfo(),
getDetailedMemory: () => getDetailedMemoryInfo(), getDetailedMemory: () => getDetailedMemoryInfo(),
getMediaMemory: () => getCompleteMediaMemoryInfo(),
}; };
// 싱글톤 인스턴스 저장 // 싱글톤 인스턴스 저장
@@ -230,11 +594,109 @@ export const createMemoryMonitor = (enableInitLog = true) => {
domNodeCount: detailed.domNodeCount, domNodeCount: detailed.domNodeCount,
eventListenerEstimate: detailed.eventListenerEstimate, eventListenerEstimate: detailed.eventListenerEstimate,
}); });
console.log(`${logMsg} | ${context} | Details: ${detailStr} ${info}`); const mediaMemory = detailed.mediaMemory;
const mediaStr = JSON.stringify({
totalMediaMemory: mediaMemory.totalEstimatedMediaMemoryMB + 'MB',
videoElements: mediaMemory.media.videoElements,
audioElements: mediaMemory.media.audioElements,
imageCount: mediaMemory.images.imageCount,
imageMemory: mediaMemory.images.totalImageMemoryMB + 'MB',
canvasCount: mediaMemory.canvas.canvasCount,
canvasMemory: mediaMemory.canvas.totalCanvasMemoryMB + 'MB'
});
const jsTotal = parseFloat(detailed.usedJSHeapSize);
const mediaTotal = parseFloat(mediaMemory.totalEstimatedMediaMemoryMB);
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
console.log(`${logMsg} | ${context} | Details: ${detailStr} | Media: ${mediaStr} | Est.Total: ${estimatedTotal}MB ${info}`);
}
},
logMediaMemory: (context = '', additionalInfo = {}) => {
const jsMem = getMemoryInfo();
const mediaMem = getCompleteMediaMemoryInfo();
if (jsMem && mediaMem) {
const jsTotal = parseFloat(jsMem.usedJSHeapSize);
const mediaTotal = parseFloat(mediaMem.totalEstimatedMediaMemoryMB);
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
console.log(`${logMsg} | Media: ${context}`);
console.log(`[Media Breakdown] Images: ${mediaMem.images.totalImageMemoryMB}MB (${mediaMem.images.imageCount}개), Video: ${mediaMem.media.estimatedMediaMemoryMB}MB (${mediaMem.media.mediaCount}개), Canvas: ${mediaMem.canvas.totalCanvasMemoryMB}MB (${mediaMem.canvas.canvasCount}개)`);
console.log(`[Total Estimated] JS(${jsTotal}MB) + Media(${mediaTotal}MB) = ${estimatedTotal}MB ${info}`);
} else {
const timestamp = new Date().toISOString();
console.log(`[Media Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
} }
}, },
getMemory: () => getMemoryInfo(), getMemory: () => getMemoryInfo(),
getDetailedMemory: () => getDetailedMemoryInfo(), getDetailedMemory: () => getDetailedMemoryInfo(),
getMediaMemory: () => getCompleteMediaMemoryInfo(),
logVideoMemory: (context = '', additionalInfo = {}) => {
const jsMem = getMemoryInfo();
const mediaMem = getMediaMemoryInfo();
if (jsMem && mediaMem) {
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
console.log(`${logMsg} | Video Memory: ${context}`);
console.log(`[Video Summary] ${mediaMem.videoElements}개 비디오, ${mediaMem.totalVideoBufferMB}MB 버퍼 메모리 사용`);
// 개별 비디오 정보 상세 출력
mediaMem.mediaElements.forEach((video, idx) => {
if (video.type === 'video') {
console.log(`[Video ${video.index}] ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Buffered: ${video.bufferedDuration.toFixed(1)}s/${video.duration.toFixed(1)}s (${video.bufferEfficiency}) | ${video.hlsInfo?.isHLS ? 'HLS' : 'Progressive'} | ${video.paused ? 'Paused' : 'Playing'} | Src: ${video.src}`);
}
});
console.log(`[Video Estimation] JS Heap: ${jsMem.usedJSHeapSize}MB + Video Buffer: ${mediaMem.totalVideoBufferMB}MB = ${(parseFloat(jsMem.usedJSHeapSize) + parseFloat(mediaMem.totalVideoBufferMB)).toFixed(2)}MB ${info}`);
} else {
const timestamp = new Date().toISOString();
console.log(`[Video Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
}
},
logHLSMemory: (context = '', additionalInfo = {}) => {
const jsMem = getMemoryInfo();
const mediaMem = getMediaMemoryInfo();
if (jsMem && mediaMem) {
const hlsVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && video.hlsInfo.isHLS);
const progressiveVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && !video.hlsInfo.isHLS);
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
console.log(`${logMsg} | HLS Streaming: ${context}`);
console.log(`[Streaming Analysis] HLS: ${hlsVideos.length}개, Progressive: ${progressiveVideos.length}개 | Total Video Memory: ${mediaMem.totalVideoBufferMB}MB`);
// HLS 비디오 상세 정보
if (hlsVideos.length > 0) {
console.log(`[HLS Videos]`);
hlsVideos.forEach(video => {
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Segments: ~${video.hlsInfo.estimatedSegments}개 | Buffer: ${video.bufferedDuration.toFixed(1)}s | Efficiency: ${video.bufferEfficiency}`);
});
}
// Progressive 비디오 상세 정보
if (progressiveVideos.length > 0) {
console.log(`[Progressive Videos]`);
progressiveVideos.forEach(video => {
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Format: ${video.hlsInfo.format} | Buffer: ${video.bufferedDuration.toFixed(1)}s`);
});
}
const streamingMemoryMB = hlsVideos.reduce((sum, video) => {
return sum + parseFloat(video.bufferedDuration) * (video.bitrate / 8 / 1048576);
}, 0).toFixed(2);
console.log(`[Streaming Memory] HLS Buffer: ${streamingMemoryMB}MB | Progressive Buffer: ${(parseFloat(mediaMem.totalVideoBufferMB) - parseFloat(streamingMemoryMB)).toFixed(2)}MB ${info}`);
}
},
}; };
return memoryMonitorInstance; return memoryMonitorInstance;