# 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 }