О технологии RemoteFX от Майкрософт, которая повышает качество работы в режиме удалённого рабочего стола, известно давно. В интернете хватает материалов, демонстрирующих её эффективность. Но большинство оценок носят качественный характер: "вот играем в %game_name%, fps в норме", "вот запустили 3D софт, как будто локально работает! Скриншот здесь, видео там".
В данном исследовании мы разберёмся, как перейти от "попугаев" к конкретным метрикам, чтобы количественно оценить эффективность технологии и объективно сравнить её использование с обычным режимом RDP.
Содержание
Кратко о RemoteFX
Конфигурация тестовой среды
Сервер
Клиент
Выбор показателей для измерений
Методика тестирования
Тест #1: ввод текста
Тест #2: ввод текста + 3D BenchMark
Тест #3: ввод текста + просмотр локальных видеофайлов
Тест #4: ввод текста + просмотр youtube-ролика
Обработка данных и построение графиков
Анализ результатов и наиболее интересные графики
Задержки при обработке ввода от пользователя
Частота кадров
Общие сетевые метрики
Загрузка центрального процессора
Загрузка видеокарты
Выводы
Что дальше
Список источников
Приложения
Переопределение групп сбора данных: _1_task_define.cmd
Принудительная остановка записи данных: _1_task_breake.cmd
Конвертация двоичных данных в CSV: blg2csv.cmd
Нормализация заголовков CSV: blg2csv.ps1
Jupiter Notebook: импорт данных
Jupiter Notebook: отрисовка одной метрики на диаграмму
Jupiter Notebook: отрисовка всех метрик на одной диаграмме
Диаграмма со всеми графиками
Кратко о RemoteFX
Традиционный RDP работает так, чтобы как можно больше работы по отрисовке окна удалённого рабочего стола переложить на клиента: по сети передаются графические примитивы и инструкции, которые должна выполнить клиентская видеокарта. Такой подход, в случае показа видео или использования интерфейса Windows Aero, требует поддержки со стороны клиента. Иначе вместо Aero будет использована упрощённая схема, а обработанный CPU сервера видеопоток передан клиенту в виде растровой графики, производительность отрисовки при этом может оказаться просто неприемлемой. Поэтому приходилось выбирать: либо использовать традиционный RDP только в связке с достаточно производительным клиентским железом, либо отказываться от сложной графики.
При включении RemoteFX клиенту по сети по прежнему передаются растровые кадры. Но есть два существенных отличия от традиционного RDP. Во-первых, вся подготовка и обработка графики перекладывается на GPU сервера, это происходит намного быстрее и разгружает CPU. А во-вторых, используется сжатие кадра, которое выполняет кодек RemoteFX. Это существенно снижает объём передаваемых по сети данных, тем самым разгружая канал связи. Это существенно снижает требования к клиентскому железу, но сохраняет достаточный уровень отрисовки и отзывчивости удалённого рабочего стола.
Конфигурация тестовой среды
Сервер
2 vCPU Intel(R) Xeon(R) CPU E5-2696 v4 @ 2.20GHz
8 GB RAM
GPU NVIDIA GRID M60-1Qб, Dedicated Memory 929 MB, Shared Memory 4095 MB
гостевая ОС Windows Server 2019 Standart x64 1809 (Version 10.0.17763.1577), DirectX 12
network in/out rate limit 50 Mbps
Клиент
Intel(R) Core(TM) i5-7600K CPU @ 3.80GHz, 3801 МГц, ядер: 4, логических процессоров: 4
16 GB RAM
network in/out rate limit 100 Mbps
OS Windows 10 Pro 2004 (Version 10.0.19041.685), DirectX 12
настройки RDP-сеанса
1920 x 1080 (32 bit) (32Hz)
на вкладке "Взаимодействие" выбрано "Локальная сеть (10 Мбит/с и выше)"
Выбор показателей для измерений
Качество и производительность удалённого рабочего стола нужно оценить с точки зрения как пользователя, так и потребления облачных ресурсов. Будем собирать данные о частоте кадров отрисовки, задержках отклика на ввод данных, сетевом трафике и загрузке CPU/GPU/RAM. Метрики выбраны с учётом официальных рекомендаций по диагностике.
Замеры выполним с помощью стандартного средства Системный монитор
. Для этого на каждый тест определим свой сборщик данных, который в течении двух минут будет каждую секунду фиксировать в двоичном blg-файле журнала показания счётчиков:
Показания
\Графика RemoteFX(*)\Качество кадра
\Графика RemoteFX(*)\Исходящих кадров в секунду
\Графика RemoteFX(*)\Входящих кадров в секунду
\Графика RemoteFX(*)\Среднее время кодирования
\Графика RemoteFX(*)\Коэффициент сжатия графических данных
\Графика RemoteFX(*)\Пропущено кадров в секунду — у сервера недостаточно ресурсов
\Графика RemoteFX(*)\Пропущено кадров в секунду — недостаточно сетевых ресурсов
\Графика RemoteFX(*)\Пропущено кадров в секунду — у клиента недостаточно ресурсов
\Задержка ввода данных пользователем на сеанс(Max)\Максимальная задержка ввода
\Сведения о процессоре(_Total)\% загруженности процессора
\NVIDIA GPU(*)\% Video Decoder Usage
\NVIDIA GPU(*)\% Video Encoder Usage
\NVIDIA GPU(*)\% GPU Memory Usage
\NVIDIA GPU(*)\% GPU Usage
\NVIDIA GPU(*)\% FB Usage
\Сеть RemoteFX(*)\Потери
\Сеть RemoteFX(*)\Общая скорость отправки
\Сеть RemoteFX(*)\Общая скорость приема
\Сеть RemoteFX(*)\Скорость отправки TCP-данных
\Сеть RemoteFX(*)\Скорость отправки UDP-данных
\Сеть RemoteFX(*)\Общая скорость приема
\Сеть RemoteFX(*)\Скорость получения TCP-данных
\Сеть RemoteFX(*)\Скорость получения UDP-данных
\Сеть RemoteFX(*)\Пропускная способность текущего TCP-подключения
\Сеть RemoteFX(*)\Пропускная способность текущего UDP-подключения
\Память\% использования выделенной памяти
\Память\Доступно байт
PS. По непонятной причине данные по оперативной памяти не сохранялись в журнал при выполнении задания группы сбора, хотя "вживую" Системный монитор
нормально строил графики по обоим показателям. Сброс кэша счётчиков не помог.
Методика тестирования
Чтобы оценить эффективность использования выделенной видеокарты на облачным сервере, нужно провести две серии одинаковых тестов: до и после включения отрисовки удалённого рабочего стола на GPU.
В каждой серии выполним следующие тесты:
ввод текста
ввод текста + 3D BenchMark
ввод текста + просмотр локальных видеофайлов
ввод текста + просмотр youtube-ролика
Для оценки влияния теста на задержку обработки ввода от пользователя поверх основной программы открывается стандартный Блокнот
и зажимается произвольная клавиша. Окно редактора на протяжении теста будет постоянной частью всех кадров, поэтому исказит результат в лучшую сторону. Чтобы снизить эффект, размер окна уменьшим до 122*156% 99% кадра будут меняться и визуально будет видно, что имитация активности пользователя работает.
Тест #1: ввод текста
В качестве тестов рекомендуется "использовать любые приложения, которые плотно работают с графикой (например, потоковое видео), приложения, использующие DirectX". Результаты первого теста, с точки зрения пользователя (частота кадров и задержка ввода), практически одинаковые. Поэтому строить графики и анализировать их нет особого смысла. Такой тест больше пригодится для диагностики RemoteFX.
Тест #2: ввод текста + 3D BenchMark
Выполнялся при помощи FurMark в полноэкранном режиме.
Тест #3: ввод текста + просмотр локальных видеофайлов
Локальные видеофайлы воспроизводились в Windows Media Player, равёрнутом на весь экран, без установки каких-либо дополнительных кодеков, по кругу в следущем порядке:
"Ants carrying dead spider": 1920 x 1080, 10667 кбит/с, 19 секунд, 29.97 fps
"Flying Through Forest 1": 1920 x 1088, 48072 кбит/с, 9 секунд, 25 fps
"Low Angle Of Pedestrians Walking In Busy Street, Daytime": 4096 x 2160, 31721 кбит/с, 13 секунд, 25 fps
Единственным критерием отбора была динамичность ролика: видеоряд должен был как можно сильнее нагрузить кодек RemoteFX. Но ролик "Flying Through Forest 1" оказался в этом плане интересной находкой: выходной FPS заметно проседал, а входной от запуска к запуску был сильно выше или ниже среднего! Его влияние на различные метрики будет заметно на графиках, которые будут ниже.
Тест #4: ввод текста + просмотр youtube-ролика
В качестве youtube-теста был выбран чудесный ролик "Коста-Рика", который проигрывался в качестве 1080p60
в браузере Firefox, режим "киоск".
Обработка данных и построение графиков
Файлы журналов - blg
- имеют двоичный формат: легко открываются в родной программе, графики всех счётчиков на одной шкале, можно выключать/включать/масштабировать/etc. Но чтобы работать с данными из нескольких файлов, нужно другое решение.
Сначала конвертируем двоичные файлы в csv (см. Приложение) с помощью стандартной утилиты reglog
и очистим их заголовки (см. Приложение).
Затем в Jupiter
-блокноте при помощи библиотек pandas
и matplotlib
прочитаем csv
(см. Приложение, Jupiter Notebook: импорт) и построим графики (см. Приложение, Jupiter Notebook: одна метрика — одна диаграмма).
Анализ результатов и наиболее интересные графики
Задержки при обработке ввода от пользователя
До включения групповых политик сильнее всего на обработке ввода сказалось проигрывание локальных видеофайлов: в среднем 45 мс при рекомендованных 33 мс.
Включение отрисовки через GPU в групповых политиках стабилизировало этот показатель во всех сценариях.
Частота кадров
После включения GPU ускорения ситуация явно становится лучше, особенно в 3D тесте. Результаты двух других тестов хоть и демонстрируют улучшение, но недостаточно: провалы на графиках соответствуют визуальным "рывкам" при просмотре.
Воспроизведение "Flying Through Forest 1" (1920 x 1088, 48072 кбит/с, 9 секунд, 25 fps): от запуска к запуску на вход кодека RеmoteFX поступало либо повышенное либо пониженное количество кадров. Возможно, причина в перекодировке из формата QuickTime, "лишних" 8 пикселях ширины кадра или битрейте.
"Коста-Рика" также вызвал "проседание" FPS у RemoteFX: его проигрывание в браузере в 1080p60
ложилось на центральный процессор. Возможно, он просто не успевал перекодировать из 60fps и подготовить нужный кадр для RemoteFX.
Общие сетевые метрики
При включении GPU ускорения происходит рост сетевого трафика, который зависит от сценария теста.
Видно, что самым тяжёлым для кодека RemoteFX опять оказался тот же самый видеофайл, "Flying Through Forest 1". Первый запуск этого теста, когда наблюдается провал входящих кадров, также видим скачки трафика до 60 Мбит/с и потери до 30% - 40%.
Загрузка центрального процессора
Включение GPU ускорения практически вдвое разгружает центральный процессор, за исключением youtube-теста. И даже здесь удалось уйти от периодической 100% загрузки.
Загрузка видеокарты
Возможно, разгадка странного поведения второго ролика кроется в графике 17: входящих для RemoteFX кадров было больше, когда видеокарта была больше загружена кодированием.
Выводы
В RDP протоколе частота кадров ограничена 30-ю кадрами в секунду. Со стороны сервера увеличить лимит FPS можно, но бессмысленно: на практике терминальная сессия перестала отрисовывать окно и реагировать на действия как раз при проигрывании "того самого" видеофайла :).
Итоги в цифрах:
Частота кадров стабилизируется почти на максимально возможном для протокола уровне: 29-30 FPS в среднем вместо 25 или даже 15 FPS
Отзывчивость удалённого рабочего стола также стабилизируется, при этом возрастая в несколько раз
Заметно, на 20-50 %, разгружается центральный процессор
Немного возрастает утилизация канала связи, на 3-6 Мбит/сек
Утилизация GPU составила до 20%
Результат действительно очень хороший: RemoteFX значительно увеличивает качество работы в терминальной сессии — плавность отрисовки окна и отклик на действия пользователя сравнимы с локальным режимом. Даже "тяжёлые" сценарии показывают в этом плане заметный прирост.
Тесты, конечно, носят искусственный характер: выбором способа нагрузки на кодек RemoteFX можно как "завалить" так и "подтянуть" его результаты. Возможно, более релевантным было бы проведение чего-то вроде "конфетти-теста", например, такого.
Что дальше
Так как на этом этапе тесты проводились для одной сессии и при включении лишь рекомендованных настроек, то далее имеет смысл протестировать производительность:
при одновременной работе нескольких пользователей
при включении в групповых политиках различных дополнительных настроек кодека
Список источников
Настройка ускорения графического процессора (GPU) для виртуального рабочего стола Windows
Диагностика проблем производительности графики удалённого рабочего стола
Рекомендация по тестированию взята из хоть и старого, но очень подробного описания RemoteFX
Про лимиты FPS
Сброс кэша счётчиков
Как сделать работу с Microsoft Remote Desktop лучше. Ветка комментариев про UDP, TCP, потери и т.д. В самой статье есть ссылки на спецификации мультитранспортного расширения для протокола RDP
Приложения
Переопределение групп сбора данных: _1_task_define.cmd
@echo off
setlocal EnableDelayedExpansion
@REM для сбора используются счётчики, перечисленные в _1_counters.cfg
@REM счётчики называются только на английском, на русском это просто описание
@REM
@REM запуск сбора данных:
@REM при каждом входе на удалённый рабочий стол исправить _1_counters.cfg:
@REM 1) посмотреть номер своей терминальной сессии консольной командой
@REM query session
@REM
@REM 2) вписать этот номер в _1_counters.cfg, например, RDP-Tcp 9
@REM
@REM 3) перерегистрировать сброщик запуском данного файла
@REM
@REM 4) замер производительности производится запуском _2_task_run_X.cmd и длится 2 минуты
@REM
@REM удаление старого сборщика данных
logman delete -n RemoteFX_1
logman delete -n RemoteFX_2
logman delete -n RemoteFX_3
logman delete -n RemoteFX_4
logman delete -n RemoteFX_5
for /F "usebackq delims= " %%a IN (`query session ^| find "Administrator"`) DO (
set "x=%%a"
set "x=!x:~9,10!"
echo !x!
type NUL>_1_counters.cfg
echo ^\NVIDIA GPU^(^*^)^\%% Video Decoder Usage>>_1_counters.cfg
echo ^\NVIDIA GPU^(^*^)^\%% Video Encoder Usage>>_1_counters.cfg
echo ^\NVIDIA GPU^(^*^)^\%% GPU Memory Usage>>_1_counters.cfg
echo ^\NVIDIA GPU^(^*^)^\%% GPU Usage>>_1_counters.cfg
echo.>>_1_counters.cfg
echo ^\Processor Information^(_Total^)^\%% Processor Time>>_1_counters.cfg
echo.>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\Loss Rate>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\Current TCP Bandwidth>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\Current UDP Bandwidth>>_1_counters.cfg
echo.>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\Total Sent Rate>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\TCP Sent Rate>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\UDP Sent Rate>>_1_counters.cfg
echo.>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\Total Received Rate>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\TCP Received Rate>>_1_counters.cfg
echo ^\RemoteFX Network^(RDP-Tcp !x!^)^\UDP Received Rate>>_1_counters.cfg
echo.>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Output Frames/Second>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Input Frames/Second>>_1_counters.cfg
echo.>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Frames Skipped/Second - Insufficient Server Resources>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Frames Skipped/Second - Insufficient Network Resources>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Frames Skipped/Second - Insufficient Client Resources>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Frame Quality>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Average Encoding Time>>_1_counters.cfg
echo ^\User Input Delay per Session^(Max^)^\Max Input Delay>>_1_counters.cfg
echo.>>_1_counters.cfg
echo ^\RemoteFX Graphics^(^*^)^\Graphics Compression ratio>>_1_counters.cfg
echo ^\NVIDIA GPU^(^*^)^\%% FB Usage>>_1_counters.cfg
@REM счётчик памяти не работает, сброс кэша счётчиков также не помог
@REM https://docs.microsoft.com/ru-ru/troubleshoot/windows-server/performance/manually-rebuild-performance-counters
echo ^\Memory^\%% Committed Bytes In Use>>_1_counters.cfg
echo ^\Memory^\Available Bytes>>_1_counters.cfg
)
@REM и регистрация нового сборщика
logman create counter -n RemoteFX_1 -f bin -max 10 -si 00:00:01 -rf 00:02:00 --v -o "%~dp0logs\test #1 void GPO X" -cf "%~dp0_1_counters.cfg"
logman create counter -n RemoteFX_2 -f bin -max 10 -si 00:00:01 -rf 00:02:00 --v -o "%~dp0logs\test #2 3D GPO X" -cf "%~dp0_1_counters.cfg"
logman create counter -n RemoteFX_3 -f bin -max 10 -si 00:00:01 -rf 00:02:00 --v -o "%~dp0logs\test #3 wmp GPO X" -cf "%~dp0_1_counters.cfg"
logman create counter -n RemoteFX_4 -f bin -max 10 -si 00:00:01 -rf 00:02:00 --v -o "%~dp0logs\test #4 youtube GPO X" -cf "%~dp0_1_counters.cfg"
logman create counter -n RemoteFX_5 -f bin -max 10 -si 00:00:01 -rf 00:02:00 --v -o "%~dp0logs\test #5 webGL GPO X" -cf "%~dp0_1_counters.cfg"
@REM pause
@REM exit
Принудительная остановка записи данных: _1_task_breake.cmd
@REM запускаем сбор данных
logman stop RemoteFX_1
logman stop RemoteFX_2
logman stop RemoteFX_3
logman stop RemoteFX_4
logman stop RemoteFX_5
Конвертация двоичных данных в CSV: blg2csv.cmd
@echo off
@REM смена кодировки нужна для powershell-скрипта "%~dpn0.ps1"
chcp 65001
@REM работаем в текущей папке скрипта
cd "%~dp0logs"
@REM включаем расширения для переопределения переменных в цикле
setlocal EnableDelayedExpansion
@REM цикл по двоичным файлам мониторинга
FOR /F "usebackq delims=." %%a IN (`dir *.blg /b`) DO (
set "blg=%%a.blg"
set "csv=%%a.csv"
@REM convert binary to csv
relog "!blg!" -f csv -o "!csv!" -y
)
@REM имена cmd и powershell скриптов должны совпадать
start "%~dpn0.ps1" /WAIT /B pwsh.exe -Command "& {%~dpn0.ps1 -en:$False}"
@REM справка reglog - утилиты работы с журналами производительности
@REM https://docs.microsoft.com/ru-ru/windows-server/administration/windows-commands/relog
Нормализация заголовков CSV: blg2csv.ps1
Заголовки столбцов содержат различные уникальные данные (id терминальной сессии или оборудования), которые будут несовпадать из-за применения групповых политик и выхода-входа на терминальный сервер. Приводим к единому виду с помощью PowerShell-скрипта:
[CmdletBinding()]
param (
[switch] $en = $False # substitute ru alias of counters by real en name
)
$WorkDir = $MyInvocation.MyCommand.Definition | Split-Path -Parent
$LogsDir = Join-Path -Path $WorkDir -ChildPath 'logs'
$EncodeFrom = [System.Text.Encoding]::GetEncoding(1251)
$EncodeTo = New-Object System.Text.UTF8Encoding $False
$names = @(
New-Object psobject -Property @{ 'ru' = 'Задержка ввода данных пользователем на сеанс(Max)\Максимальная задержка ввода' ; 'en' = 'User Input Delay per Session\Max Input Delay'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Входящих кадров в секунду' ; 'en' = 'RemoteFX Graphics\Input Frames/Second'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Исходящих кадров в секунду' ; 'en' = 'RemoteFX Graphics\Output Frames/Second'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Среднее время кодирования' ; 'en' = 'RemoteFX Graphics\Average Encoding Time'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Качество кадра' ; 'en' = 'RemoteFX Graphics\Frame Quality'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Коэффициент сжатия графических данных' ; 'en' = 'RemoteFX Graphics\Graphics Compression ratio'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Общая скорость отправки' ; 'en' = 'RemoteFX Network\Total Sent Rate'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Общая скорость приема' ; 'en' = 'RemoteFX Network\Total Received Rate'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Потери' ; 'en' = 'RemoteFX Network\Loss Rate'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Пропущено кадров в секунду — недостаточно сетевых ресурсов' ; 'en' = 'RemoteFX Graphics\Frames Skipped/Second - Insufficient Network Resources'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Пропущено кадров в секунду — у сервера недостаточно ресурсов' ; 'en' = 'RemoteFX Graphics\Frames Skipped/Second - Insufficient Server Resources'}
New-Object psobject -Property @{ 'ru' = 'Графика RemoteFX\Пропущено кадров в секунду — у клиента недостаточно ресурсов' ; 'en' = 'RemoteFX Graphics\Frames Skipped/Second - Insufficient Client Resources'}
New-Object psobject -Property @{ 'ru' = 'Сведения о процессоре(_Total)\% загруженности процессора' ; 'en' = 'Processor Information(_Total)\% Processor Time'}
New-Object psobject -Property @{ 'ru' = 'NVIDIA GPU\% GPU Usage' ; 'en' = 'NVIDIA GPU\% GPU Usage'}
New-Object psobject -Property @{ 'ru' = 'NVIDIA GPU\% GPU Memory Usage' ; 'en' = 'NVIDIA GPU\% GPU Memory Usage'}
New-Object psobject -Property @{ 'ru' = 'NVIDIA GPU\% Video Decoder Usage' ; 'en' = 'NVIDIA GPU\% Video Decoder Usage'}
New-Object psobject -Property @{ 'ru' = 'NVIDIA GPU\% Video Encoder Usage' ; 'en' = 'NVIDIA GPU\% Video Encoder Usage'}
New-Object psobject -Property @{ 'ru' = 'NVIDIA GPU\% FB Usage' ; 'en' = 'NVIDIA GPU\% FB Usage'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Пропускная способность текущего UDP-подключения' ; 'en' = 'RemoteFX Network\Current UDP Bandwidth'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Пропускная способность текущего TCP-подключения' ; 'en' = 'RemoteFX Network\Current TCP Bandwidth'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Скорость отправки UDP-данных' ; 'en' = 'RemoteFX Network\UDP Sent Rate'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Скорость получения UDP-данных' ; 'en' = 'RemoteFX Network\UDP Received Rate'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Скорость отправки TCP-данных' ; 'en' = 'RemoteFX Network\TCP Sent Rate'}
New-Object psobject -Property @{ 'ru' = 'Сеть RemoteFX\Скорость получения TCP-данных' ; 'en' = 'RemoteFX Network\TCP Received Rate'}
)
$Heads = @()
foreach ($f in Get-ChildItem -Path $LogsDir -File -Filter '*.csv')
{
$FileContent = $f | Get-Content -Encoding $EncodeFrom
$HeadOrig = $FileContent[0]
# приводим заголовки к единому виду, убираем ненужное
# "\\TESTGPU\NVIDIA GPU(#0 GRID M60-1Q (id=1, NVAPI ID=513))\% GPU Memory Usage"
if ($HeadOrig -match '.*(?<hostname>\\\\[a-zA-Z0-9]*\\).*')
{
$HeadOrig = $HeadOrig.Replace($Matches['hostname'], '')
}
if ($HeadOrig -match '.*NVIDIA GPU(?<gpu>\(#[A-Z0-9 ]*-[A-Z0-9]* \(id=[0-9,]* NVAPI ID=[0-9]*\)\))')
{
$HeadOrig = $HeadOrig.Replace($Matches['gpu'], '')
}
# "\\TESTGPU\Графика RemoteFX(RDP-Tcp 55)\Входящих кадров в секунду"
if ($HeadOrig -match '.*(?<session>\(RDP-Tcp[ 0-9]*\)).*')
{
$HeadOrig = $HeadOrig.Replace($Matches['session'], '')
}
# "(PDH-CSV 4.0) (Russia TZ 2 Standard Time)(-180)"
if ($HeadOrig -match '.*(?<time>\(.*\) \(.*Time\)\([0-9 +-]*\))')
{
$HeadOrig = $HeadOrig.Replace($Matches['time'], 'Time')
}
if ($en)
{
$HeadOrig = ($HeadOrig -split '","') -replace '"', ''
foreach ($h in $HeadOrig)
{
if ($h -notin $names.ru) { continue }
$n = $names | Where-Object {$_.ru -eq $h}
$HeadOrig[($HeadOrig.IndexOf($h))] = $n.en # $h = $n.en не работает
}
$HeadOrig = '"{0}"' -f ($HeadOrig -join '","')
}
$FileContent[0] = $HeadOrig # перезапись заголовка
$FileContent | Out-File -Encoding $EncodeTo -FilePath $f.FullName
$Heads += $f.Name + $HeadOrig # сохранение заголовка
}
# вывод заголовков столбцов в отдельный файл для доп. контроля порядка, названий и т.д.
$Heads | Out-File -Encoding $EncodeTo -FilePath (Join-Path -Path $LogsDir -ChildPath 'heads.txt')
Jupiter Notebook: импорт данных
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import EngFormatter # для вывода форматированных единиц измерения
plt.rcParams['figure.max_open_warning'] = 30 # порог предупреждения при одновременном построении нескольких рисунков
%matplotlib inline
# импорт данных csv-файлов
t21 = pd.read_csv('./logs/test #2 3D GPO 1.csv', na_values=' ')
t20 = pd.read_csv('./logs/test #2 3D GPO 0.csv', na_values=' ') # , encoding='cp1251')
t31 = pd.read_csv('./logs/test #3 wmp GPO 1.csv', na_values=' ')
t30 = pd.read_csv('./logs/test #3 wmp GPO 0.csv', na_values=' ')
t31_prev = pd.read_csv('./logs/test #3 wmp GPO 1 anomaly.csv', na_values=' ')
t41 = pd.read_csv('./logs/test #4 youtube GPO 1.csv', na_values=' ')
t40 = pd.read_csv('./logs/test #4 youtube GPO 0.csv', na_values=' ')
# слияние результатов каждого теста: сначала рекомендованные GPO, потом default GPO
t2 = pd.concat([t21, t20], join='inner', axis=1)
t3 = pd.concat([t31, t30, t31_prev], join='inner', axis=1)
t4 = pd.concat([t41, t40], join='inner', axis=1)
# разные наборы для итерации и рисования в цикле
dataframes = [t2, t3, t4]
ax_titles = ['test #2: 3D benchmark', 'test #3: play 1080p video', 'test #4: play 1080p youtube video']
legend = ['GPU acceleration', 'default', 'GPU, anomaly']
img_sx, img_sy = 15, 5 # размеры одного ряда графиков
fgs = [ # макет графиков # yunit ед. изм., ylabel метка Y-оси, ydata колонка из датафрейма
{'yunit': 'ms', 'ylabel': 'Input Delay', 'ydata': 'Задержка ввода данных пользователем на сеанс(Max)\Максимальная задержка ввода'},
{'yunit': 'fps', 'ylabel': 'RemoteFX input FPS', 'ydata': 'Графика RemoteFX\Входящих кадров в секунду'},
{'yunit': 'fps', 'ylabel': 'RemoteFX output FPS', 'ydata': 'Графика RemoteFX\Исходящих кадров в секунду'},
{'yunit': 'bps', 'ylabel': 'Tx Speed', 'ydata': 'Сеть RemoteFX\Общая скорость отправки'},
{'yunit': 'bps', 'ylabel': 'Rx Speed', 'ydata': 'Сеть RemoteFX\Общая скорость приема'},
{'yunit': '%', 'ylabel': 'Tx / Rx Loss', 'ydata': 'Сеть RemoteFX\Потери'},
{'yunit': '%', 'ylabel': 'CPU Usage', 'ydata': 'Сведения о процессоре(_Total)\% загруженности процессора'},
{'yunit': '%', 'ylabel': 'GPU Usage', 'ydata': 'NVIDIA GPU\% GPU Usage'},
{'yunit': '%', 'ylabel': 'GPU Memory Usage', 'ydata': 'NVIDIA GPU\% GPU Memory Usage'},
{'yunit': '%', 'ylabel': 'GPU Decoder Usage', 'ydata': 'NVIDIA GPU\% Video Decoder Usage'},
{'yunit': '%', 'ylabel': 'GPU Encoder Usage', 'ydata': 'NVIDIA GPU\% Video Encoder Usage'},
{'yunit': 'ms', 'ylabel': 'Encoding Time', 'ydata': 'Графика RemoteFX\Среднее время кодирования'},
{'yunit': '%', 'ylabel': 'Frame Quality', 'ydata': 'Графика RemoteFX\Качество кадра'},
{'yunit': '%', 'ylabel': 'Compression: enc byte / in byte', 'ydata': 'Графика RemoteFX\Коэффициент сжатия графических данных'},
{'yunit': 'fps', 'ylabel': 'FPS Loss by network', 'ydata': 'Графика RemoteFX\Пропущено кадров в секунду — недостаточно сетевых ресурсов'},
{'yunit': 'fps', 'ylabel': 'FPS Loss by server', 'ydata': 'Графика RemoteFX\Пропущено кадров в секунду — у сервера недостаточно ресурсов'},
{'yunit': 'fps', 'ylabel': 'FPS Loss by client', 'ydata': 'Графика RemoteFX\Пропущено кадров в секунду — у клиента недостаточно ресурсов'},
{'yunit': '%', 'ylabel': 'GPU Framebufer Usage', 'ydata': 'NVIDIA GPU\% FB Usage'},
{'yunit': 'bps', 'ylabel': 'Tx Speed UDP', 'ydata': 'Сеть RemoteFX\Скорость отправки UDP-данных'},
{'yunit': 'bps', 'ylabel': 'Rx Speed UDP', 'ydata': 'Сеть RemoteFX\Скорость получения UDP-данных'},
{'yscale': 1000, 'yunit': 'bps', 'ylabel': 'Bandwidth UDP', 'ydata': 'Сеть RemoteFX\Пропускная способность текущего UDP-подключения'},
{'yunit': 'bps', 'ylabel': 'Tx Speed TCP', 'ydata': 'Сеть RemoteFX\Скорость отправки TCP-данных'},
{'yunit': 'bps', 'ylabel': 'Rx Speed TCP', 'ydata': 'Сеть RemoteFX\Скорость получения TCP-данных'},
{'yscale': 1000, 'yunit': 'bps', 'ylabel': 'Bandwidth TCP', 'ydata': 'Сеть RemoteFX\Пропускная способность текущего TCP-подключения'},
]
Jupiter Notebook: отрисовка одной метрики на диаграмму
for i in range(len(fgs)): # сколько метрик, столько рисунков (рядов диаграмм)
fig, axs = plt.subplots(1, 3, figsize=(img_sx, img_sy), sharex='col', sharey='row', gridspec_kw=dict(hspace=0, wspace=0, )) # width_ratios=[1, 1, 1]
fig_name = fgs[i]['ydata'].split('\\')[1]
fig.suptitle(f'Рис. {i + 1:>2}. {fig_name}', fontsize=14) #, color='grey') # имя рисунка
fig.patch.set_facecolor('white') # фон рисунка белый вместо прозрачного
axs[0].set(ylabel=fgs[i]['ylabel']) # подпись Y-оси только на первых (левых) графиках из трёх
axs[2].yaxis.set_tick_params(labelleft=False, labelright=True, which='major') # дублируем значения справа
for ax, d, title in zip(axs, dataframes, ax_titles): # на каждый тест своя диаграмма
ax.plot(d.index.values, d[fgs[i]['ydata']] * (fgs[i].get('yscale', 1))) # строим график
ax.set_title(title) #, color='grey') # заголовок диаграммы
ax.xaxis.set_tick_params(which='major', labelcolor='grey') # подписи к X-шкале
ax.xaxis.set_major_formatter(EngFormatter(unit='s')) # по X секунды
ax.yaxis.set_tick_params(which='major', labelcolor='grey') # подписи Y-шкале
ax.yaxis.set_major_formatter(EngFormatter(unit=fgs[i]['yunit'])) # по Y у каждого графика своя ед. изм.
# расчёт средних и вывод в легенду диаграммы "на лету"
avg = [EngFormatter(places=0).format_eng(avg * (fgs[i].get('yscale', 1))) for avg in d[fgs[i]['ydata']].mean()]
lgn = [f'avg {m}{fgs[i]["yunit"]}, {l}' for l, m in zip(legend, avg)]
ax.legend(lgn, fontsize=8) # отображение легенды графика
ax.grid(axis='y') # горизонтальная сетка
ax.set_xlim(0,119) # метка "120" засоряла график
if ax == axs[1]: # выделяем аномалии на средней диаграмме
ax.axvline(x=15, linestyle=":", color='C0')
ax.axvline(x=58, linestyle=":", color='C0')
ax.axvline(x=101, linestyle=":", color='C0')
fig.tight_layout() # (pad=0.4, w_pad=1.5, h_pad=30.0)
Jupiter Notebook: отрисовка всех метрик на одной диаграмме
Общая ось X на все графики: метрики тестов располагаются строго друг под другом - удобно сопоставлять разные метрики между собой.
# один рисунок # plt.style.available # plt.style.use('seaborn-whitegrid')
fig, axs = plt.subplots(len(fgs), 3, figsize=(img_sx, img_sy * len(fgs)), sharex='col', sharey='row', gridspec_kw=dict(hspace=0.2, wspace=0))
fig.patch.set_facecolor('white') # фон рисунка белый вместо прозрачного
# заголовки только вверху
[ax.set_title(title) for ax, title in zip(axs[0], ax_titles)] # ax.set_title(title, color='grey')
for i in range(len(fgs)):
axs[i,0].set(ylabel=fgs[i]['ylabel'])
fig_name = fgs[i]['ydata'].split('\\')[1]
axs[i,1].set(xlabel=f'Рис. {i + 1:>02}. {fig_name}')
# axs[i,1].xaxis.label.set_color('grey')
axs[i,2].yaxis.set_tick_params(labelleft=False, labelright=True, which='major')
for ax, d, title in zip(axs[i], dataframes, ax_titles):
ax.plot(d.index.values, d[fgs[i]['ydata']] * (fgs[i].get('yscale', 1)))
ax.xaxis.set_tick_params(which='major', labelcolor='grey') # подписи к X-шкале
ax.xaxis.set_major_formatter(EngFormatter(unit='s')) # по X секунды
ax.yaxis.set_tick_params(which='major', labelcolor='grey') # подписи Y-шкале
ax.yaxis.set_major_formatter(EngFormatter(unit=fgs[i]['yunit'])) # по Y у каждого графика своя ед. изм.
# расчёт средних и изменение легенды диаграммы "на лету"
avg = [EngFormatter(places=0).format_eng(avg * (fgs[i].get('yscale', 1))) for avg in d[fgs[i]['ydata']].mean()]
lgn = [f'avg {m}{fgs[i]["yunit"]}, {l}' for l, m in zip(legend, avg)]
ax.legend(lgn, fontsize=8) # отображение легенды графика
ax.grid(axis='y') # горизонтальная сетка
ax.set_xlim(0,119) # метка "120" засоряла график
if ax == axs[i,1]: # выделяем аномалии на средней диаграмме
ax.axvline(x=15, linestyle="--", color='C0')
ax.axvline(x=58, linestyle="--", color='C0')
ax.axvline(x=101, linestyle="--", color='C0')
Диаграмма со всеми графиками
Продолжение истории будет на следующей неделе. Спасибо за внимание!
Что ещё интересного есть в блоге Cloud4Y
→ Частые ошибки в настройках Nginx, из-за которых веб-сервер становится уязвимым
→ Пароль как крестраж: ещё один способ защитить свои учётные данные
→ Тим Бернерс-Ли предлагает хранить персональные данные в подах
→ Подготовка шаблона vApp тестовой среды VMware vCenter + ESXi
→ Создание группы доступности AlwaysON на основе кластера Failover
Подписывайтесь на наш Telegram-канал, чтобы не пропустить очередную статью. Пишем не чаще двух раз в неделю и только по делу.