commit 574783ab150a69ac5a2c44de3fe73de2df69579b Author: rl544 Date: Sun Sep 13 19:19:37 2026 +0900 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/analyzer/Report_20260913_181145.docx b/analyzer/Report_20260913_181145.docx new file mode 100644 index 0000000..dd01a2d Binary files /dev/null and b/analyzer/Report_20260913_181145.docx differ diff --git a/analyzer/Report_20260913_182051.docx b/analyzer/Report_20260913_182051.docx new file mode 100644 index 0000000..c5bd3c4 Binary files /dev/null and b/analyzer/Report_20260913_182051.docx differ diff --git a/analyzer/Report_20260913_182428.docx b/analyzer/Report_20260913_182428.docx new file mode 100644 index 0000000..e9ef802 Binary files /dev/null and b/analyzer/Report_20260913_182428.docx differ diff --git a/analyzer/Report_20260913_185439.docx b/analyzer/Report_20260913_185439.docx new file mode 100644 index 0000000..5a20793 Binary files /dev/null and b/analyzer/Report_20260913_185439.docx differ diff --git a/analyzer/Report_20260913_190306.docx b/analyzer/Report_20260913_190306.docx new file mode 100644 index 0000000..a59d8b2 Binary files /dev/null and b/analyzer/Report_20260913_190306.docx differ diff --git a/analyzer/Report_20260913_190306.md b/analyzer/Report_20260913_190306.md new file mode 100644 index 0000000..2204d5c --- /dev/null +++ b/analyzer/Report_20260913_190306.md @@ -0,0 +1,127 @@ +# 시스템 성능 및 장애 원인 분석 리포트 + +작성 일시: 2026-09-13 19:03:06 + + +## 🚨 보안 솔루션 개발 환경 충돌 정밀 분석 + +본 섹션은 보안 프로그램(백신/EDR/DLP)이 개발자의 빌드/컴파일 작업에 미치는 실질적인 성능 저하 연관성을 교차 검증한 결과입니다. + +분석 데이터 내 개발 도구(IDE, 컴파일러 등) 활성화 감지: 총 1 회 + +**▶ 👉 위 빌드 작업 중, 보안 프로그램이 개입하여 커널 프리징/시스템 렉을 유발한 횟수: 0 회** + +**▶ 🔥 보안 솔루션으로 인한 개발 업무 방해(병목) 확률: 0.0%** + + +## 1. 시스템 체감 렉(Lag) 유발 핵심 주범 요약 + +**▶ [프리징 원인] 장애 시점 커널(EDR/백신) 병목 프로세스** + +- agy (멈춤 유발 횟수: 1회) +- msedge#5 (멈춤 유발 횟수: 1회) + +**▶ [지연 원인] 장애 시점 트래픽 폭주 유발 프로세스** + +- AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1 (포화 유발: 5회 | 최고 I/O 속도: 76.7 MB/s) +- svchost#79 (포화 유발: 5회 | 최고 I/O 속도: 46.9 MB/s) +- svchost#80 (포화 유발: 4회 | 최고 I/O 속도: 50.2 MB/s) +- Docker Desktop#1 (포화 유발: 3회 | 최고 I/O 속도: 5.2 MB/s) +- SearchIndexer (포화 유발: 2회 | 최고 I/O 속도: 164.1 MB/s) + +**▶ [대역폭 도둑] 렉 유발 핵심 통신 목적지 IP** + +- 해당 원인 없음 + +**▶ 만성적 응답 없음(Hang) 발생 애플리케이션** + +- 해당 원인 없음 + +## 2. 시간대별 상세 이상 징후 발생 이력 (Chronological Log) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 19:02:53 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#79 (PID: 9624) / 총 40.8 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:56:23 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#79 (PID: 9624) / 총 24.1 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:54:12 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#79 (PID: 9624) / 총 46.1 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:50:53 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#79 (PID: 9624) / 총 46.9 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:44:21 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: agy (PID: 23000) / 총 8.0 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:42:11 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#79 (PID: 9624) / 총 46.6 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:35:39 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: Docker Desktop#1 (PID: 25948) / 총 5.2 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:33:29 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: Docker Desktop#1 (PID: 25948) / 총 5.1 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:22:40 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: SearchIndexer (PID: 9308) / 총 164.1 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:21:35 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: SearchIndexer (PID: 9308) / 총 62.2 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:17:44 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#80 (PID: 9624) / 총 42.4 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 18:05:49 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: Docker Desktop#1 (PID: 25948) / 총 5.2 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 17:53:40 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#80 (PID: 9624) / 총 50.2 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 17:51:30 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1 (PID: 32192) / 총 55.9 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 17:50:25 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1 (PID: 32192) / 총 49.6 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 17:49:19 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1 (PID: 32192) / 총 44.1 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 17:48:13 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1 (PID: 32192) / 총 66.1 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 17:47:08 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1 (PID: 32192) / 총 76.7 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) +- 트래픽 점유: svchost#80 (PID: 9624) / 총 11.0 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +**▶ [프리징 유발] 커널 오버헤드 감지 - 시간: 2026-09-13 17:44:59 | PC: HANA-FINANCIAL- | 오버헤드: 3.4%** + +- 주범: agy (PID: 23000) / 커널 점유 72.0% +- 주범: msedge#5 (PID: 23976) / 커널 점유 5.0% + +**▶ [대역폭 포화 감지] 네트워크/디스크 지연 - 시간: 2026-09-13 17:43:40 | PC: HANA-FINANCIAL-** + +- 트래픽 점유: svchost#80 (PID: 9624) / 총 6.4 MB/s -> 목적지 연결 없음 (내부 대용량 파일 I/O) + +## 3. ETW (.etl) 커널 덤프 정밀 분석 결과 + +분석할 .etl 커널 덤프 파일이 없습니다. + diff --git a/analyzer/VDI_Analysis_Report.md b/analyzer/VDI_Analysis_Report.md new file mode 100644 index 0000000..08926c2 --- /dev/null +++ b/analyzer/VDI_Analysis_Report.md @@ -0,0 +1,48 @@ +# VDI 성능 및 장애 원인 분석 리포트 + +**작성 일시:** 2026-09-13 17:49:53 + +## 1. DB 이상 징후 분석 + +### 🌐 과다 대역폭 유발 프로세스 추적 +- **PC:** HANA-FINANCIAL- +- **시간:** 2026-09-13T08:49:19Z +- **프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 4.4 MB/s + - 📁 네트워크 연결 없음 (로컬 파일스캔/디스크 I/O로 추정) + +### 🌐 과다 대역폭 유발 프로세스 추적 +- **PC:** HANA-FINANCIAL- +- **시간:** 2026-09-13T08:48:13Z +- **프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 6.6 MB/s + - 📁 네트워크 연결 없음 (로컬 파일스캔/디스크 I/O로 추정) + +### 🌐 과다 대역폭 유발 프로세스 추적 +- **PC:** HANA-FINANCIAL- +- **시간:** 2026-09-13T08:47:08Z +- **프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 7.7 MB/s + - 📁 네트워크 연결 없음 (로컬 파일스캔/디스크 I/O로 추정) + +### 🌐 과다 대역폭 유발 프로세스 추적 +- **PC:** HANA-FINANCIAL- +- **시간:** 2026-09-13T08:47:08Z +- **프로세스:** `svchost#80` (PID: 9624) / 트래픽: 1.1 MB/s + - 📁 네트워크 연결 없음 (로컬 파일스캔/디스크 I/O로 추정) + +### ⚠️ 커널 오버헤드 (EDR/백신 필터) 감지 +- **PC:** HANA-FINANCIAL- +- **시간:** 2026-09-13T08:44:59Z +- **시스템 전체 DPC/Interrupt 오버헤드:** 3.4% + - 🔍 **주범 프로세스:** `agy` (PID: 23000) 가 커널 CPU의 72.0% 를 점유! + - 🔍 **주범 프로세스:** `WmiPrvSE#2` (PID: 2188) 가 커널 CPU의 10.0% 를 점유! + - 🔍 **주범 프로세스:** `Taskmgr` (PID: 4632) 가 커널 CPU의 20.0% 를 점유! + - 🔍 **주범 프로세스:** `msedge#5` (PID: 23976) 가 커널 CPU의 5.0% 를 점유! + +### 🌐 과다 대역폭 유발 프로세스 추적 +- **PC:** HANA-FINANCIAL- +- **시간:** 2026-09-13T08:43:40Z +- **프로세스:** `svchost#80` (PID: 9624) / 트래픽: 0.6 MB/s + - 📁 네트워크 연결 없음 (로컬 파일스캔/디스크 I/O로 추정) + +## 2. ETW (.etl) 커널 덤프 정밀 분석 결과 + +*현재 실행 경로 및 `C:\temp\` 에 분석할 `.etl` 커널 덤프 파일이 발견되지 않았습니다.* diff --git a/analyzer/VDI_Analysis_Report_20260913_180402.md b/analyzer/VDI_Analysis_Report_20260913_180402.md new file mode 100644 index 0000000..bb9579d --- /dev/null +++ b/analyzer/VDI_Analysis_Report_20260913_180402.md @@ -0,0 +1,76 @@ +# VDI 특화 성능 및 장애 원인 분석 리포트 + +**작성 일시:** 2026-09-13 18:04:02 + +## 1. 🎯 VDI 체감 렉(Lag) 유발 핵심 주범 요약 +> **분석 기준:** 이 통계는 단순 리소스 점유율을 넘어, **VDI 화면 전송 프로토콜을 지연시키는 '네트워크 대역폭 한계 도달(5MB/s 초과)'** 및 **VDI OS의 마우스/키보드 프리징을 유발하는 '커널 인터럽트 지연'** 시점을 '실제 장애'로 규정하여 분석한 인과관계 결과입니다. + +- 분석 기간 내 VDI 렉/장애 유발 순간: **총 8 회** + +### 🛑 [프리징 원인] 장애 시점 커널(EDR/백신) 병목 프로세스 +- `Taskmgr` (VDI 멈춤 유발 횟수: 1회) +- `msedge#5` (VDI 멈춤 유발 횟수: 1회) +- `agy` (VDI 멈춤 유발 횟수: 1회) +- `WmiPrvSE#2` (VDI 멈춤 유발 횟수: 1회) + +### 🛑 [화면 끊김 원인] 장애 시점 트래픽 폭주 유발 프로세스 +- `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (VDI 네트워크 포화 유발 횟수: 5회) +- `svchost#80` (VDI 네트워크 포화 유발 횟수: 3회) + +### 🔗 [대역폭 도둑] VDI 렉 유발 핵심 통신 목적지 IP +- 해당 원인 없음 + +### 🔴 만성적 응답 없음(Hang) 발생 애플리케이션 +- 해당 원인 없음 + +--- +## 2. 상세 이상 징후 발생 이력 + +### 🌐 [VDI 세션 지연/끊김 유발] 대역폭 포화 상태 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:53:40Z +- **트래픽 점유 프로세스:** `svchost#80` (PID: 9624) / 트래픽: 5.0 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +### 🌐 [VDI 세션 지연/끊김 유발] 대역폭 포화 상태 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:51:30Z +- **트래픽 점유 프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 5.6 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +### 🌐 [VDI 세션 지연/끊김 유발] 대역폭 포화 상태 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:50:25Z +- **트래픽 점유 프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 5.0 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +### 🌐 [VDI 세션 지연/끊김 유발] 대역폭 포화 상태 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:49:19Z +- **트래픽 점유 프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 4.4 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +### 🌐 [VDI 세션 지연/끊김 유발] 대역폭 포화 상태 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:48:13Z +- **트래픽 점유 프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 6.6 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +### 🌐 [VDI 세션 지연/끊김 유발] 대역폭 포화 상태 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:47:08Z +- **트래픽 점유 프로세스:** `AntigravitySetup-stable-ecfbad74d93962fc8ca485d93ab9b4f3d4cb6cf8#1` (PID: 32192) / 트래픽: 7.7 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +- **트래픽 점유 프로세스:** `svchost#80` (PID: 9624) / 트래픽: 1.1 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +### ⚠️ [VDI 렌더링/프리징 유발] 커널 오버헤드 감지 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:44:59Z | **오버헤드:** 3.4% + - 🔍 **주범:** `agy` (PID: 23000) / 커널 점유 72.0% + - 🔍 **주범:** `WmiPrvSE#2` (PID: 2188) / 커널 점유 10.0% + - 🔍 **주범:** `Taskmgr` (PID: 4632) / 커널 점유 20.0% + - 🔍 **주범:** `msedge#5` (PID: 23976) / 커널 점유 5.0% + +### 🌐 [VDI 세션 지연/끊김 유발] 대역폭 포화 상태 +- **PC:** HANA-FINANCIAL- | **시간:** 2026-09-13T08:43:40Z +- **트래픽 점유 프로세스:** `svchost#80` (PID: 9624) / 트래픽: 0.6 MB/s + - 📁 네트워크 연결 없음 (대용량 디스크 I/O로 인한 VDI 성능 저하) + +## 3. 🔬 ETW (.etl) 커널 덤프 정밀 분석 결과 + +*현재 실행 경로 및 `C:\temp\` 에 분석할 `.etl` 커널 덤프 파일이 발견되지 않았습니다.* diff --git a/analyzer/VDI_Analysis_Report_20260913_180845.docx b/analyzer/VDI_Analysis_Report_20260913_180845.docx new file mode 100644 index 0000000..77575dd Binary files /dev/null and b/analyzer/VDI_Analysis_Report_20260913_180845.docx differ diff --git a/analyzer/config.json b/analyzer/config.json new file mode 100644 index 0000000..d8014e6 --- /dev/null +++ b/analyzer/config.json @@ -0,0 +1,7 @@ +{ + "db_host": "localhost", + "db_port": 5433, + "db_user": "monitor", + "db_password": "monitorpassword", + "db_name": "vdimonitor" +} diff --git a/analyzer/go.mod b/analyzer/go.mod new file mode 100644 index 0000000..6c7d4b6 --- /dev/null +++ b/analyzer/go.mod @@ -0,0 +1,7 @@ +module vdi-analyzer + +go 1.21 + +require github.com/lib/pq v1.10.9 + +require github.com/gingfrederik/docx v0.0.1 // indirect diff --git a/analyzer/go.sum b/analyzer/go.sum new file mode 100644 index 0000000..4bf05c8 --- /dev/null +++ b/analyzer/go.sum @@ -0,0 +1,4 @@ +github.com/gingfrederik/docx v0.0.1 h1:XciAehRNcFThJnH1ESfOb7amAYk6IGkvFHtVyTNn0oM= +github.com/gingfrederik/docx v0.0.1/go.mod h1:0+v8qYUEEQr66ZKvnQKVhrZBX59pG1MSsQpTYSYOC0A= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/analyzer/main.go b/analyzer/main.go new file mode 100644 index 0000000..e86d171 --- /dev/null +++ b/analyzer/main.go @@ -0,0 +1,430 @@ +package main + +import ( + "database/sql" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "sort" + "strings" + "time" + + "github.com/gingfrederik/docx" + _ "github.com/lib/pq" +) + +type Config struct { + DBHost string `json:"db_host"` + DBPort int `json:"db_port"` + DBUser string `json:"db_user"` + DBPassword string `json:"db_password"` + DBName string `json:"db_name"` +} + +type MetricRecord struct { + Hostname string + Timestamp time.Time + TotalCpu float64 + TotalMem float64 + DpcInterrupt float64 + TopCpuProcesses string + TopIoProcesses string + NetworkConnections string + HungProcesses string +} + +type ProcessCPU struct { + ProcessName string `json:"ProcessName"` + PID int `json:"PID"` + CPUPercent float64 `json:"CPU_Percent"` + KernelCPUPercent float64 `json:"Kernel_CPU_Percent"` +} + +type ProcessIO struct { + ProcessName string `json:"ProcessName"` + PID int `json:"PID"` + IODataBytesPersec float64 `json:"IODataBytesPersec"` +} + +type NetConn struct { + ProcessName string `json:"ProcessName"` + PID int `json:"PID"` + RemoteAddress string `json:"RemoteAddress"` + RemotePort int `json:"RemotePort"` + IOBytesPerSec float64 `json:"IO_BytesPerSec"` +} + +type ProcessHung struct { + ProcessName string `json:"ProcessName"` + PID int `json:"PID"` +} + +type kv struct { + Key string + Value int +} +func sortMap(m map[string]int) []kv { + var ss []kv + for k, v := range m { ss = append(ss, kv{k, v}) } + sort.Slice(ss, func(i, j int) bool { return ss[i].Value > ss[j].Value }) + return ss +} + +func isIgnoredProcess(name string) bool { + name = strings.ToLower(name) + if idx := strings.Index(name, "#"); idx != -1 { name = name[:idx] } + ignoreList := map[string]bool{ + "idle": true, "_total": true, "taskmgr": true, + "wmiprvse": true, "powershell": true, "pwsh": true, "dwm": true, + } + return ignoreList[name] +} + +func isDevTool(name string) bool { + name = strings.ToLower(name) + if idx := strings.Index(name, "#"); idx != -1 { name = name[:idx] } + devTools := map[string]bool{ + "code": true, "eclipse": true, "idea64": true, "java": true, + "node": true, "msbuild": true, "docker": true, "git": true, + "devenv": true, "python": true, "python3": true, "goland": true, + } + return devTools[name] +} + +func isSecProc(name string) bool { + name = strings.ToLower(name) + if idx := strings.Index(name, "#"); idx != -1 { name = name[:idx] } + secTools := map[string]bool{ + "v3svc": true, "asdsvc": true, "v3main": true, "v3lite": true, + "privacyi": true, "piagent": true, "ngm": true, "corebguard": true, + "gncsensor": true, "gsagent": true, "gsprotect": true, "gsview": true, "gsflow": true, + } + return secTools[name] +} + +func isSecDriver(name string) bool { + name = strings.ToLower(name) + return strings.HasPrefix(name, "v3") || strings.Contains(name, "ahnlab") || strings.Contains(name, "asd") || + strings.Contains(name, "privacy") || strings.Contains(name, "piagent") || strings.Contains(name, "somansa") || strings.Contains(name, "ngm") || + strings.Contains(name, "gnc") || strings.Contains(name, "gsagent") || strings.Contains(name, "gsprotect") || strings.Contains(name, "gsflow") || strings.Contains(name, "genian") +} + +func getProcessTag(name string) string { + if isDevTool(name) { return " 💻[개발/빌드 도구]" } + if isSecProc(name) { return " 🛡️[보안 프로그램]" } + return "" +} + +type DetailRecord struct { + Category string + Content []string +} + +func main() { + etlPath := flag.String("etl", "", "분석할 .etl 파일 경로") + flag.Parse() + + configFile, err := ioutil.ReadFile("config.json") + if err != nil { log.Fatalf("config.json 읽기 실패: %v", err) } + var config Config + json.Unmarshal(configFile, &config) + + dbUrl := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", config.DBUser, config.DBPassword, config.DBHost, config.DBPort, config.DBName) + db, err := sql.Open("postgres", dbUrl) + if err != nil { log.Fatalf("DB 연결 실패: %v", err) } + defer db.Close() + + timestampStr := time.Now().Format("20060102_150405") + reportDocx := fmt.Sprintf("Report_%s.docx", timestampStr) + reportMd := fmt.Sprintf("Report_%s.md", timestampStr) + + f := docx.NewFile() + var md strings.Builder + + addH1 := func(text string) { + p := f.AddParagraph(); p.AddText(text).Size(20) + md.WriteString("# " + text + "\n\n") + } + addH2 := func(text string) { + p := f.AddParagraph(); p.AddText(text).Size(14) + md.WriteString("## " + text + "\n\n") + } + addText := func(text string) { + if text != "" { f.AddParagraph().AddText(text).Size(11) } else { f.AddParagraph() } + if text != "" { md.WriteString(text + "\n\n") } else { md.WriteString("\n") } + } + addBoldText := func(text string) { + p := f.AddParagraph(); p.AddText("▶ " + text).Size(11) + md.WriteString("**▶ " + text + "**\n\n") + } + addBullet := func(text string) { + f.AddParagraph().AddText(" • " + text).Size(11) + md.WriteString("- " + text + "\n") + } + addRedBullet := func(text string) { + p := f.AddParagraph(); p.AddText(" • " + text).Size(11).Color("FF0000") + md.WriteString("- 🔴 **" + text + "**\n") + } + + addH1("시스템 성능 및 장애 원인 분석 리포트") + addText(fmt.Sprintf("작성 일시: %s", time.Now().Format("2006-01-02 15:04:05"))) + addText("") + + devBuildCount := 0 + secInterferenceCount := 0 + + sysKernelHogs := make(map[string]int) + sysNetworkHogs := make(map[string]int) + sysNetworkHogsMaxIO := make(map[string]float64) + sysTargetIps := make(map[string]int) + sysTargetIpsMaxIO := make(map[string]float64) + hungCounts := make(map[string]int) + + var details []DetailRecord + + rows, err := db.Query(` + SELECT hostname, timestamp, total_cpu_percent, total_mem_percent, dpc_interrupt_percent, + COALESCE(top_cpu_processes::text, '[]'), + COALESCE(top_io_processes::text, '[]'), + COALESCE(network_connections::text, '[]'), + COALESCE(hung_processes::text, '[]') + FROM client_metrics + ORDER BY timestamp DESC LIMIT 10000 + `) + + if err == nil { + defer rows.Close() + for rows.Next() { + var r MetricRecord + err := rows.Scan(&r.Hostname, &r.Timestamp, &r.TotalCpu, &r.TotalMem, &r.DpcInterrupt, &r.TopCpuProcesses, &r.TopIoProcesses, &r.NetworkConnections, &r.HungProcesses) + if err != nil { continue } + + localTimeStr := r.Timestamp.Local().Format("2006-01-02 15:04:05") + + var cpuProcs []ProcessCPU + json.Unmarshal([]byte(r.TopCpuProcesses), &cpuProcs) + var ioProcs []ProcessIO + json.Unmarshal([]byte(r.TopIoProcesses), &ioProcs) + + // 🚨 [Smoking Gun 타겟팅] + isCompiling := false + for _, p := range ioProcs { + if isDevTool(p.ProcessName) && p.IODataBytesPersec > 1048576 { + isCompiling = true + break + } + } + + if isCompiling { + devBuildCount++ + secInterfered := false + if r.DpcInterrupt > 3.0 { secInterfered = true } + for _, cp := range cpuProcs { + if isSecProc(cp.ProcessName) && cp.KernelCPUPercent > 2.0 { secInterfered = true; break } + } + if secInterfered { secInterferenceCount++ } + } + + // 상세 내역 및 일반 요약 로직 + // 1. 커널 프리징 + if r.DpcInterrupt > 3.0 { + 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) { + sysKernelHogs[cp.ProcessName]++ + rec.Content = append(rec.Content, fmt.Sprintf("주범: %s%s (PID: %d) / 커널 점유 %.1f%%", cp.ProcessName, getProcessTag(cp.ProcessName), cp.PID, cp.KernelCPUPercent)) + } + } + if len(rec.Content) > 0 { details = append(details, rec) } + } + + // 2. 대역폭 포화 + isNetworkChoke := false + var netRec DetailRecord + for _, p := range ioProcs { + if p.IODataBytesPersec > 5242880 && !isIgnoredProcess(p.ProcessName) { + sysNetworkHogs[p.ProcessName]++ + mbps := p.IODataBytesPersec / 1048576.0 + if mbps > sysNetworkHogsMaxIO[p.ProcessName] { sysNetworkHogsMaxIO[p.ProcessName] = mbps } + + if !isNetworkChoke { + isNetworkChoke = true + netRec = DetailRecord{Category: fmt.Sprintf("[대역폭 포화 감지] 네트워크/디스크 지연 - 시간: %s | PC: %s", localTimeStr, r.Hostname)} + } + + var conns []NetConn + json.Unmarshal([]byte(r.NetworkConnections), &conns) + connStr := "목적지 연결 없음 (내부 대용량 파일 I/O)" + for _, c := range conns { + if c.ProcessName == p.ProcessName { + ipStr := fmt.Sprintf("%s:%d", c.RemoteAddress, c.RemotePort) + sysTargetIps[ipStr]++ + connMbps := c.IOBytesPerSec / 1048576.0 + if connMbps > sysTargetIpsMaxIO[ipStr] { sysTargetIpsMaxIO[ipStr] = connMbps } + + connStr = fmt.Sprintf("목적지 IP: %s (속도: %.1f MB/s)", ipStr, connMbps) + break + } + } + netRec.Content = append(netRec.Content, fmt.Sprintf("트래픽 점유: %s%s (PID: %d) / 총 %.1f MB/s -> %s", p.ProcessName, getProcessTag(p.ProcessName), p.PID, mbps, connStr)) + } + } + if isNetworkChoke { details = append(details, netRec) } + + // 3. 앱 Hang + var hung []ProcessHung + json.Unmarshal([]byte(r.HungProcesses), &hung) + 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]++ + hasValidHang = true + hangRec.Content = append(hangRec.Content, fmt.Sprintf("멈춤: %s%s (PID: %d)", h.ProcessName, getProcessTag(h.ProcessName), h.PID)) + } + } + if hasValidHang { details = append(details, hangRec) } + } + } + } + + // --- Smoking Gun Report Section --- + addH2("🚨 보안 솔루션 개발 환경 충돌 정밀 분석") + addText("본 섹션은 보안 프로그램(백신/EDR/DLP)이 개발자의 빌드/컴파일 작업에 미치는 실질적인 성능 저하 연관성을 교차 검증한 결과입니다.") + addText(fmt.Sprintf("분석 데이터 내 개발 도구(IDE, 컴파일러 등) 활성화 감지: 총 %d 회", devBuildCount)) + addBoldText(fmt.Sprintf("👉 위 빌드 작업 중, 보안 프로그램이 개입하여 커널 프리징/시스템 렉을 유발한 횟수: %d 회", secInterferenceCount)) + + if devBuildCount > 0 { + rate := (float64(secInterferenceCount) / float64(devBuildCount)) * 100 + addBoldText(fmt.Sprintf("🔥 보안 솔루션으로 인한 개발 업무 방해(병목) 확률: %.1f%%", rate)) + } + addText("") + + // --- General Report --- + addH2("1. 시스템 체감 렉(Lag) 유발 핵심 주범 요약") + + addBoldText("[프리징 원인] 장애 시점 커널(EDR/백신) 병목 프로세스") + for i, kv := range sortMap(sysKernelHogs) { + if i >= 5 { break } + addBullet(fmt.Sprintf("%s%s (멈춤 유발 횟수: %d회)", kv.Key, getProcessTag(kv.Key), kv.Value)) + } + if len(sysKernelHogs) == 0 { addBullet("해당 원인 없음") } + addText("") + + addBoldText("[지연 원인] 장애 시점 트래픽 폭주 유발 프로세스") + for i, kv := range sortMap(sysNetworkHogs) { + if i >= 5 { break } + maxIo := sysNetworkHogsMaxIO[kv.Key] + addBullet(fmt.Sprintf("%s%s (포화 유발: %d회 | 최고 I/O 속도: %.1f MB/s)", kv.Key, getProcessTag(kv.Key), kv.Value, maxIo)) + } + if len(sysNetworkHogs) == 0 { addBullet("해당 원인 없음") } + addText("") + + addBoldText("[대역폭 도둑] 렉 유발 핵심 통신 목적지 IP") + for i, kv := range sortMap(sysTargetIps) { + if i >= 5 { break } + maxIo := sysTargetIpsMaxIO[kv.Key] + addBullet(fmt.Sprintf("%s (접속 빈도: %d회 | 최고 트래픽: %.1f MB/s)", kv.Key, kv.Value, maxIo)) + } + if len(sysTargetIps) == 0 { addBullet("해당 원인 없음") } + addText("") + + addBoldText("만성적 응답 없음(Hang) 발생 애플리케이션") + for i, kv := range sortMap(hungCounts) { + if i >= 5 { break } + addBullet(fmt.Sprintf("%s%s (빈도: %d회)", kv.Key, getProcessTag(kv.Key), kv.Value)) + } + if len(hungCounts) == 0 { addBullet("해당 원인 없음") } + addText("") + + // --- Detailed Logs --- + addH2("2. 시간대별 상세 이상 징후 발생 이력 (Chronological Log)") + if len(details) == 0 { + addText("기록된 이상 징후가 없습니다.") + addText("") + } else { + limit := len(details) + if limit > 100 { limit = 100 } // 너무 길어지는 것 방지 + for i := 0; i < limit; i++ { + d := details[i] + addBoldText(d.Category) + for _, c := range d.Content { addBullet(c) } + addText("") + } + if len(details) > 100 { + addText(fmt.Sprintf("... (생략됨: 총 %d건의 이벤트 중 최신 100건만 출력)", len(details))) + } + } + + addH2("3. ETW (.etl) 커널 덤프 정밀 분석 결과") + var etlFiles []string + if *etlPath != "" { etlFiles = append(etlFiles, *etlPath) + } else { + localFiles, _ := filepath.Glob("*.etl") + etlFiles = append(etlFiles, localFiles...) + tempFiles, _ := filepath.Glob("C:\\temp\\*.etl") + etlFiles = append(etlFiles, tempFiles...) + } + + if len(etlFiles) > 0 { + for _, file := range etlFiles { + sysMap := parseETL(file) + addBoldText(fmt.Sprintf("📄 분석 파일: %s", filepath.Base(file))) + if len(sysMap) > 0 { + addText("커널 덤프 파일 내에서 가장 많은 인터럽트 및 파일 검사를 유발한 서드파티 커널 드라이버(.sys) 랭킹입니다.") + for i, kv := range sortMap(sysMap) { + if i >= 10 { break } + if isSecDriver(kv.Key) { + addRedBullet(fmt.Sprintf("%d위: %s (빈도: %d) 🚨[보안 솔루션 커널 드라이버 적발]", i+1, kv.Key, kv.Value)) + } else { + addBullet(fmt.Sprintf("%d위: %s (빈도: %d)", i+1, kv.Key, kv.Value)) + } + } + addText("") + } else { + addText("서드파티 드라이버 정보를 추출하지 못했습니다.") + } + } + } else { + addText("분석할 .etl 커널 덤프 파일이 없습니다.") + } + + err = f.Save(reportDocx) + if err != nil { log.Fatalf("DOCX 파일 저장 실패: %v", err) } + + err = ioutil.WriteFile(reportMd, []byte(md.String()), 0644) + if err != nil { log.Fatalf("MD 파일 저장 실패: %v", err) } + + fmt.Printf("==================================================\n") + fmt.Printf(" 분석 완료: '%s' 및 '%s' 2종류의 문서가 생성되었습니다.\n", reportDocx, reportMd) + fmt.Printf("==================================================\n") +} + +func parseETL(etlPath string) map[string]int { + sysCounts := make(map[string]int) + dumpFile := "etl_dump.xml" + cmd := exec.Command("tracerpt.exe", etlPath, "-o", dumpFile, "-of", "XML", "-y") + cmd.Run() + data, err := ioutil.ReadFile(dumpFile) + if err != nil { return sysCounts } + re := regexp.MustCompile(`(?i)([a-zA-Z0-9_-]+\.sys)`) + matches := re.FindAllString(string(data), -1) + ignoreList := map[string]bool{ + "ntoskrnl.sys": true, "ndis.sys": true, "tcpip.sys": true, "fltmgr.sys": true, + "wof.sys": true, "ntfs.sys": true, "dxgkrnl.sys": true, "netbt.sys": true, + } + for _, match := range matches { + match = strings.ToLower(match) + if !ignoreList[match] { sysCounts[match]++ } + } + os.Remove(dumpFile) + os.Remove("summary.txt") + return sysCounts +} diff --git a/analyzer/vdi-analyzer.exe b/analyzer/vdi-analyzer.exe new file mode 100644 index 0000000..cd06d3c Binary files /dev/null and b/analyzer/vdi-analyzer.exe differ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..39d8f11 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.21-alpine AS builder + +WORKDIR /app +COPY . . + +# Initialize and download dependencies +RUN go mod tidy + +# Build the Go app +RUN CGO_ENABLED=0 GOOS=linux go build -o backend main.go + +FROM alpine:latest +WORKDIR /root/ +COPY --from=builder /app/backend . +EXPOSE 8080 +CMD ["./backend"] diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..6d7d328 --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,5 @@ +module vdimonitor + +go 1.21 + +require github.com/lib/pq v1.10.9 diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..cd62b38 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "database/sql" + "encoding/json" + "log" + "net/http" + "os" + + _ "github.com/lib/pq" +) + +type MetricPayload struct { + Hostname string `json:"hostname"` + TotalCpuPercent float64 `json:"total_cpu_percent"` + TotalMemPercent float64 `json:"total_mem_percent"` + DpcInterruptPercent float64 `json:"dpc_interrupt_percent"` + TopCpuProcesses json.RawMessage `json:"top_cpu_processes"` + TopMemProcesses json.RawMessage `json:"top_mem_processes"` + TopIoProcesses json.RawMessage `json:"top_io_processes"` + NetworkConnections json.RawMessage `json:"network_connections"` + HungProcesses json.RawMessage `json:"hung_processes"` +} + +var db *sql.DB + +func main() { + dbUrl := os.Getenv("DB_URL") + if dbUrl == "" { + dbUrl = "postgres://monitor:monitorpassword@localhost:5432/vdimonitor?sslmode=disable" + } + + var err error + db, err = sql.Open("postgres", dbUrl) + if err != nil { + log.Fatalf("Failed to connect to db: %v", err) + } + defer db.Close() + + http.HandleFunc("/api/metrics", handleMetrics) + + log.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +func handleMetrics(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var payload MetricPayload + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + + _, err := db.Exec( + `INSERT INTO client_metrics + (hostname, total_cpu_percent, total_mem_percent, dpc_interrupt_percent, top_cpu_processes, top_mem_processes, top_io_processes, network_connections, hung_processes) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, + payload.Hostname, payload.TotalCpuPercent, payload.TotalMemPercent, payload.DpcInterruptPercent, + payload.TopCpuProcesses, payload.TopMemProcesses, payload.TopIoProcesses, payload.NetworkConnections, payload.HungProcesses, + ) + if err != nil { + log.Printf("DB insert error: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusCreated) +} diff --git a/client/monitor.ps1 b/client/monitor.ps1 new file mode 100644 index 0000000..3a35ba6 --- /dev/null +++ b/client/monitor.ps1 @@ -0,0 +1,104 @@ +# VDI Performance, EDR Overhead & Hang Monitor +$BackendUrl = "http://127.0.0.1:8081/api/metrics" # 서버 IP로 변경 +$Hostname = $env:COMPUTERNAME + +$StartTime = Get-Date +$EndTime = $StartTime.AddHours(14) +Write-Host "Monitoring started for $Hostname. Will auto-terminate at $EndTime." + +$EtwCooldown = $null +rm "monitor.ps1" +while ((Get-Date) -lt $EndTime) { + try { + $ShouldSend = $false + + # 2. CPU & 커널 레벨(DPC/Interrupt) + $CpuTotal = Get-WmiObject Win32_Processor | Measure-Object -Property LoadPercentage -Average | Select-Object -ExpandProperty Average + if ($null -eq $CpuTotal) { $CpuTotal = 0 } + if ($CpuTotal -gt 70) { $ShouldSend = $true } # CPU 70% 조건 + + $DpcCounter = Get-Counter '\Processor(_Total)\% DPC Time' -ErrorAction SilentlyContinue + $IntCounter = Get-Counter '\Processor(_Total)\% Interrupt Time' -ErrorAction SilentlyContinue + $DpcInterrupt = 0 + if ($DpcCounter -and $IntCounter) { + $DpcInterrupt = [math]::Round($DpcCounter.CounterSamples.CookedValue + $IntCounter.CounterSamples.CookedValue, 2) + } + if ($DpcInterrupt -gt 3.0) { $ShouldSend = $true } # 커널 부하 3% 조건 + + # 3. 메모리 + $Mem = Get-WmiObject Win32_OperatingSystem + $MemUsage = [math]::Round((($Mem.TotalVisibleMemorySize - $Mem.FreePhysicalMemory) / $Mem.TotalVisibleMemorySize) * 100, 2) + if ($MemUsage -gt 80) { $ShouldSend = $true } # 메모리 80% 조건 + + # 4. 프로세스 성능 (관측용 시스템 프로세스 제외 - Observer Effect 방지 및 #1, #2 인스턴스 꼬리표 처리) + $IgnoreRegex = "^(_total|idle|taskmgr|wmiprvse|powershell|pwsh|dwm)(#\d+)?$" + $ProcessPerf = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -notmatch $IgnoreRegex } + + $TopCpuRaw = $ProcessPerf | Sort-Object PercentProcessorTime -Descending | Select-Object -First 5 + $TopIoRaw = $ProcessPerf | Sort-Object IODataBytesPersec -Descending | Select-Object -First 5 + + # [물증 확보] 3대 보안 솔루션(V3, 소만사, 지니언스)은 순위와 상관없이 자원 사용 시 강제 수집 + $SecRegex = "^(v3svc|asdsvc|v3main|v3lite|privacyi|piagent|ngm|corebguard|gncsensor|gsagent|gsprotect|gsview|gsflow)" + $SecCpuRaw = $ProcessPerf | Where-Object { $_.Name -match $SecRegex -and $_.PercentProcessorTime -gt 0 } + $SecIoRaw = $ProcessPerf | Where-Object { $_.Name -match $SecRegex -and $_.IODataBytesPersec -gt 0 } + + $TopCpu = $TopCpuRaw + $SecCpuRaw | Sort-Object IDProcess -Unique | Select-Object @{Name="ProcessName";Expression={$_.Name}}, @{Name="PID";Expression={$_.IDProcess}}, @{Name="CPU_Percent";Expression={$_.PercentProcessorTime}}, @{Name="Kernel_CPU_Percent";Expression={$_.PercentPrivilegedTime}} + $TopIo = $TopIoRaw + $SecIoRaw | Sort-Object IDProcess -Unique | Select-Object @{Name="ProcessName";Expression={$_.Name}}, @{Name="PID";Expression={$_.IDProcess}}, IODataBytesPersec + + if ($TopIo[0].IODataBytesPersec -gt 5242880) { $ShouldSend = $true } # I/O 5MB/s 조건 + + $TopMem = Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 5 | Select-Object @{Name="ProcessName";Expression={$_.Name}}, @{Name="PID";Expression={$_.Id}}, @{Name="WorkingSetMB";Expression={[math]::Round($_.WorkingSet / 1MB, 2)}} + + # 5. Hang 프로세스 + $HungProcs = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 -and $_.Responding -eq $false -and $_.Name -notmatch $IgnoreRegex } | Select-Object @{Name="ProcessName";Expression={$_.Name}}, @{Name="PID";Expression={$_.Id}} + if ($null -eq $HungProcs) { $HungProcs = @() } + if ($HungProcs.Count -gt 0) { $ShouldSend = $true } + + # 6. ETW 자동 트리거 (커널 병목 5% 초과 발생 시) + if ($DpcInterrupt -gt 5.0) { + if ($null -eq $EtwCooldown -or (Get-Date) -gt $EtwCooldown) { + if (-not (Test-Path "C:\temp")) { New-Item -ItemType Directory -Force -Path "C:\temp" | Out-Null } + Start-Process -FilePath "wpr.exe" -ArgumentList "-start GeneralProfile" -WindowStyle Hidden -Wait + Start-Sleep -Seconds 10 + + $Timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss") + $EtlPath = "C:\temp\Trace_$Timestamp.etl" + Start-Process -FilePath "wpr.exe" -ArgumentList "-stop $EtlPath" -WindowStyle Hidden -Wait + Write-Host "ETW Trace saved to $EtlPath" + + $EtwCooldown = (Get-Date).AddMinutes(15) + } + } + + # 7. 이상 데이터 전송 + if ($ShouldSend) { + # 프로세스별 I/O (네트워크+디스크) 해시테이블 생성 (빠른 매핑용) + $IoDict = @{} + foreach ($p in $ProcessPerf) { $IoDict[$p.IDProcess] = $p.IODataBytesPersec } + + # 네트워크 연결 목록에 프로세스별 총 I/O 대역폭 병합 후 부하가 큰 순으로 정렬 + $NetConnections = Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue | + Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, + @{Name="ProcessName";Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name}}, + @{Name="PID";Expression={$_.OwningProcess}}, + @{Name="IO_BytesPerSec";Expression={ if ($IoDict.ContainsKey($_.OwningProcess)) { $IoDict[$_.OwningProcess] } else { 0 } }} | + Where-Object ProcessName -ne $null | Sort-Object IO_BytesPerSec -Descending + + $Payload = @{ + hostname = $Hostname + total_cpu_percent = $CpuTotal + total_mem_percent = $MemUsage + dpc_interrupt_percent = $DpcInterrupt + top_cpu_processes = $TopCpu + top_mem_processes = $TopMem + top_io_processes = $TopIo + network_connections = $NetConnections + hung_processes = $HungProcs + } | ConvertTo-Json -Depth 4 + + Invoke-RestMethod -Uri $BackendUrl -Method Post -Body $Payload -ContentType "application/json" -ErrorAction SilentlyContinue + } + } catch { } + + Start-Sleep -Seconds 60 +} diff --git a/db/init.sql b/db/init.sql new file mode 100644 index 0000000..421fae6 --- /dev/null +++ b/db/init.sql @@ -0,0 +1,14 @@ +DROP TABLE IF EXISTS client_metrics; +CREATE TABLE client_metrics ( + id SERIAL PRIMARY KEY, + hostname VARCHAR(255) NOT NULL, + timestamp TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + total_cpu_percent FLOAT, + total_mem_percent FLOAT, + dpc_interrupt_percent FLOAT, + top_cpu_processes JSONB, + top_mem_processes JSONB, + top_io_processes JSONB, + network_connections JSONB, + hung_processes JSONB +); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..064f77a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + db: + image: postgres:15 + environment: + POSTGRES_USER: monitor + POSTGRES_PASSWORD: monitorpassword + POSTGRES_DB: vdimonitor + ports: + - "5433:5432" + volumes: + - pgdata:/var/lib/postgresql/data + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql + + backend: + image: vdimonitor-backend:latest + ports: + - "8081:8080" + environment: + DB_URL: postgres://monitor:monitorpassword@db:5432/vdimonitor?sslmode=disable + depends_on: + - db + +volumes: + pgdata: diff --git a/postgres15.tar b/postgres15.tar new file mode 100644 index 0000000..85fed84 Binary files /dev/null and b/postgres15.tar differ diff --git a/read.md b/read.md new file mode 100644 index 0000000..f41696d --- /dev/null +++ b/read.md @@ -0,0 +1,6 @@ +# 1. 도커 이미지 로드 +docker load -i postgres15.tar +docker load -i vdimonitor-backend.tar + +# 2. 컨테이너 띄우기 +docker-compose up -d \ No newline at end of file diff --git a/vdimonitor-backend.tar b/vdimonitor-backend.tar new file mode 100644 index 0000000..6e5a808 Binary files /dev/null and b/vdimonitor-backend.tar differ