up
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
analyzer/*.md
|
||||
analyzer/*.docx
|
||||
analyzer/*.html
|
||||
+149
-6
@@ -65,6 +65,13 @@ type ProcessHung struct {
|
||||
PID int `json:"PID"`
|
||||
}
|
||||
|
||||
type DevToolUsage struct {
|
||||
ProcessName string `json:"processName"`
|
||||
Count int `json:"count"`
|
||||
MaxCPU float64 `json:"maxCpu"`
|
||||
MaxIO float64 `json:"maxIo"`
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
Key string
|
||||
Value int
|
||||
@@ -178,6 +185,8 @@ func main() {
|
||||
|
||||
devBuildCount := 0
|
||||
secInterferenceCount := 0
|
||||
memSwapCount := 0
|
||||
var sumDpcInterfered float64
|
||||
|
||||
sysKernelHogs := make(map[string]int)
|
||||
sysNetworkHogs := make(map[string]int)
|
||||
@@ -185,7 +194,13 @@ func main() {
|
||||
sysTargetIps := make(map[string]int)
|
||||
sysTargetIpsMaxIO := make(map[string]float64)
|
||||
hungCounts := make(map[string]int)
|
||||
devToolsStats := make(map[string]*DevToolUsage)
|
||||
|
||||
// 심화 분석 지표
|
||||
hourlyStress := make(map[int]int)
|
||||
networkHangCorrels := 0
|
||||
totalEventsParsed := 0
|
||||
|
||||
var details []DetailRecord
|
||||
|
||||
rows, err := db.Query(`
|
||||
@@ -214,26 +229,75 @@ func main() {
|
||||
|
||||
// 🚨 [Smoking Gun 타겟팅]
|
||||
isCompiling := false
|
||||
var activeDevTools []string
|
||||
|
||||
for _, p := range ioProcs {
|
||||
if isDevTool(p.ProcessName) && p.IODataBytesPersec > 1048576 {
|
||||
mbps := p.IODataBytesPersec / 1048576.0
|
||||
cpuVal := 0.0
|
||||
for _, cp := range cpuProcs {
|
||||
if cp.ProcessName == p.ProcessName && cp.PID == p.PID {
|
||||
cpuVal = cp.CPUPercent; break
|
||||
}
|
||||
}
|
||||
|
||||
if stat, ok := devToolsStats[p.ProcessName]; ok {
|
||||
stat.Count++
|
||||
if cpuVal > stat.MaxCPU { stat.MaxCPU = cpuVal }
|
||||
if mbps > stat.MaxIO { stat.MaxIO = mbps }
|
||||
} else {
|
||||
devToolsStats[p.ProcessName] = &DevToolUsage{ProcessName: p.ProcessName, Count: 1, MaxCPU: cpuVal, MaxIO: mbps}
|
||||
}
|
||||
|
||||
activeDevTools = append(activeDevTools, fmt.Sprintf("%s (CPU: %.1f%%, I/O: %.1f MB/s)", p.ProcessName, cpuVal, mbps))
|
||||
isCompiling = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isCompiling {
|
||||
devBuildCount++
|
||||
secInterfered := false
|
||||
var secCulprits []string
|
||||
|
||||
if r.DpcInterrupt > 3.0 { secInterfered = true }
|
||||
for _, cp := range cpuProcs {
|
||||
if isSecProc(cp.ProcessName) && cp.KernelCPUPercent > 2.0 { secInterfered = true; break }
|
||||
if isSecProc(cp.ProcessName) && cp.KernelCPUPercent > 2.0 {
|
||||
secInterfered = true
|
||||
secCulprits = append(secCulprits, fmt.Sprintf("%s (Kernel CPU: %.1f%%)", cp.ProcessName, cp.KernelCPUPercent))
|
||||
}
|
||||
}
|
||||
if secInterfered { secInterferenceCount++ }
|
||||
if secInterfered {
|
||||
secInterferenceCount++
|
||||
sumDpcInterfered += r.DpcInterrupt
|
||||
|
||||
rec := DetailRecord{
|
||||
Category: fmt.Sprintf("[빌드-보안 충돌] 개발 도구와 보안 솔루션 경합 감지 - 시간: %s | PC: %s", localTimeStr, r.Hostname),
|
||||
Content: []string{
|
||||
"감지된 개발 프로세스: " + strings.Join(activeDevTools, ", "),
|
||||
"관여된 보안 솔루션: " + strings.Join(secCulprits, ", "),
|
||||
fmt.Sprintf("발생 커널 오버헤드(DPC): %.1f%%", r.DpcInterrupt),
|
||||
},
|
||||
}
|
||||
details = append(details, rec)
|
||||
}
|
||||
}
|
||||
|
||||
if r.TotalMem > 90.0 {
|
||||
hasHighIO := false
|
||||
for _, p := range ioProcs {
|
||||
if p.IODataBytesPersec > 1048576 {
|
||||
hasHighIO = true; break
|
||||
}
|
||||
}
|
||||
if hasHighIO { memSwapCount++ }
|
||||
}
|
||||
|
||||
// 상세 내역 및 일반 요약 로직
|
||||
isEventOccurred := false
|
||||
|
||||
// 1. 커널 프리징
|
||||
if r.DpcInterrupt > 3.0 {
|
||||
isEventOccurred = true
|
||||
rec := DetailRecord{Category: fmt.Sprintf("[프리징 유발] 커널 오버헤드 감지 - 시간: %s | PC: %s | 오버헤드: %.1f%%", localTimeStr, r.Hostname, r.DpcInterrupt)}
|
||||
for _, cp := range cpuProcs {
|
||||
if cp.KernelCPUPercent > 3.0 && !isIgnoredProcess(cp.ProcessName) {
|
||||
@@ -255,6 +319,7 @@ func main() {
|
||||
|
||||
if !isNetworkChoke {
|
||||
isNetworkChoke = true
|
||||
isEventOccurred = true
|
||||
netRec = DetailRecord{Category: fmt.Sprintf("[대역폭 포화 감지] 네트워크/디스크 지연 - 시간: %s | PC: %s", localTimeStr, r.Hostname)}
|
||||
}
|
||||
|
||||
@@ -280,9 +345,9 @@ func main() {
|
||||
// 3. 앱 Hang
|
||||
var hung []ProcessHung
|
||||
json.Unmarshal([]byte(r.HungProcesses), &hung)
|
||||
hasValidHang := false
|
||||
if len(hung) > 0 {
|
||||
hangRec := DetailRecord{Category: fmt.Sprintf("[응답 없음] 프로세스 Hang 발생 - 시간: %s | PC: %s", localTimeStr, r.Hostname)}
|
||||
hasValidHang := false
|
||||
for _, h := range hung {
|
||||
if !isIgnoredProcess(h.ProcessName) {
|
||||
hungCounts[h.ProcessName]++
|
||||
@@ -290,8 +355,20 @@ func main() {
|
||||
hangRec.Content = append(hangRec.Content, fmt.Sprintf("멈춤: %s%s (PID: %d)", h.ProcessName, getProcessTag(h.ProcessName), h.PID))
|
||||
}
|
||||
}
|
||||
if hasValidHang { details = append(details, hangRec) }
|
||||
if hasValidHang {
|
||||
details = append(details, hangRec)
|
||||
isEventOccurred = true
|
||||
}
|
||||
}
|
||||
|
||||
// 심화 분석: 상관관계 및 시간대별 스트레스 추적
|
||||
if isNetworkChoke && hasValidHang {
|
||||
networkHangCorrels++ // 대역폭 포화가 앱 응답없음을 유발한 연관성 의심 사례
|
||||
}
|
||||
if isEventOccurred {
|
||||
hourlyStress[r.Timestamp.Hour()]++
|
||||
}
|
||||
totalEventsParsed++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,8 +479,74 @@ func main() {
|
||||
err = ioutil.WriteFile(reportMd, []byte(md.String()), 0644)
|
||||
if err != nil { log.Fatalf("MD 파일 저장 실패: %v", err) }
|
||||
|
||||
// --- HTML Report Data Preparation ---
|
||||
var rate float64 = 0
|
||||
if devBuildCount > 0 {
|
||||
rate = (float64(secInterferenceCount) / float64(devBuildCount)) * 100
|
||||
}
|
||||
|
||||
// Calculate advanced metrics
|
||||
var avgDpc float64 = 0
|
||||
if secInterferenceCount > 0 {
|
||||
avgDpc = sumDpcInterfered / float64(secInterferenceCount)
|
||||
}
|
||||
|
||||
healthScore := 100.0
|
||||
healthScore -= float64(devBuildCount) * 0.1
|
||||
healthScore -= float64(secInterferenceCount) * 1.5
|
||||
healthScore -= float64(memSwapCount) * 2.0
|
||||
healthScore -= float64(len(hungCounts)) * 1.0
|
||||
if healthScore < 0 { healthScore = 0 }
|
||||
|
||||
// Prepare flat slices for charts
|
||||
kernelHogsSlice := sortMap(sysKernelHogs)
|
||||
networkHogsSlice := sortMap(sysNetworkHogs)
|
||||
targetIpsSlice := sortMap(sysTargetIps)
|
||||
hungCountsSlice := sortMap(hungCounts)
|
||||
|
||||
var etlDriversSlice []kv
|
||||
if len(etlFiles) > 0 {
|
||||
sysMap := parseETL(etlFiles[0])
|
||||
etlDriversSlice = sortMap(sysMap)
|
||||
}
|
||||
|
||||
var devStatsSlice []DevToolUsage
|
||||
for _, v := range devToolsStats {
|
||||
devStatsSlice = append(devStatsSlice, *v)
|
||||
}
|
||||
|
||||
var hourlyStressSlice []kv
|
||||
for i := 0; i < 24; i++ {
|
||||
hourlyStressSlice = append(hourlyStressSlice, kv{Key: fmt.Sprintf("%02d:00", i), Value: hourlyStress[i]})
|
||||
}
|
||||
|
||||
reportData := ReportData{
|
||||
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
||||
DevBuildCount: devBuildCount,
|
||||
SecInterferenceCount: secInterferenceCount,
|
||||
InterferenceRate: rate,
|
||||
KernelHogs: kernelHogsSlice,
|
||||
NetworkHogs: networkHogsSlice,
|
||||
TargetIps: targetIpsSlice,
|
||||
HungCounts: hungCountsSlice,
|
||||
Details: details,
|
||||
EtlDrivers: etlDriversSlice,
|
||||
MemSwapEvents: memSwapCount,
|
||||
AvgDpcWhenInterfered: avgDpc,
|
||||
DevToolsStats: devStatsSlice,
|
||||
VDIHealthScore: healthScore,
|
||||
HourlyStress: hourlyStressSlice,
|
||||
NetworkHangCorrels: networkHangCorrels,
|
||||
}
|
||||
|
||||
reportHtml := fmt.Sprintf("Report_%s.html", timestampStr)
|
||||
err = generateHTMLReport(reportHtml, reportData)
|
||||
if err != nil {
|
||||
log.Printf("HTML 파일 저장 실패: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("==================================================\n")
|
||||
fmt.Printf(" 분석 완료: '%s' 및 '%s' 2종류의 문서가 생성되었습니다.\n", reportDocx, reportMd)
|
||||
fmt.Printf(" 분석 완료: '%s', '%s', '%s' 문서가 생성되었습니다.\n", reportDocx, reportMd, reportHtml)
|
||||
fmt.Printf("==================================================\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
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)
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user