diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..981a0f3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+analyzer/*.md
+analyzer/*.docx
+analyzer/*.html
\ No newline at end of file
diff --git a/analyzer/main.go b/analyzer/main.go
index e86d171..5cbe645 100644
--- a/analyzer/main.go
+++ b/analyzer/main.go
@@ -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")
}
diff --git a/analyzer/report_html.go b/analyzer/report_html.go
new file mode 100644
index 0000000..5d868ed
--- /dev/null
+++ b/analyzer/report_html.go
@@ -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 := `
+
+
+
+
+ 시스템 성능 분석 논문
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 초록 (Abstract)
+
+ 엔터프라이즈 데스크톱 환경에서 발생하는 시스템 성능 저하는 대량의 I/O를 발생시키는 개발 워크로드와 이를 실시간으로 감시하는 보안 솔루션 간의 자원 경합에서 기인하는 경우가 많다. 본 연구는 백엔드 텔레메트리 데이터를 정량적으로 분석하여 만성적인 시스템 지연의 근본 원인을 규명하고, 새롭게 고안된 시스템 건강도 지수(Health Score) 및 시간대별 스트레스 패턴을 통해 해결 방안을 모색한다.
+
+
+
+
+
+
+
+
1. 서론 및 연구 방법
+
+ 시스템 메트릭을 지속적으로 모니터링한 결과, 과도한 I/O 작업이 커널 레벨의 병목을 유발하는 패턴이 확인되었다. 본 연구는 CPU, 메모리, DPC(지연된 프로시저 호출) 인터럽트, 프로세스별 I/O 포화도 등 시계열 데이터를 분석하였다. 특히 보안 에이전트가 소스 코드 컴파일이나 컨테이너 빌드와 같은 대규모 I/O 이벤트에 과도하게 개입(Hooking)하여 DPC 인터럽트를 증가시키고 시스템 프리징을 유발한다는 가설을 검증하고자 한다.
+
+
+
2. 분석 결과: 자원 경합 및 병목 현상
+
+ 수집된 실증 데이터는 성능 저하를 유발하는 구체적인 원인 프로세스들을 지목하고 있다. 이를 크게 커널 병목(DPC/EDR), 네트워크 및 디스크 I/O 포화, 애플리케이션 응답 없음(Hang)의 세 가지 벡터로 분류하여 분석하였다.
+
+
+
+
+
+
+
+
+ Figure 1. 커널 레벨 오버헤드. 치명적인 DPC 인터럽트 스파이크(>3%%)를 유발한 프로세스의 빈도로, 주로 EDR 및 백신의 실시간 감시 지연을 나타낸다.
+
+
+
+
+
+ Figure 2. I/O 및 대역폭 포화. 심각한 대역폭 고갈을 유발하여 디스크 큐(Queue) 대기열을 증가시키고 스레드 기아 상태를 초래한 프로세스 분포.
+
+
+
+
+
+
3. 심화 분석: 보안 솔루션의 영향도 및 상관관계
+
+
+
핵심 통계 지표 (Statistical Highlights)
+
+ 시스템 건강도 점수 (Health Score) 100 / 100
+ 네트워크 포화 - 응답없음 연관 사례 0
+ 개발 도구 빌드 감지 0
+ 보안 솔루션 개입 (Interference) 0
+ 업무 방해 확률 (Interference Rate) 0%%
+ 개입 시 평균 DPC 오버헤드 0%%
+
+
+
Table 1. 감지된 개발 워크로드 및 최대 자원 사용량
+
+
+
+
+ 프로세스 명
+ 감지 횟수
+ 최대 CPU (%%)
+ 최대 I/O (MB/s)
+
+
+
+
+
+
+
+
+
+
+ Table 1에서 관찰할 수 있듯이, 개발 워크로드는 본질적으로 높은 I/O 처리량을 요구한다. 개발 환경의 잦은 파일 생성 및 수정은 보안 에이전트의 파일 핸들 인터셉트로 이어지며, 이는 연쇄적인 커널 CPU 점유율 상승과 DPC 지연 시간 증가를 낳는다. 이러한 메커니즘이 사용자가 체감하는 "프리징(Freezing)"의 주요 매개체로 작용한다.
+
+
+
+
+
+
+ Figure 3. 시간대별 시스템 스트레스 패턴. 하루 중 시스템 이벤트(프리징, 포화, 응답없음)가 집중적으로 발생한 시간대를 나타내며, 출근 직후의 부트 스톰이나 일과 중 빌드 집중 시간을 유추할 수 있다.
+
+
+
+
+
+
+ Figure 4. ETW 커널 드라이버 분석. Windows 커널 추적(.etl)을 심층 분석하여 가장 많은 인터럽트 및 파일 시스템 훅을 발생시킨 서드파티 드라이버(.sys)를 식별한 결과이다.
+
+
+
4. 결론 및 제언
+
+ 본 분석 결과, 과도한 I/O가 동반되는 개발 워크로드와 보안 솔루션 간의 충돌이 시스템 성능 저하의 핵심 원인임이 입증되었다. 이러한 문제를 완화하기 위해 알려진 개발 디렉토리 및 빌드 도구에 대해 보안 소프트웨어 내 예외 처리(Whitelisting)를 적용할 것을 강력히 권고한다. 또한 대량의 I/O 작업 중 발생하는 휴리스틱 스캐닝 파라미터를 튜닝함으로써 관측된 DPC 지연 스파이크를 예방하고 시스템 안정성을 회복할 수 있을 것이다.
+
+
+
+
+
+
부록 (Appendix): 시간대별 상세 이상 징후 발생 이력
+
특정 성능 저하 이벤트에 대한 시계열 텔레메트리 상세 기록이다.
+
+
+
+
+
+ #
+ 분류 / 이벤트 유형
+ 상세 원인 및 지표
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+ htmlContent := fmt.Sprintf(htmlTemplate, string(jsonData))
+ return ioutil.WriteFile(filename, []byte(htmlContent), 0644)
+}
diff --git a/analyzer/vdi-analyzer.exe b/analyzer/vdi-analyzer.exe
index cd06d3c..3ce29a2 100644
Binary files a/analyzer/vdi-analyzer.exe and b/analyzer/vdi-analyzer.exe differ