Files
sam-scenarios/_global-performance-config.json

253 lines
12 KiB
JSON
Raw Normal View History

{
"$schema": "E2E Performance Metrics Configuration",
"version": "1.0.0",
"description": "성능 메트릭 수집 및 분석을 위한 전역 설정",
"lastUpdated": "2026-01-31",
"metrics": {
"pageLoad": {
"enabled": true,
"description": "페이지 로드 성능 측정",
"thresholds": {
"good": 1000,
"acceptable": 2000,
"slow": 3000,
"critical": 5000
},
"measurements": [
"domContentLoaded",
"load",
"firstContentfulPaint",
"largestContentfulPaint",
"timeToInteractive"
]
},
"apiResponse": {
"enabled": true,
"description": "API 응답 시간 측정",
"thresholds": {
"fast": 200,
"good": 500,
"acceptable": 1000,
"slow": 2000,
"critical": 5000
}
},
"resourceUsage": {
"enabled": true,
"description": "리소스 사용량 모니터링",
"track": [
"jsHeapSize",
"domNodes",
"networkRequests",
"transferSize"
]
},
"userInteraction": {
"enabled": true,
"description": "사용자 인터랙션 응답 시간",
"thresholds": {
"instant": 100,
"fast": 300,
"acceptable": 1000,
"slow": 3000
}
}
},
"scripts": {
"initPerformanceMonitoring": "(function() { window.__PERF_METRICS__ = { startTime: Date.now(), pageLoads: [], apiCalls: [], interactions: [], resources: [], errors: [] }; window.__PERF_OBSERVER__ = new PerformanceObserver((list) => { list.getEntries().forEach(entry => { if (entry.entryType === 'navigation') { window.__PERF_METRICS__.pageLoads.push({ url: entry.name, domContentLoaded: entry.domContentLoadedEventEnd - entry.startTime, load: entry.loadEventEnd - entry.startTime, dns: entry.domainLookupEnd - entry.domainLookupStart, tcp: entry.connectEnd - entry.connectStart, ttfb: entry.responseStart - entry.requestStart, timestamp: new Date().toISOString() }); } else if (entry.entryType === 'paint') { window.__PERF_METRICS__[entry.name] = entry.startTime; } else if (entry.entryType === 'largest-contentful-paint') { window.__PERF_METRICS__.lcp = entry.startTime; } }); }); try { window.__PERF_OBSERVER__.observe({ entryTypes: ['navigation', 'paint', 'largest-contentful-paint'] }); } catch(e) { console.warn('Performance observer not fully supported'); } return 'Performance monitoring initialized'; })()",
"getNavigationTiming": "(function() { const perf = performance.getEntriesByType('navigation')[0]; if (!perf) return null; return { url: window.location.href, domContentLoaded: Math.round(perf.domContentLoadedEventEnd - perf.startTime), load: Math.round(perf.loadEventEnd - perf.startTime), domInteractive: Math.round(perf.domInteractive - perf.startTime), dns: Math.round(perf.domainLookupEnd - perf.domainLookupStart), tcp: Math.round(perf.connectEnd - perf.connectStart), ttfb: Math.round(perf.responseStart - perf.requestStart), responseTime: Math.round(perf.responseEnd - perf.responseStart), domProcessing: Math.round(perf.domComplete - perf.domInteractive), transferSize: perf.transferSize, encodedBodySize: perf.encodedBodySize }; })()",
"getPaintTiming": "(function() { const paints = performance.getEntriesByType('paint'); const result = {}; paints.forEach(p => { result[p.name] = Math.round(p.startTime); }); return result; })()",
"getLCP": "(function() { return new Promise((resolve) => { let lcp = 0; const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); entries.forEach(entry => { lcp = entry.startTime; }); }); try { observer.observe({ type: 'largest-contentful-paint', buffered: true }); setTimeout(() => { observer.disconnect(); resolve(Math.round(lcp)); }, 1000); } catch(e) { resolve(null); } }); })()",
"getResourceMetrics": "(function() { const resources = performance.getEntriesByType('resource'); const summary = { total: resources.length, totalSize: 0, byType: {}, slowest: [] }; resources.forEach(r => { const type = r.initiatorType || 'other'; summary.byType[type] = summary.byType[type] || { count: 0, size: 0, totalTime: 0 }; summary.byType[type].count++; summary.byType[type].size += r.transferSize || 0; summary.byType[type].totalTime += r.duration || 0; summary.totalSize += r.transferSize || 0; }); summary.slowest = resources.sort((a, b) => b.duration - a.duration).slice(0, 5).map(r => ({ name: r.name.split('/').pop().substring(0, 30), duration: Math.round(r.duration), size: r.transferSize })); summary.totalSizeKB = Math.round(summary.totalSize / 1024); return summary; })()",
"getMemoryUsage": "(function() { if (!performance.memory) return { supported: false }; return { supported: true, usedJSHeapSize: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024), totalJSHeapSize: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024), jsHeapSizeLimit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024), usagePercent: Math.round((performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit) * 100) }; })()",
"getDOMMetrics": "(function() { return { nodeCount: document.getElementsByTagName('*').length, bodySize: document.body.innerHTML.length, maxDepth: (function getDepth(el) { if (!el.children.length) return 0; return 1 + Math.max(...Array.from(el.children).map(getDepth)); })(document.body), forms: document.forms.length, inputs: document.querySelectorAll('input, textarea, select').length, images: document.images.length, scripts: document.scripts.length, stylesheets: document.styleSheets.length }; })()",
"measureInteraction": "(async function(action, selector) { const start = performance.now(); try { const el = document.querySelector(selector); if (el) { el.click(); await new Promise(r => setTimeout(r, 100)); } const duration = performance.now() - start; return { action, selector, duration: Math.round(duration), timestamp: new Date().toISOString() }; } catch(e) { return { action, selector, error: e.message }; } })",
"getFullPerformanceReport": "(function() { const nav = performance.getEntriesByType('navigation')[0]; const paints = {}; performance.getEntriesByType('paint').forEach(p => paints[p.name] = Math.round(p.startTime)); const resources = performance.getEntriesByType('resource'); const apiLogs = window.__API_LOGS__ || []; return { timestamp: new Date().toISOString(), url: window.location.href, navigation: nav ? { domContentLoaded: Math.round(nav.domContentLoadedEventEnd - nav.startTime), load: Math.round(nav.loadEventEnd - nav.startTime), ttfb: Math.round(nav.responseStart - nav.requestStart), domInteractive: Math.round(nav.domInteractive - nav.startTime) } : null, paint: paints, resources: { count: resources.length, totalSizeKB: Math.round(resources.reduce((sum, r) => sum + (r.transferSize || 0), 0) / 1024) }, api: { totalCalls: apiLogs.length, avgResponseTime: apiLogs.length > 0 ? Math.round(apiLogs.reduce((sum, l) => sum + (l.duration || 0), 0) / apiLogs.length) : 0, slowCalls: apiLogs.filter(l => l.duration > 2000).length }, memory: performance.memory ? { usedMB: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024), usagePercent: Math.round((performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit) * 100) } : null, dom: { nodeCount: document.getElementsByTagName('*').length } }; })()",
"getPerformanceSummary": "(function() { const nav = performance.getEntriesByType('navigation')[0]; const apiLogs = window.__API_LOGS__ || []; const thresholds = { pageLoad: { good: 1000, acceptable: 2000, slow: 3000 }, api: { good: 500, acceptable: 1000, slow: 2000 } }; const pageLoadTime = nav ? Math.round(nav.loadEventEnd - nav.startTime) : 0; const avgApiTime = apiLogs.length > 0 ? Math.round(apiLogs.reduce((sum, l) => sum + (l.duration || 0), 0) / apiLogs.length) : 0; const getGrade = (value, th) => { if (value <= th.good) return '🟢 Good'; if (value <= th.acceptable) return '🟡 Acceptable'; if (value <= th.slow) return '🟠 Slow'; return '🔴 Critical'; }; return { pageLoad: { time: pageLoadTime, grade: getGrade(pageLoadTime, thresholds.pageLoad) }, api: { avgTime: avgApiTime, grade: getGrade(avgApiTime, thresholds.api), totalCalls: apiLogs.length, slowCalls: apiLogs.filter(l => l.duration > 2000).length }, overall: pageLoadTime <= 2000 && avgApiTime <= 1000 ? '✅ PASS' : '⚠️ NEEDS ATTENTION' }; })()"
},
"thresholds": {
"pageLoad": {
"domContentLoaded": {
"good": 800,
"acceptable": 1500,
"slow": 2500,
"critical": 4000
},
"load": {
"good": 1000,
"acceptable": 2000,
"slow": 3000,
"critical": 5000
},
"ttfb": {
"good": 200,
"acceptable": 500,
"slow": 1000,
"critical": 2000
},
"fcp": {
"good": 1000,
"acceptable": 2000,
"slow": 3000,
"critical": 4000
},
"lcp": {
"good": 2500,
"acceptable": 4000,
"slow": 6000,
"critical": 8000
}
},
"api": {
"responseTime": {
"fast": 200,
"good": 500,
"acceptable": 1000,
"slow": 2000,
"critical": 5000
}
},
"memory": {
"usagePercent": {
"good": 50,
"acceptable": 70,
"warning": 85,
"critical": 95
}
},
"dom": {
"nodeCount": {
"good": 1000,
"acceptable": 2000,
"warning": 3000,
"critical": 5000
}
}
},
"grading": {
"levels": {
"excellent": { "symbol": "🟢", "label": "Excellent", "range": "< good" },
"good": { "symbol": "🟢", "label": "Good", "range": "good ~ acceptable" },
"acceptable": { "symbol": "🟡", "label": "Acceptable", "range": "acceptable ~ slow" },
"slow": { "symbol": "🟠", "label": "Slow", "range": "slow ~ critical" },
"critical": { "symbol": "🔴", "label": "Critical", "range": "> critical" }
},
"overall": {
"pass": "모든 핵심 지표가 acceptable 이상",
"warning": "일부 지표가 slow",
"fail": "하나 이상의 지표가 critical"
}
},
"reporting": {
"includeInTestReport": true,
"separatePerformanceReport": true,
"format": {
"summary": {
"sections": [
"pageLoadMetrics",
"apiMetrics",
"resourceMetrics",
"memoryMetrics"
]
},
"detail": {
"includeRawData": false,
"includeCharts": false,
"includeRecommendations": true
}
},
"template": {
"header": "## ⚡ 성능 메트릭",
"sections": {
"pageLoad": "### 페이지 로드 성능",
"api": "### API 응답 성능",
"resources": "### 리소스 사용량",
"memory": "### 메모리 사용량",
"recommendations": "### 개선 권장사항"
}
}
},
"recommendations": {
"slowPageLoad": [
"이미지 최적화 (WebP 형식, lazy loading)",
"JavaScript 번들 크기 최적화",
"CSS 최적화 및 Critical CSS 적용",
"서버 응답 시간 개선"
],
"slowApi": [
"API 응답 캐싱 적용",
"데이터베이스 쿼리 최적화",
"불필요한 API 호출 제거",
"페이지네이션 적용"
],
"highMemory": [
"메모리 누수 확인",
"불필요한 이벤트 리스너 제거",
"대용량 데이터 가상화 적용",
"컴포넌트 언마운트 시 정리"
],
"tooManyDomNodes": [
"가상 스크롤 적용",
"조건부 렌더링 최적화",
"불필요한 DOM 요소 제거",
"컴포넌트 분할"
]
},
"hooks": {
"beforeNavigation": {
"description": "페이지 이동 전 성능 데이터 수집 시작",
"actions": ["initPerformanceMonitoring"]
},
"afterNavigation": {
"description": "페이지 로드 후 성능 데이터 수집",
"actions": ["getNavigationTiming", "getPaintTiming", "getResourceMetrics"],
"delay": 2000
},
"afterTest": {
"description": "테스트 종료 후 성능 리포트 생성",
"actions": ["getFullPerformanceReport", "generatePerformanceSection"]
}
},
"baseline": {
"description": "성능 기준선 (비교용)",
"values": {
"dashboard": {
"pageLoad": 1500,
"apiCalls": 5,
"avgApiTime": 200
},
"listPage": {
"pageLoad": 2000,
"apiCalls": 3,
"avgApiTime": 300
},
"formPage": {
"pageLoad": 1200,
"apiCalls": 2,
"avgApiTime": 150
}
},
"tolerance": 20
}
}