Соседство по кэшу снижает производительность сервера незаметно для обычного мониторинга. Два сервиса на одной машине честно делят ядра и память, но один из них заполняет кэш L3 постоянным потоком данных, и у второго растут задержки даже при свободных ядрах. Технология Intel Resource Director Technology, доступная в Linux через интерфейс resctrl, закрывает обе проблемы: она показывает, сколько кэша и полосы памяти потребляет каждая группа процессов, и позволяет закрепить за группой конкретную долю ресурсов. В статье разобрана проверка поддержки в процессоре, монтирование файловой системы, настройка масок кэша и лимитов полосы, чтение мониторов llc_occupancy и mbm_total_bytes и разбор типичных ошибок. Все числа взяты для типичного двухсокетного сервера с 20-битной маской кэша, под другие модели они пересчитываются по тем же правилам.
Флаги процессора и монтирование файловой системы resctrl
Вся работа начинается с проверки оборудования. Поддержку выдают флаги процессора в /proc/cpuinfo: мониторинг занятости кэша отмечен флагом cqm_occup_llc, мониторинг полосы памяти флагами cqm_mbm_total и cqm_mbm_local, разделение кэша L3 флагом cat_l3, разделение кэша L2 флагом cat_l2, раздельные лимиты для кода и данных флагом cdp_l3, ограничение полосы памяти флагом mba. Быстрая проверка одной командой:
grep -m1 flags /proc/cpuinfo | grep -oE 'cat_l3|cdp_l3|cqm_occup_llc|cqm_mbm_total|cqm_mbm_local|mba|rdt_a'
Если вывод пуст, аппаратной поддержки нет и настраивать нечего. Ядро при этом должно быть собрано с параметром CONFIG_X86_CPU_RESCTRL, в серверных дистрибутивах он включен по умолчанию. Монтирование занимает одну строку:
mount -t resctrl resctrl /sys/fs/resctrl
На системах с раздельными лимитами для кода и данных добавляют опцию cdp, а для перевода лимитов полосы в мегабайты в секунду опцию mba_MBps:
mount -t resctrl resctrl -o cdp,mba_MBps /sys/fs/resctrl
Чтобы точка подключения появлялась после перезагрузки, строку заносят в /etc/fstab. Сразу после монтирования в /sys/fs/resctrl виден служебный каталог info и корневая группа, которой принадлежат все задачи и все ядра машины. Поколения процессоров поддерживают набор по-разному: мониторинг занятости кэша появился на серверных Haswell, разделение кэша на Broadwell, а управление полосой памяти и раздельные лимиты кода и данных на серверном Skylake.
Каталог info и аппаратные пределы групп
Каталог info описывает пределы конкретного процессора, и без него настройка превращается в работу вслепую. Для кэша L3 ключевыми будут три файла:
cat /sys/fs/resctrl/info/L3/num_closids
cat /sys/fs/resctrl/info/L3/cbm_mask
cat /sys/fs/resctrl/info/L3/min_cbm_bits
Файл num_closids показывает, сколько контрольных групп поддерживает процессор, на распространенных серверных моделях это 16, встречаются образцы с меньшим числом. Файл cbm_mask содержит маску полного объема кэша, например ffffff при ширине 20 бит, и каждый бит такой маски соответствует 5 процентам емкости L3. Файл min_cbm_bits задает минимальную длину сплошного блока единиц в маске. Аппаратура принимает только непрерывные блоки: для 4-битной маски законны значения 0x3, 0x6 и 0xc, а 0x5, 0x9 и 0xa ядро отвергнет.
Для мониторинга пределы лежат в info/L3_MON:
cat /sys/fs/resctrl/info/L3_MON/num_rmids
cat /sys/fs/resctrl/info/L3_MON/mon_features
Число RMID ограничивает количество мониторинговых групп, и когда аппаратные счетчики заканчиваются, создание каталога возвращает ошибку EBUSY. Файл mon_features перечисляет доступные события, обычно это llc_occupancy, mbm_total_bytes и mbm_local_bytes. Файл max_threshold_occupancy хранит порог в байтах: освобожденный RMID нельзя переиспользовать, пока занятые им строки кэша не вытеснены, и порог решает, когда счетчик считается пригодным.
Параметры полосы памяти описывают два файла в info/MB. Шкала допустимых значений строится как min_bandwidth плюс N умножить на bandwidth_gran, промежуточные числа ядро округляет до ближайшего шага вверх. На многих серверных моделях оба числа равны 10, то есть лимиты идут десятками процентов от 10 до 100.
Контрольные группы и файл schemata
Любой каталог внутри /sys/fs/resctrl, кроме info, это группа ресурсов со своим набором файлов. Группу создают обычным mkdir, затем наполняют файл schemata масками и переносят туда номера процессов:
cd /sys/fs/resctrl
mkdir p0
echo "L3:0=3e0;1=fffff" > p0/schemata
echo 4123 > p0/tasks
Маска 3e0 в двоичном виде это биты с 5 по 9, пять сплошных бит, то есть четверть кэша первого сокета. Процесс с номером 4123 теперь размещает новые данные только в своей четверти на сокете 0, а на сокете 1 пользуется всем кэшем. Ровное деление кэша на четыре части при 20-битной маске выглядит так: 0x1f, 0x3e0, 0x7c00 и 0xf8000, по 5 бит или 25 процентов на часть.
Кроме tasks в группе есть файлы cpus и cpus_list: первый хранит битовую маску ядер, второй список с диапазонами. Запись в них закрепляет за группой целые ядра, и тогда все процессы на этих ядрах попадают в ее лимиты без правки отдельных номеров. Файл size переводит маски в байты, и по нему удобно проверять фактический размер зоны. Файл bit_usage рисует буквы по каждому биту: 0 свободный бит, S общий для программ, E занят группой в эксклюзивном режиме, P закреплен под псевдофиксацию.
При переносе задачи в другую группу действуют только новые размещения. Строки кэша, загруженные до переноса, остаются в старой зоне, поэтому llc_occupancy в прежней группе падает по мере вытеснения, а не мгновенно. Этот эффект стоит помнить при сравнении замеров до и после переноса.
Ограничение полосы памяти и режим mba_MBps
Лимит полосы задается строкой MB в том же файле schemata, домен здесь совпадает с доменом кэша L3:
MB:0=20;1=100
Значение 20 на первом сокете означает верхнюю границу в 20 процентов, и ядро округлит его до разрешенного шага шкалы. Проценты не равны гигабайтам, и тут есть две ловушки. Механизм MBA регулирует полосу на уровне ядра процессора, а счетчики MBM считают на уровне пакета. Возьмем процессор с 24 ядрами, внешней полосой 10 гигабайт в секунду на ядро и суммарной полосой L3 в 100 гигабайт в секунду. Двадцать потоков с лимитом 50 процентов берут по 5 гигабайт каждый и вместе упираются в 100 гигабайт, хотя лимит равен только 50 процентам. Повышение процента не даст ни байта, пока узким местом остается полоса L3. Вторая ловушка: один поток с лимитом 10 процентов способен занять до 10 гигабайт, а четыре потока с тем же лимитом до 40, потому что ограничение действует на каждое ядро отдельно.
Режим mba_MBps убирает арифметику с процентами: ядро само читает счетчики MBM и подбирает проценты так, чтобы фактическая полоса не превышала заданную:
mount -o remount,mba_MBps /sys/fs/resctrl
echo "MB:0=1024;1=500" > p0/schemata
Теперь строка MB означает мегабайты в секунду на домен, и лимит 1024 держит первую группу в пределах гигабайта. Управление процентами остается доступным без этой опции, а проверять фактическую полосу удобнее счетчиками mbm_total_bytes, о них дальше.
Мониторы llc_occupancy и mbm_total_bytes в mon_data
Данные мониторинга лежат в каталоге mon_data внутри каждой группы. Каталоги mon_L3_00 и mon_L3_01 соответствуют доменам кэша, то есть сокетам, и внутри них находятся файлы событий: llc_occupancy, mbm_total_bytes и mbm_local_bytes. Чтение выдает моментальный снимок в байтах:
cat /sys/fs/resctrl/p0/mon_data/mon_L3_00/llc_occupancy
16234000
Значение 16234000 байт означает около 16 мегабайт кэша, занятых задачами группы на первом сокете. Сравнение по доменам сразу показывает, где работает группа: 31234000 байт на mon_L3_00 против 34555 на mon_L3_01 означает, что вся нагрузка сидит на первом сокете, и настройки для второго можно не трогать.
Счетчик mbm_total_bytes считает суммарную полосу обращений к памяти, mbm_local_bytes отделяет локальную часть, приходящуюся на свой узел NUMA. Файл выдает накопленное значение, поэтому скорость считают по разнице двух чтений:
a=$(cat /sys/fs/resctrl/p0/mon_data/mon_L3_00/mbm_total_bytes)
sleep 10
b=$(cat /sys/fs/resctrl/p0/mon_data/mon_L3_00/mbm_total_bytes)
echo $(( (b - a) / 10 ))
Разница в 7340000000 байт за десять секунд дает около 700 мегабайт в секунду. Обновление счетчиков происходит с шагом порядка секунды, поэтому замер на коротком интервале выдает шум, а не полосу. Для сравнения двух процессов внутри одной контрольной группы создают мониторинговые подгруппы:
cd /sys/fs/resctrl/p1/mon_groups
mkdir m11 m12
echo 5678 > m11/tasks
echo 5679 > m12/tasks
Контрольная группа суммирует показания всех вложенных подгрупп, поэтому p1 покажет сумму по обоим процессам. Мониторинг работает и там, где разделение кэша недоступно: на системах только с CQM файловая система монтируется, а подгруппы создают внутри корневого каталога, и такой способ профилирует след кэша до всяких лимитов.
Диагностика ошибок через last_cmd_status и блокировка изменений
Файл info/last_cmd_status хранит результат последней операции: при успехе в нем слово ok, при отказе развернутое сообщение. Вывод команды echo при этом часто показывает только write error, а подробность ищут в файле:
echo "L3:0=f7" > schemata
cat info/last_cmd_status
mask f7 has non-consecutive 1-bits
Маска f7 в двоичном виде это 11110111, в середине блока разрыв из нулевого бита, и ядро ее отвергло. Второй частый отказ случается при включении эксклюзивного режима. Маска новой группы обязана не пересекаться с зоной эксклюзивной группы:
mkdir p0
echo 'L2:0=0x3;1=0x3' > p0/schemata
echo exclusive > p0/mode
cat info/last_cmd_status
schemata overlaps
Сначала сужают группу по умолчанию, освобождая верхние биты, и только потом включают exclusive:
echo 'L2:0=0xfc;1=0xfc' > schemata
echo exclusive > p0/mode
После этого попытка перекрыть чужую эксклюзивную зону даст сообщение overlaps with exclusive group, и правки маски не помогут, пока занятые биты не освобождены. Одновременные изменения каталогов двух администраторов могут отдать одну и ту же свободную полосу битов двум группам сразу, поэтому последовательности чтения и записи защищают замком flock:
flock -s /sys/fs/resctrl find /sys/fs/resctrl
flock /sys/fs/resctrl ./setup-groups.sh
Замок с опцией s допускает параллельное чтение, монопольный замок без опций страхует скрипт с изменениями. Удаление каталога группы переносит все ее задачи в родительскую группу, а вложенные мониторинговые подгруппы удаляются вместе с ней.
Порядок внедрения на рабочем сервере
На практике внедрение проходит спокойнее по короткой последовательности:
- проверить флаги cat_l3, cqm_occup_llc и mba в /proc/cpuinfo и смонтировать resctrl в /sys/fs/resctrl;
- прочитать num_closids, cbm_mask и num_rmids и убедиться, что лимитов хватит на все запланированные группы;
- несколько дней собирать llc_occupancy и mbm_total_bytes по службам без ограничений, чтобы узнать фактический след каждой;
- создать контрольные группы, выставить маски и проценты полосы с запасом в 10-20 процентов от измеренного следа;
- сравнить замеры и задержки до и после, затем поджать маски там, где запас оказался избыточным.
В арсенале интерфейса есть и псевдофиксация: каталогу группы пишут pseudo-locksetup в файл mode, затем маску из неиспользуемых бит в schemata, и ядро закрепляет область кэша за устройством в /dev/pseudo_lock. Приложение отображает его через mmap и читает данные за десятки тактов: в замерах задержки основная масса чтений укладывается в 38-45 тактов. Классические cgroups делят процессорное время и память, а resctrl добавляет к ним кэш и полосу памяти, и связка двух механизмов закрывает весь набор рычагов для многопользовательского сервера. Первым делом стоит завести хотя бы пару групп и погонять мониторинг неделю: цифры занятости кэша обычно удивляют сильнее, чем проценты процессора.