При работе пользователей в терминальном режиме (RDP) нередко возникает ситуация, когда клиентский процесс 1cv8.exe внезапно начинает непрерывно потреблять 100% одного процессорного ядра (или эквивалентную долю от общей мощности многоядерного сервера). При этом интерфейс программы может продолжать откликаться, либо окно зависает, а закрытие всех вкладок внутри сеанса не снимает аномальную нагрузку. Разберем основные причины такого поведения, способы точной диагностики и готовые сценарии автоматического реагирования.
Выясним, какие факторы приводят к непрерывной загрузке процессора потоком клиентского приложения 1С:
rtl70.bpl) способно вызывать бесконечный цикл ожидания событий в фоновом потоке клиентского приложения.ПодключитьОбработчикОжидания (или ОбработкаОжидания) с минимальным интервалом вызова для выполнения тяжелых алгоритмов, проверки оповещений или фонового обновления данных.ПриВыводеСтроки и ПриПолученииДанных приводит к лавинообразному потреблению ресурсов процессора при автообновлении или прокрутке.Для выявления конкретной процедуры или сторонней библиотеки, вызвавшей зависание, выполним следующие шаги:
1cv8.exe, переходим на вкладку потоков и определяем поток, потребляющий процессорное время. Просмотр стека вызовов (Call Stack) покажет, в какой именно библиотеке (библиотека платформы, драйвер оборудования или модуль печати) выполняется непрерывный цикл.logcfg.xml в папке conf клиентской платформы на терминальном сервере, настроив сбор событий CALL и EXCP с фильтром по длительности для фиксации последней выполненной клиентской операции.Если проблему вызывает внешняя компонента или периодический сбой, который сложно исправить мгновенно, организуем автоматический контроль за сессиями пользователей на терминальном сервере. Реализуем сценарий, который рассчитывает нагрузку на ядро процессора каждым процессом 1cv8.exe, предупреждает пользователя и при отсутствии реакции завершает зависший процесс.
Создадим основной скрипт 1cControl.ps1:
# 1cControl.ps1 - Мониторинг зависших процессов 1C
$core = Get-WmiObject -Class Win32_ComputerSystem | Select-Object NumberOfLogicalProcessors
$logging = $true
$screen = $false
$procname = "1cv8"
$logfile = ".\1cControl.log"
$ExecuteTimer = 60 # Время задержки перед принудительным завершением (в секундах)
$CollectTimer = 20 # Время замера интервала загрузки процессора (в секундах)
$data_start = Get-Date
if ($logging -eq $true) {
$data_start | Out-File -FilePath $logfile -Append
"Скрипт мониторинга запущен." | Out-File -FilePath $logfile -Append
}
# Получение списка владельцев процессов
$owners = @{}
Get-WmiObject Win32_Process -Filter "name='$procname.exe'" | ForEach-Object {
$owners[$_.handle] = $_.GetOwner().User
}
# Первый замер процессорного времени
$data_start = Get-Date
[System.Collections.ArrayList]$process_start = @(Get-Process $procname | Select-Object TotalProcessorTime, Id, PrivateMemorySize, @{Name="Owner"; Expression={$owners[$_.Id.ToString()]}})
# Интервал ожидания для оценки динамики
Start-Sleep -Seconds $CollectTimer
# Второй замер процессорного времени
$data_stop = Get-Date
[System.Collections.ArrayList]$process_stop = @(Get-Process $procname | Select-Object TotalProcessorTime, Id, PrivateMemorySize)
$Data_delta = $data_stop.Ticks - $data_start.Ticks
$msg = "Ваш сеанс 1С перегружает процессор. Пожалуйста, сохраните результаты работы и перезапустите программу, иначе сеанс будет завершен через 1 минуту."
$sign = "Автоматический мониторинг производительности."
$lstop = $process_stop.Count
for ($i = 0; $i -lt $lstop; $i++) {
$proc = $process_stop[$i]
$lstart = $process_start.Count
for ($j = 0; $j -lt $lstart; $j++) {
$procs = $process_start[$j]
if ($proc.Id -eq $procs.Id) {
if ($proc.PrivateMemorySize -eq $procs.PrivateMemorySize) {
$CPU_delta = $proc.TotalProcessorTime.Ticks - $procs.TotalProcessorTime.Ticks
if ($Data_delta -gt 0 -and $CPU_delta -gt 0) {
# Расчет процента загрузки относительно общего числа ядер
$CPU_Load = ($CPU_delta * 100) / ($Data_delta * $core.NumberOfLogicalProcessors)
# Проверка загрузки эквивалента одного полного ядра
if ($CPU_Load -gt ((100 / $core.NumberOfLogicalProcessors) - 0.5)) {
c:\windows\system32\msg.exe $procs.Owner $msg $sign
$id = $procs.Id
$powershellArguments = "-file .\killWithTimer.ps1", $id, $ExecuteTimer
Start-Process powershell.exe -ArgumentList $powershellArguments
if ($logging -eq $true) {
"Запущен таймер завершения для процесса пользователя " + $procs.Owner | Out-File -FilePath $logfile -Append
}
}
}
}
$process_start.RemoveRange($j, 1)
break
}
}
}
Рядом разместим вспомогательный скрипт killWithTimer.ps1, выполняющий завершение процесса после истечения заданного интервала ожидания:
# killWithTimer.ps1
param(
[int]$ProcessId = $args[0],
[int]$DelaySeconds = $args[1]
)
Start-Sleep -Seconds $DelaySeconds
Stop-Process -Id $ProcessId -Force
ПриВыводеСтроки в обычных формах и исключим из них обращение к тяжелым функциям и серверным контекстам.ОтключитьОбработчикОжидания для всех ранее запущенных таймеров.