379 lines
22 KiB
Go
379 lines
22 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
)
|
|
|
|
type ReportData struct {
|
|
Timestamp string `json:"timestamp"`
|
|
DevBuildCount int `json:"devBuildCount"`
|
|
SecInterferenceCount int `json:"secInterferenceCount"`
|
|
InterferenceRate float64 `json:"interferenceRate"`
|
|
KernelHogs []kv `json:"kernelHogs"`
|
|
NetworkHogs []kv `json:"networkHogs"`
|
|
TargetIps []kv `json:"targetIps"`
|
|
HungCounts []kv `json:"hungCounts"`
|
|
Details []DetailRecord `json:"details"`
|
|
EtlDrivers []kv `json:"etlDrivers"`
|
|
MemSwapEvents int `json:"memSwapEvents"`
|
|
AvgDpcWhenInterfered float64 `json:"avgDpcWhenInterfered"`
|
|
DevToolsStats []DevToolUsage `json:"devToolsStats"`
|
|
VDIHealthScore float64 `json:"vdiHealthScore"`
|
|
HourlyStress []kv `json:"hourlyStress"`
|
|
NetworkHangCorrels int `json:"networkHangCorrels"`
|
|
}
|
|
|
|
func generateHTMLReport(filename string, data ReportData) error {
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
htmlTemplate := `<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>시스템 성능 분석 논문</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
body { background-color: #fcfcfc; }
|
|
.academic-font { font-family: 'Nanum Myeongjo', 'Batang', 'Georgia', serif; }
|
|
.sans-font { font-family: 'Malgun Gothic', 'Helvetica Neue', Arial, sans-serif; }
|
|
figure { border-bottom: 1px solid #e5e7eb; padding-bottom: 1rem; margin-bottom: 2rem; }
|
|
figcaption { font-size: 0.875rem; color: #4b5563; margin-top: 0.5rem; text-align: justify; word-break: keep-all; }
|
|
.nature-header { border-bottom: 4px solid #222; padding-bottom: 1rem; margin-bottom: 2rem; }
|
|
h1, h2, h3, h4, h5, h6 { font-family: 'Nanum Myeongjo', 'Batang', serif; word-break: keep-all; }
|
|
p { word-break: keep-all; }
|
|
</style>
|
|
</head>
|
|
<body class="academic-font text-gray-900 leading-relaxed p-4 md:p-12">
|
|
<article class="max-w-4xl mx-auto bg-white p-8 md:p-16 shadow-lg border border-gray-200">
|
|
|
|
<!-- Header Section -->
|
|
<header class="nature-header">
|
|
<div class="text-sm font-bold text-red-700 tracking-wider mb-2 sans-font uppercase">Research Article</div>
|
|
<h1 class="text-3xl md:text-4xl font-bold mb-4 leading-tight">엔터프라이즈 데스크톱 환경 내 보안 솔루션과 개발 워크로드 간의 자원 경합 및 시스템 지연(Lag)에 대한 실증적 분석</h1>
|
|
<div class="flex flex-col md:flex-row justify-between items-baseline mb-4 text-sm sans-font">
|
|
<div>
|
|
<span class="font-bold text-gray-800">시스템 성능 분석 연구소 (System Performance Analysis Lab)</span><br>
|
|
<span class="text-gray-600">자동화 분석 사업부</span>
|
|
</div>
|
|
<div class="text-gray-500 mt-2 md:mt-0" id="reportTime"></div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Abstract -->
|
|
<section class="mb-10 text-justify">
|
|
<h2 class="text-xl font-bold mb-3">초록 (Abstract)</h2>
|
|
<p class="font-semibold text-gray-800 mb-4">
|
|
엔터프라이즈 데스크톱 환경에서 발생하는 시스템 성능 저하는 대량의 I/O를 발생시키는 개발 워크로드와 이를 실시간으로 감시하는 보안 솔루션 간의 자원 경합에서 기인하는 경우가 많다. 본 연구는 백엔드 텔레메트리 데이터를 정량적으로 분석하여 만성적인 시스템 지연의 근본 원인을 규명하고, 새롭게 고안된 시스템 건강도 지수(Health Score) 및 시간대별 스트레스 패턴을 통해 해결 방안을 모색한다.
|
|
</p>
|
|
<p id="abstractDynamic"></p>
|
|
</section>
|
|
|
|
<!-- Main Content -->
|
|
<div class="prose prose-lg max-w-none text-justify">
|
|
|
|
<h3>1. 서론 및 연구 방법</h3>
|
|
<p>
|
|
시스템 메트릭을 지속적으로 모니터링한 결과, 과도한 I/O 작업이 커널 레벨의 병목을 유발하는 패턴이 확인되었다. 본 연구는 CPU, 메모리, DPC(지연된 프로시저 호출) 인터럽트, 프로세스별 I/O 포화도 등 시계열 데이터를 분석하였다. 특히 보안 에이전트가 소스 코드 컴파일이나 컨테이너 빌드와 같은 대규모 I/O 이벤트에 과도하게 개입(Hooking)하여 DPC 인터럽트를 증가시키고 시스템 프리징을 유발한다는 가설을 검증하고자 한다.
|
|
</p>
|
|
|
|
<h3>2. 분석 결과: 자원 경합 및 병목 현상</h3>
|
|
<p>
|
|
수집된 실증 데이터는 성능 저하를 유발하는 구체적인 원인 프로세스들을 지목하고 있다. 이를 크게 커널 병목(DPC/EDR), 네트워크 및 디스크 I/O 포화, 애플리케이션 응답 없음(Hang)의 세 가지 벡터로 분류하여 분석하였다.
|
|
</p>
|
|
|
|
<!-- Figures Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 my-8 not-prose">
|
|
<figure>
|
|
<div class="bg-gray-50 border border-gray-200 p-4">
|
|
<canvas id="kernelChart"></canvas>
|
|
</div>
|
|
<figcaption><strong>Figure 1. 커널 레벨 오버헤드.</strong> 치명적인 DPC 인터럽트 스파이크(>3%%)를 유발한 프로세스의 빈도로, 주로 EDR 및 백신의 실시간 감시 지연을 나타낸다.</figcaption>
|
|
</figure>
|
|
<figure>
|
|
<div class="bg-gray-50 border border-gray-200 p-4">
|
|
<canvas id="networkChart"></canvas>
|
|
</div>
|
|
<figcaption><strong>Figure 2. I/O 및 대역폭 포화.</strong> 심각한 대역폭 고갈을 유발하여 디스크 큐(Queue) 대기열을 증가시키고 스레드 기아 상태를 초래한 프로세스 분포.</figcaption>
|
|
</figure>
|
|
</div>
|
|
|
|
<p id="resultsDynamic"></p>
|
|
|
|
<h3>3. 심화 분석: 보안 솔루션의 영향도 및 상관관계</h3>
|
|
|
|
<div class="bg-gray-100 p-6 border-l-4 border-gray-800 my-6 not-prose sans-font">
|
|
<h4 class="font-bold text-gray-900 mb-4 text-lg">핵심 통계 지표 (Statistical Highlights)</h4>
|
|
<ul class="grid grid-cols-2 gap-4 text-sm mb-6">
|
|
<li><span class="block text-gray-500">시스템 건강도 점수 (Health Score)</span><strong class="text-xl text-green-600" id="vdiHealth">100 / 100</strong></li>
|
|
<li><span class="block text-gray-500">네트워크 포화 - 응답없음 연관 사례</span><strong class="text-xl" id="networkHangCorrels">0</strong></li>
|
|
<li><span class="block text-gray-500">개발 도구 빌드 감지</span><strong class="text-xl" id="devBuildCount">0</strong></li>
|
|
<li><span class="block text-gray-500">보안 솔루션 개입 (Interference)</span><strong class="text-xl text-red-600" id="secInterferenceCount">0</strong></li>
|
|
<li><span class="block text-gray-500">업무 방해 확률 (Interference Rate)</span><strong class="text-xl" id="interferenceRate">0%%</strong></li>
|
|
<li><span class="block text-gray-500">개입 시 평균 DPC 오버헤드</span><strong class="text-xl" id="avgDpc">0%%</strong></li>
|
|
</ul>
|
|
|
|
<h4 class="font-bold text-gray-900 mb-2 mt-4 text-md">Table 1. 감지된 개발 워크로드 및 최대 자원 사용량</h4>
|
|
<div class="overflow-x-auto shadow-sm border border-gray-200 bg-white">
|
|
<table class="w-full text-sm text-left">
|
|
<thead class="bg-gray-50 text-gray-700">
|
|
<tr>
|
|
<th class="p-2 border-b">프로세스 명</th>
|
|
<th class="p-2 border-b text-center">감지 횟수</th>
|
|
<th class="p-2 border-b text-right">최대 CPU (%%)</th>
|
|
<th class="p-2 border-b text-right">최대 I/O (MB/s)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="devToolsTable" class="divide-y divide-gray-100">
|
|
<!-- JS Injection -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<p>
|
|
Table 1에서 관찰할 수 있듯이, 개발 워크로드는 본질적으로 높은 I/O 처리량을 요구한다. 개발 환경의 잦은 파일 생성 및 수정은 보안 에이전트의 파일 핸들 인터셉트로 이어지며, 이는 연쇄적인 커널 CPU 점유율 상승과 DPC 지연 시간 증가를 낳는다. 이러한 메커니즘이 사용자가 체감하는 "프리징(Freezing)"의 주요 매개체로 작용한다.
|
|
</p>
|
|
|
|
<figure class="my-8 not-prose">
|
|
<div class="bg-gray-50 border border-gray-200 p-4 max-w-2xl mx-auto">
|
|
<canvas id="hourlyChart"></canvas>
|
|
</div>
|
|
<figcaption><strong>Figure 3. 시간대별 시스템 스트레스 패턴.</strong> 하루 중 시스템 이벤트(프리징, 포화, 응답없음)가 집중적으로 발생한 시간대를 나타내며, 출근 직후의 부트 스톰이나 일과 중 빌드 집중 시간을 유추할 수 있다.</figcaption>
|
|
</figure>
|
|
|
|
<figure class="my-8 not-prose">
|
|
<div class="bg-gray-50 border border-gray-200 p-4 max-w-2xl mx-auto">
|
|
<canvas id="etlChart"></canvas>
|
|
</div>
|
|
<figcaption><strong>Figure 4. ETW 커널 드라이버 분석.</strong> Windows 커널 추적(.etl)을 심층 분석하여 가장 많은 인터럽트 및 파일 시스템 훅을 발생시킨 서드파티 드라이버(<code>.sys</code>)를 식별한 결과이다.</figcaption>
|
|
</figure>
|
|
|
|
<h3>4. 결론 및 제언</h3>
|
|
<p>
|
|
본 분석 결과, 과도한 I/O가 동반되는 개발 워크로드와 보안 솔루션 간의 충돌이 시스템 성능 저하의 핵심 원인임이 입증되었다. 이러한 문제를 완화하기 위해 알려진 개발 디렉토리 및 빌드 도구에 대해 보안 소프트웨어 내 예외 처리(Whitelisting)를 적용할 것을 강력히 권고한다. 또한 대량의 I/O 작업 중 발생하는 휴리스틱 스캐닝 파라미터를 튜닝함으로써 관측된 DPC 지연 스파이크를 예방하고 시스템 안정성을 회복할 수 있을 것이다.
|
|
</p>
|
|
|
|
<hr class="my-10 border-gray-300">
|
|
|
|
<!-- Appendix: Logs -->
|
|
<h3>부록 (Appendix): 시간대별 상세 이상 징후 발생 이력</h3>
|
|
<p class="text-sm">특정 성능 저하 이벤트에 대한 시계열 텔레메트리 상세 기록이다.</p>
|
|
|
|
<div class="not-prose overflow-x-auto my-6 text-sm sans-font shadow-sm border border-gray-200">
|
|
<table class="w-full text-left border-collapse">
|
|
<thead class="bg-gray-100 text-gray-700">
|
|
<tr>
|
|
<th class="p-3 border-b">#</th>
|
|
<th class="p-3 border-b">분류 / 이벤트 유형</th>
|
|
<th class="p-3 border-b">상세 원인 및 지표</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="detailsTableBody" class="text-gray-600 divide-y divide-gray-100 bg-white">
|
|
<!-- JS Injection -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</article>
|
|
|
|
<script>
|
|
const rawData = %s;
|
|
|
|
// Populate DOM elements
|
|
document.getElementById('reportTime').textContent = "게재 일자: " + rawData.timestamp;
|
|
|
|
const vdiScoreElement = document.getElementById('vdiHealth');
|
|
vdiScoreElement.textContent = rawData.vdiHealthScore.toFixed(1) + " / 100";
|
|
if(rawData.vdiHealthScore < 70) {
|
|
vdiScoreElement.className = "text-xl text-red-600 font-bold";
|
|
} else if(rawData.vdiHealthScore < 90) {
|
|
vdiScoreElement.className = "text-xl text-yellow-600 font-bold";
|
|
}
|
|
|
|
document.getElementById('networkHangCorrels').textContent = rawData.networkHangCorrels + " 건";
|
|
|
|
// Dynamic Text Generation
|
|
const abstractP = document.getElementById('abstractDynamic');
|
|
if (rawData.devBuildCount > 0) {
|
|
let text = "분석 결과, 총 <strong>" + rawData.devBuildCount + "</strong>회의 유의미한 빌드 및 컴파일 작업이 감지되었다. ";
|
|
if (rawData.interferenceRate > 0) {
|
|
text += "특히 이 중 <strong>" + rawData.interferenceRate.toFixed(1) + "%%</strong>의 사례에서 보안 솔루션의 공격적인 개입이 확인되었으며, " +
|
|
"이로 인해 커널 DPC 오버헤드가 평균 <strong>" + rawData.avgDpcWhenInterfered.toFixed(2) + "%%</strong>까지 치솟아 급성 시스템 프리징과 직접적인 상관관계를 보였다. ";
|
|
} else {
|
|
text += "다행히 본 측정 기간 동안 보안 솔루션이 빌드 작업에 과도하게 개입하지 않아, 기준 커널 안정성이 유지된 것으로 확인되었다. ";
|
|
}
|
|
if (rawData.memSwapEvents > 0) {
|
|
text += "더불어, 총 <strong>" + rawData.memSwapEvents + "</strong>회의 심각한 메모리 스와핑(Memory Swapping) 현상이 관측되어 I/O 지연을 한층 악화시킨 것으로 나타났다. ";
|
|
}
|
|
abstractP.innerHTML = text;
|
|
} else {
|
|
abstractP.innerHTML = "모니터링 기간 동안 유의미한 개발 빌드 워크로드는 감지되지 않았다. 따라서 관측된 시스템 성능 저하는 컴파일 자원 경합보다는 백그라운드 서비스 또는 일반적인 사용자 애플리케이션의 오동작에서 기인했을 가능성이 높다.";
|
|
}
|
|
|
|
const resultsP = document.getElementById('resultsDynamic');
|
|
let resText = "수집된 지표를 종합한 결과, ";
|
|
if (rawData.kernelHogs && rawData.kernelHogs.length > 0) {
|
|
resText += "커널 병목을 유발한 가장 치명적인 프로세스는 <strong>" + rawData.kernelHogs[0].Key + "</strong>(으)로 확인되었다. ";
|
|
} else {
|
|
resText += "커널 레벨에서 심각한 DPC 병목 현상은 발견되지 않았다. ";
|
|
}
|
|
|
|
if (rawData.networkHogs && rawData.networkHogs.length > 0) {
|
|
resText += "동시에 I/O 포화 상태를 주도한 주범은 <strong>" + rawData.networkHogs[0].Key + "</strong>(이)었다. ";
|
|
} else {
|
|
resText += "I/O 포화도 역시 허용 가능한 임계치 내에 머물렀다. ";
|
|
}
|
|
|
|
const totalHangs = rawData.hungCounts ? rawData.hungCounts.reduce((a, b) => a + b.Value, 0) : 0;
|
|
if (totalHangs > 0) {
|
|
resText += "이러한 자원 경합이 발생하는 동안 총 <strong>" + totalHangs + "</strong>회의 애플리케이션 응답 없음(Hang) 현상이 기록되어 사용자 경험이 크게 저하되었음을 알 수 있다. ";
|
|
if (rawData.networkHangCorrels > 0) {
|
|
resText += "특히 대역폭 포화와 응답 없음 현상이 동반 발생한 사례가 <strong>" + rawData.networkHangCorrels + "</strong>건 발견되어 높은 상관관계를 시사한다.";
|
|
}
|
|
} else {
|
|
resText += "긍정적인 점은, 해당 기간 동안 완전한 애플리케이션 응답 없음(Hang) 현상은 보고되지 않았다는 것이다.";
|
|
}
|
|
resultsP.innerHTML = resText;
|
|
|
|
document.getElementById('devBuildCount').textContent = rawData.devBuildCount;
|
|
document.getElementById('secInterferenceCount').textContent = rawData.secInterferenceCount;
|
|
document.getElementById('interferenceRate').textContent = rawData.interferenceRate.toFixed(1) + "%%";
|
|
document.getElementById('avgDpc').textContent = rawData.avgDpcWhenInterfered.toFixed(2) + "%%";
|
|
|
|
// Chart defaults
|
|
Chart.defaults.font.family = "'Malgun Gothic', 'Helvetica Neue', 'Arial', sans-serif";
|
|
Chart.defaults.color = '#4b5563';
|
|
|
|
// Kernel Hogs Chart
|
|
const kernelHogs = (rawData.kernelHogs || []).slice(0, 5);
|
|
new Chart(document.getElementById('kernelChart').getContext('2d'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: kernelHogs.map(h => h.Key),
|
|
datasets: [{
|
|
label: '프리징 유발 횟수',
|
|
data: kernelHogs.map(h => h.Value),
|
|
backgroundColor: 'rgba(153, 27, 27, 0.7)',
|
|
borderColor: 'rgb(153, 27, 27)',
|
|
borderWidth: 1
|
|
}]
|
|
}
|
|
});
|
|
|
|
// Network Hogs Chart
|
|
const netHogs = (rawData.networkHogs || []).slice(0, 5);
|
|
new Chart(document.getElementById('networkChart').getContext('2d'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: netHogs.map(h => h.Key),
|
|
datasets: [{
|
|
label: '대역폭 포화 횟수',
|
|
data: netHogs.map(h => h.Value),
|
|
backgroundColor: 'rgba(30, 64, 175, 0.7)',
|
|
borderColor: 'rgb(30, 64, 175)',
|
|
borderWidth: 1
|
|
}]
|
|
}
|
|
});
|
|
|
|
// Hourly Stress Chart
|
|
const hourlyStress = rawData.hourlyStress || [];
|
|
new Chart(document.getElementById('hourlyChart').getContext('2d'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: hourlyStress.map(h => h.Key),
|
|
datasets: [{
|
|
label: '시간대별 이상 징후 (건)',
|
|
data: hourlyStress.map(h => h.Value),
|
|
backgroundColor: 'rgba(234, 88, 12, 0.2)',
|
|
borderColor: 'rgb(234, 88, 12)',
|
|
borderWidth: 2,
|
|
fill: true,
|
|
tension: 0.3
|
|
}]
|
|
}
|
|
});
|
|
|
|
// ETL Chart
|
|
const etlDrivers = (rawData.etlDrivers || []).slice(0, 5);
|
|
new Chart(document.getElementById('etlChart').getContext('2d'), {
|
|
type: 'bar',
|
|
data: {
|
|
labels: etlDrivers.map(h => h.Key),
|
|
datasets: [{
|
|
label: '인터럽트 빈도',
|
|
data: etlDrivers.map(h => h.Value),
|
|
backgroundColor: 'rgba(17, 24, 39, 0.7)',
|
|
borderColor: 'rgb(17, 24, 39)',
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: { indexAxis: 'y' }
|
|
});
|
|
|
|
// Populate Dev Tools Table
|
|
const devTable = document.getElementById('devToolsTable');
|
|
const devStats = rawData.devToolsStats || [];
|
|
if (devStats.length > 0) {
|
|
devStats.sort((a,b) => b.count - a.count).forEach(dt => {
|
|
devTable.innerHTML += "<tr>" +
|
|
"<td class=\"p-2 border-b\"><code>" + dt.processName + "</code></td>" +
|
|
"<td class=\"p-2 border-b text-center\">" + dt.count + "</td>" +
|
|
"<td class=\"p-2 border-b text-right text-red-600 font-semibold\">" + dt.maxCpu.toFixed(1) + "%%</td>" +
|
|
"<td class=\"p-2 border-b text-right text-blue-600 font-semibold\">" + dt.maxIo.toFixed(1) + " MB/s</td>" +
|
|
"</tr>";
|
|
});
|
|
} else {
|
|
devTable.innerHTML = "<tr><td colspan=\"4\" class=\"p-3 text-center italic text-gray-400\">감지된 개발 워크로드가 없습니다.</td></tr>";
|
|
}
|
|
|
|
// Populate Details Table
|
|
const tbody = document.getElementById('detailsTableBody');
|
|
const detailsList = rawData.details || [];
|
|
const limit = Math.min(detailsList.length, 100);
|
|
|
|
if (limit === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="3" class="p-3 text-center italic text-gray-400">발생한 이상 징후 기록이 없습니다.</td></tr>';
|
|
} else {
|
|
for(let i=0; i<limit; i++) {
|
|
const d = detailsList[i];
|
|
const tr = document.createElement('tr');
|
|
tr.className = i %% 2 === 0 ? 'bg-white' : 'bg-gray-50';
|
|
|
|
const td1 = document.createElement('td');
|
|
td1.className = 'p-3 text-xs';
|
|
td1.textContent = i + 1;
|
|
|
|
const td2 = document.createElement('td');
|
|
td2.className = 'p-3 font-semibold text-gray-800 text-sm';
|
|
td2.textContent = d.Category;
|
|
|
|
const td3 = document.createElement('td');
|
|
td3.className = 'p-3';
|
|
td3.innerHTML = '<ul class="list-disc list-inside text-xs space-y-1">' +
|
|
(d.Content || []).map(c => '<li>'+c+'</li>').join('') +
|
|
'</ul>';
|
|
|
|
tr.appendChild(td1);
|
|
tr.appendChild(td2);
|
|
tr.appendChild(td3);
|
|
tbody.appendChild(tr);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>`
|
|
|
|
htmlContent := fmt.Sprintf(htmlTemplate, string(jsonData))
|
|
return ioutil.WriteFile(filename, []byte(htmlContent), 0644)
|
|
}
|