Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 커널 덤프 파일이 없습니다.
|
||||
|
||||
@@ -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` 커널 덤프 파일이 발견되지 않았습니다.*
|
||||
@@ -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` 커널 덤프 파일이 발견되지 않았습니다.*
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"db_host": "localhost",
|
||||
"db_port": 5433,
|
||||
"db_user": "monitor",
|
||||
"db_password": "monitorpassword",
|
||||
"db_name": "vdimonitor"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
Binary file not shown.
@@ -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"]
|
||||
@@ -0,0 +1,5 @@
|
||||
module vdimonitor
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/lib/pq v1.10.9
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+14
@@ -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
|
||||
);
|
||||
@@ -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:
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
# 1. 도커 이미지 로드
|
||||
docker load -i postgres15.tar
|
||||
docker load -i vdimonitor-backend.tar
|
||||
|
||||
# 2. 컨테이너 띄우기
|
||||
docker-compose up -d
|
||||
Binary file not shown.
Reference in New Issue
Block a user