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 := ` + + + + + 시스템 성능 분석 논문 + + + + + + +
+ + +
+
Research Article
+

엔터프라이즈 데스크톱 환경 내 보안 솔루션과 개발 워크로드 간의 자원 경합 및 시스템 지연(Lag)에 대한 실증적 분석

+
+
+ 시스템 성능 분석 연구소 (System Performance Analysis Lab)
+ 자동화 분석 사업부 +
+
+
+
+ + +
+

초록 (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