Skyforge с высоты птичьего полета

Привет! Меня зовут Александр Акбашев. Я – QA-инженер Allods Team на проекте Skyforge. В зону моей ответственности входит организация тестирования сервера нашей игры, и именно о тестировании сервера будет данная статья. В мае я делал доклад на КРИ, который превратился в статью из двух частей.

Технологический стек Skyforge таков: клиент игры написан на C++, сервер игры разрабатывается на Java, много различных полезных утилит и скриптов написано на Python. Еще у нас есть инструменты для дизайнеров, написанные на C#, но в статье не будет больше ни слова про C#. :)

Часть 1. Боты – санитары сервера

Во время разработки проекта Аллоды Онлайн перед командой сервера встал серьезный вопрос: как проводить комплексные и нагрузочные тесты сервера? Решили написать для этого ботов – автономное клиентское приложение, эмулирующее поведение реального игрока. С тех пор, конечно, много воды утекло. Боты, используемые в Skyforge, уже мало похожи на самых первых хиленьких ботов. Назначение и инфраструктура тестов, проводимых с их использованием, также эволюционировали: теперь боты помогают нам получать огромное количество серверных характеристик для анализа. Всё благодаря тому, что на нашем проекте в процесс непрерывной интеграции включены ботовые тесты. Такие тесты разрабатываются с упором на максимальную устойчивость. То есть, если во время регулярного теста что-то закрашилось, но не случилось ничего критического, тестирование продолжается. В этом наши ботовые тесты кардинально отличаются от unit-тестов, которые должны падать при первом же чихе. При этом починка ботового теста не менее приоритетна.

 

Что же собой представляют боты?

Наши боты реализованы так называемым «методом белого ящика»: у клиента, написанного на C++, удален интерфейс, а вместо него добавлен «мозг», написанный также на C++. Более того, общая база кода у клиента и ботов позволяет тестировать достаточно низкоуровневые вещи в самом клиенте.

«Мозги» ботов реализованы с помощью конечного автомата – графа переходов из одного состояния в другое при определенных условиях. Также боты имеют постоянные активные элементы, которые действуют независимо от того, в каком состоянии сейчас находится бот и его «мозг». Они поддерживают, например, бессмертие ботов и чат-активности. С ботами можно переписываться в личном или общем игровом чате – они всегда готовы морально поддержать команду QA , цитируя различные истории с башорга.

Пример простейшего графа переходов: бот появляется на карте, бежит, видит монстра и убивает его. Если есть трофеи  – поднимает, если нет – бежит дальше.

 

Для тестирования с более гибкими настройками применяются так называемые ботовые сценарии. Они позволяют нам доопределять желаемое поведение ботов: выбрать «мозг», который необходимо использовать, место и условия появления, класс персонажа, заклинания, способности, активности. Получается некий портрет пользователя, которым и является данный бот. После этого его можно отдавать тестировщикам, чтобы дополнять нагрузочное тестирование или проводить какие-то другие тесты.

В самом начале основной фокус ботов был на генерации релевантной нагрузки. Однако оказалось, что использование ботов выгодно и в других направлениях.

Чтобы боты были такими полезными, они должны соответствовать двум очень важным требованиям.

Первое – адекватность поведения. Боты должны соответствовать игрокам по поведению. Если получилось так, что боты не соблюдают требования гейм-дизайна, значит, надо их чинить. Например, бот, пробегая определенный участок, должен убить столько же монстров, сколько и настоящий игрок в таких же условиях.

Второе требование – актуальность. Допустим, вы сделали ботов, которые стреляют из лука и убивают монстров по одному. Но злые гейм-дизайнеры решили, что в данном случае вместо лука должна быть ракетная установка, которая разносит в клочья по несколько монстров за раз. Соответственно, ботов нужно переделывать под эти требования, иначе нагрузка и контент будут неактуальными.

Тесты решают

В начале нашей работы с тестами основной акцент был на нагрузке. Мы стремились максимально нагрузить имеющуюся инфраструктуру и использовать все возможности ботов. Тогда и мысли не было, что боты могут приносить так много пользы. В дальнейшем мы стали искать проблемы, которые можно решить, используя созданную инфраструктуру ботовых тестов. Например, боты помогают проверять групповые активности. Тестировщик может добавить в группу бота и отправиться в групповое приключение. Это существенно облегчает проверку подобных процессов.

 

Проверка сервера

Благодаря непрерывной интеграции, несколько раз в сутки мы собираем свежую версию сервера. Однако нет уверенности в том, хорошая вышла сборка или нет и можно ли её использовать. Тестировщики не всегда могут сразу же обновить клиент и проверить новую сборку. Для этого сейчас используется smoke test, который проверяет свежую сборку с помощью бота. Это достаточно простой бот: он заходит на сервер, ищет монстра и убивает его. Если все прошло гладко – сервер рабочий и на нем можно играть.

Репетиция главного теста

У нас есть ночной тест, который идет 8 часов и позволяет собрать очень много ценной информации. К сожалению, итерации теста очень дорогие, и, чтобы не пропускать запуски этого теста из-за ошибок, которые можно исправить за 5 минут, проводится отдельный дневной тест. Он запускается на той же версии кода и тех же данных, что и ночной, но всего на час. Таким образом, в течение дня проводятся репетиции ночного запуска. При необходимости мы успеваем исправить различные ошибки до конца рабочего дня. Прохождение часового теста на 99% гарантирует прохождение теста ночного.

Также часовые тесты используются для экспериментов и проверки различных гипотез. Например, если вы захотели поиграть с ключиками в Java-машине, используйте часовой тест. Можете запустить и сравнить два набора ключиков. Еще можно сравнить два разных алгоритма или два разных набора карт.

Проверка контента

Контент Skyforge достаточно объемный. У нас очень много объектов игрового мира. Поэтому, когда запускается ночной тест, не всегда понятно, какие элементы дизайна приводят к дополнительной нагрузке. Не всегда понятно, почему на одном гейммеханическом сервере с определенным набором карт все хорошо, а на другом и с другим набором карт все плохо. Карта ли виновата, количество игроков или фаза луны GC? В результате были добавлены отдельные тесты по картам. Они позволяют сравнивать игру в стерильных условиях. Например, если взять три карты и прогнать их на одинаковых версиях кода и данных, мы получим разные сравнительные характеристики и температурные карты, на которых видно, где могут возникнуть критические места. Эти тесты, наверное, самое полезное, что у нас есть для профилирования игрового контента.

 

Пример температурной карты (на шкале – некий performance rating, вычисляемый в ботах).

Кроме этого, мы используем температурные карты для анализа большого числа характеристик, имеющих координаты: смерти персонажей, количество монстров вокруг, количество монстров в бою и т.д.

Самый главный тест

Наш самый любимый тест – ночной ботовый тест, в ходе которого проверяется весь финальный контент. Он длится, как я уже упоминал, более 8 часов. Это главный нагрузочный тест, о котором я подробно расскажу в следующей статье.

Заключение

Очевидно, что ботовые тесты работают на стыке всех элементов проекта: запускается сервер, клиент, используется ресурсная система, база данных, мастер-сервер, билд-агент... И если хоть один из этих элементов сломается, весь тест можно считать проваленным. Необходимо прикладывать большие усилия, чтобы вся система работала как часы.

Часть 2. Нагрузочное тестирование

Читатели наверняка знают, что нагрузочное тестирование – это сбор показателей производительности программного обеспечения с целью проверки соответствия требованиям.

Наши требования к подобным тестам достаточно просты: нагрузка на сервере в «боевых» условиях должна быть в пределах нормы, а user experience не должен страдать. Поэтому при организации нагрузочного тестирования нужно в первую очередь определить, какие условия считать «боевыми». Например, для нас это означает следующее: на двух серверах игровой механики, одном сервере баз данных и одном сервере с разными вспомогательными сервисами резвятся 5000 игроков одновременно. Во вторую очередь нужно определить, какая нагрузка считается нормальной. Мы считаем, что сервер справляется с нагрузкой, если он проводит в обработке меньше 20 миллисекунд за серверный «тик».

У нас сервисно-ориентированная архитектура — это значит, что топология сервисов во время теста должна совпадать с топологией в «боевых» условиях.

Объем контента тоже должен быть приближен к объему, который будет на «боевых».

В общем, нагрузочное тестирование – это production в миниатюре, где вместо реальных пользователей – боты.

 

Организация тестирования

Все тесты, в том числе и с использованием ботов, на нашем проекте запускаются в системе непрерывной интеграции. В начале теста билд-агент обновляет и запускает сервер,  запускает ботов и ждет заданное время, по истечении которого останавливает сервер, анализирует результаты и переходит к следующему тесту.

В связи с тем, что мы ограничены в ресурсах (у нас только одна ботовая площадка), нужно как-то синхронизировать их использование. Поэтому все тесты, использующие ботов, запускаются на выделенном билд-агенте.

Ночной ботовый тест

Основной нагрузочный тест, во время которого проверяется весь контент, идет ночью в течение 8 часов. Почему выбрана именно такая продолжительность? Экспериментально было замечено, что самые серьезные ошибки возникают на рубеже 4,5–6 часов, и чтобы их найти, мы просто вынуждены проводить такие длительные тесты. Именно на этот интервал приходится FullGC-пауза (подробнее об этом явлении), борьба с которой также является целью тестов. В наших планах реализовать постоянные тесты длительностью 56 часов в течение выходных. Но пока, к сожалению, это только планы.

Сервера

А сейчас я позволю себе дать несколько советов по организации нагрузочного тестирования, попутно рассказывая о том, как оно устроено у нас. Да, я понимаю, что кому-то эти советы покажутся заметками небезызвестного Капитана, но кому-то они все же могут быть полезны.

Важнейший этап нагрузочного тестирования – выбор и подготовка серверов для тестов. Т. к. у нас нет боевых серверов, мы выбирали максимально приближенные к тем, которые будут на момент релиза игры. Иначе следовало бы использовать сервера, аналогичные «боевым».

Сервера должны быть настроены именно так, как будут использоваться в «боевом» режиме. К слову, мы рассматриваем возможность использования технологии Thread Affinity, которая позволяет закрепить отдельные процессорные ядра за конкретными потоками, например за потоками игровой механики. И если эта технология «выстрелит», это будет означать, что данная настройка должна быть включена при проведении нагрузочного тестирования. Иначе поведение сервера под нагрузкой в тестовом окружении и в реальности будет значительно отличаться.

Также нужно помнить, что на современных серверах есть режимы работы «зеленая среда» или «экономия электричества». Рекомендую отключить их сразу, выставляя процессоры на полную производительность, потому что «в бою» серверам будет не до отдыха, а чинить несуществующие проблемы производительности во время теста в экорежиме – это плохая затея.

Важно, чтобы у вас был полный доступ к вашей площадке, чтобы вы могли в любой момент зайти и посмотреть, что там происходит, какие процессы запущены и т. д. Между вами и сервером не должно быть армии злых админов, которые контролируют каждый чих. Это необходимо по двум причинам. Во-первых, самостоятельно настраивая свой стенд и отвечая за его состояние, вы лучше представляете себе проблемы, которые могут возникнуть. Во-вторых, вы будете обладать полной информацией о том, что происходит с вашей тестовой площадкой. Это очень полезно при анализе аномалий в полученных данных.

Полный доступ подразумевает также, что вы там один. Если вы проводите нагрузочное тестирование, нужно убедиться, что на сервере нет вашего коллеги, занятого тем же самым, а также выяснить, не происходит ли backup базы данных. Нужно быть на 100% уверенным, что вы там один.

Снимаемая статистика

Самый простой и самый наглядный способ проанализировать данные по нагрузке – это визуализировать их. Мы используем для построения графиков библиотеку  Highcharts, которая уверенно вытеснила jqPlot. Давайте посмотрим на примеры.

Подобный график я вижу каждое утро. Он позволяет отслеживать нагрузку. Нагрузка на сервере – это величина, равная отношению времени в миллисекундах, проведенному в обработке данных за 1 «тик» сервера, к 200 миллисекундам. Если на графике показатель больше единицы (больше нормы), значит, все плохо, если меньше – все хорошо.

Это общий график использования памяти. Он позволяет приблизительно оценить работу «сборщика мусора».

Наверное, это один из наиболее важных графиков для Java-серверов. Вряд ли игроки не заметят паузы во время полной сборки мусора.

Ключи, которые мы используем для сбора данных о работе GC: 

-verbose:gc
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintPromotionFailure
-XX:+PrintClassHistogramBeforeFullGC
-XX:+PrintClassHistogramAfterFullGC
-XX:+PrintGCApplicationConcurrentTime
 

Safepoint – это точка в Java Virtual Machine, где происходит остановка каждый раз, когда нужно собрать stack trace или провести сборку мусора. Подробнее о safepoints можно почитать  здесь. На данном графике изображено, сколько миллисекунд за 1 минуту сервер проводит в этих точках.

 

А вот прекрасные «шарфики», которые были сделаны по заказу Андрея Фролова, чей доклад о базах данных вы могли прочесть ранее. Эти «шарфики» позволяют оценивать, какие database-операции и в каком количестве у нас выполняются.

Кроме этого так же логируется еще несколько десятков статистик, способных рассказать как о высокоуровневых показателях (количество мобов в бою с одним игроком), так и о низкоуровневых (количество переданных пакетов). Большинство этих статистик были заведены в ходе расследований относительно роста нагрузки.

Для этих же целей мы сделали автоматическое снятие дампа памяти и профиля нагрузки в конце теста. Сейчас они анализируются только в ручном режиме, но планируется автоматизировать и этот процесс.

Вишенка

В конце теста запускается так называемый анализатор ошибок. Мы берем все ошибки – а их за ночь иногда набегает до 100 гигабайт – и раскладываем по полочкам. Сравниваем, какие ошибки повторяются наиболее часто, а какие — редко, и разбиваем их по группам. Сортируем по типу – ошибка, предупреждение либо информационное сообщение – и выясняем, является ли это ошибкой контента. Ошибки контента мы передаем QA-специалистам по контенту, а ошибки в серверном коде исследуем сами.

 

В отчете указано, сколько раз и в какие временные промежутки ошибка повторялась. Количество косвенно характеризует сложность повторения данной ошибки, ее стоимость для сервера (не забываем, что при сборе каждого коллстека на сервере умирает котенок) и приоритет в баг-трекере.

Время появления позволяет оценивать распределение ошибок по времени теста. Одно дело, если ошибки размазаны равномерно, и совсем другое, если они начинаются и заканчиваются в некоторый временной промежуток.

В дальнейшем мы планируем развивать эту систему, чтобы можно было связывать ошибки с тасками в JIRA и находить ревизию, в которой ошибка была замечена в первый раз.

Проблемы

У нагрузочных ночных тестов очень дорогие итерации. Стоимость каждого теста – день. Конечно, мы прикладываем все усилия, чтобы тест проходил каждую ночь, но в реальности успешно он проходит реже, а итерации становятся еще дороже. Любая поломка в любом узле инфраструктуры способна сорвать проведение теста.

Заключение

Регулярное нагрузочное тестирование с применением всевозможных статистик – это лучшее средство для здорового сна разработчиков. Сейчас я не представляю, как можно жить без такого рода тестирования, потому что благодаря ему каждое утро я вижу, где у нас полный «аврал» могут возникнуть проблемы. Это помогает нам двигаться в правильную сторону. Спасибо за внимание!

Выражаю благодарность за помощь в создании доклада и написании статьи всей команде сервера Skyforge.

[11.10.2013]

Copyright © 2017 ООО "ДТФ.РУ". Все права защищены.

Воспроизведение материалов или их частей в любом виде и форме без письменного согласия запрещено.

Замечания и предложения отправляйте через форму обратной связи.

Пользовательское соглашение