Files
vdi-monitor/client/monitor.ps1
T

105 lines
6.3 KiB
PowerShell
Raw Normal View History

2026-09-13 19:19:37 +09:00
# 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
}