Расширители памяти на базе CXL приносят в сервер свежий пласт отказов, который стандартные средства мониторинга почти не видят. В dmesg изредка мелькают короткие строки про AER и Corrected error, и на этом явные следы заканчиваются, хотя устройство уже несколько недель копит исправимые ошибки в собственных журналах. Разрозненные сообщения превращаются в понятный профиль, если знать три канала отчетности и считать события по каждому из них отдельно. Статья показывает, как проверить поддержку в ядре, где лежат счетчики AER, как включить точки трассировки событий памяти и как связать все это со службой rasdaemon и ее базой SQLite. В конце разобраны числовые примеры: вычисление скорости деградации, выбор порогов для заявок вендору и проверка всего конвейера на тестовом стенде.

Три независимых канала ошибок CXL от шины AER до журналов устройства

CXL-устройство, обычно расширитель памяти класса Type 3 на карте с интерфейсом PCIe, сообщает о проблемах по трем отдельным каналам, и каждый отвечает за свой слой. Первый канал работает на уровне шины. Механизм AER классифицирует ошибки линка и протокола по трем классам: Correctable, Uncorrectable Non-Fatal и Uncorrectable Fatal. Исправимые события оседают в счетчиках порта и конечного устройства, неисправимые дублируются в системный журнал строками вида "AER: Corrected error received: 0000:0c:00.0".

Второй канал находится внутри самого устройства. По стандарту CXL оно держит три журнала событий, informational, warning и fatal, а записи делятся на несколько типов, среди которых General Media Event Record, DRAM Event Record и Memory Module Event Record. В запись попадают физический адрес DPA внутри устройства, отметка времени по внутренним часам, идентификаторы компонента и детальные флаги ошибки. Драйвер cxl вычитывает журналы через mailbox-команды, подписывается на события по прерываниям MSI-X и превращает каждую запись в точку трассировки ядра.

Третий канал определяет влияние на систему. Неисправимые ошибки памяти доезжают до архитектурных обработчиков MCA и интерфейса APEI, где оформляются в записи CPER, а исправимые ошибки хостового контроллера учитывает подсистема EDAC. Для профиля отказов разделение решает все: рост счетчиков AER указывает на канал связи и топологию, рост записей DRAM-журнала говорит о микросхемах памяти, и меры в этих двух случаях совершенно разные.

Проверка поддержки CXL и AER в ядре и на шине PCI перед замерами

Замеры бессмысленны, если ядро собрано без нужных компонентов, поэтому проверку начинают с конфигурации. Достаточно посмотреть на параметры CONFIG_CXL_PCI, CONFIG_CXL_MEM и CONFIG_PCIEAER в конфиге текущего ядра:

grep -E "CONFIG_CXL_PCI=|CONFIG_CXL_MEM=|CONFIG_PCIEAER=" /boot/config-$(uname -r)

Ожидается ответ вида CONFIG_CXL_PCI=m и CONFIG_PCIEAER=y, хотя серверные сборки часто компилируют поддержку монолитно. Дальше убеждаются, что модуль загружен, а шина видит устройство:

modprobe cxl_pci
lspci -nn | grep -iE "cxl|memory expander"

Типичный вывод содержит строку с классом "Memory controller" и подписью вендора. Для конкретного устройства смотрят расширенный статус:

lspci -vvv -s 0c:00.0

Среди возможностей конечного устройства ищут блок Advanced Error Reporting. Ключевые строки выглядят так:

Capabilities: [100 v2] Advanced Error Reporting
        UESta:  00000000
        CESta:  00000002

Единица в CESta означает, что исправимая ошибка произошла прямо сейчас и еще не снята. Последняя проверка затрагивает точки трассировки:

ls /sys/kernel/tracing/events/cxl/

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

Мгновенные счетчики AER в sysfs и расширенный вывод lspci

У каждого устройства с активным AER в sysfs появляются три файла-счетчика: aer_dev_correctable, aer_dev_nonfatal и aer_dev_fatal. Читать их нужно по одному:

cat /sys/bus/pci/devices/0000:0c:00.0/aer_dev_correctable

Вывод делится на сумму и разбивку по типам ошибок:

Total 27
Replay Timer Timeout 19
REPLAY_NUM Rollover 6
BadTLP 2

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

date +%s >> /var/log/aer-0c00.log
cat /sys/bus/pci/devices/0000:0c:00.0/aer_dev_correctable >> /var/log/aer-0c00.log

Неисправимые события читают из файла aer_dev_fatal. Типичный снимок после единственного инцидента выглядит скромно:

Total 1
Completion Timeout 1

Живое состояние битов, не попавших в счетчики, показывает секция AER в выводе lspci -vvv. Смотреть стоит и на верхний порт, например 0000:0b:00.0, и на само устройство: ошибки повторных передач часто считает порт, а не конечное устройство, и без пары точек измерения легко приписать деградацию не тому звену. Подробная разбивка по типам подсказывает характер: доминирование Replay Timer Timeout говорит о потерях пакетов на линии, рост BadTLP указывает на битые кадры, а превышение счетчиков на коммутаторе переводит поиск в сторону кабелей и коннекторов.

Точки трассировки ядра для событий памяти CXL

Каталог /sys/kernel/tracing/events/cxl на современных ядрах содержит шесть точек: cxl_aer_correctable_error и cxl_aer_uncorrectable_error для шины, cxl_general_media, cxl_dram и cxl_memory_module для журналов устройства, cxl_poison для списка поврежденных адресов. Разовый захват на работающей системе делается тремя строками:

cd /sys/kernel/tracing
echo 1 > events/cxl/enable
cat trace_pipe

Каждая строка несет имя устройства вида mem0, тип журнала, физический адрес DPA и набор флагов записи. Полный состав полей проверяют в файле format конкретной точки:

cat /sys/kernel/tracing/events/cxl/cxl_dram/format

Для длительного сеанса удобнее perf или trace-cmd, они меньше тормозят систему и складывают данные в файл:

perf list 'cxl:*'
perf record -e 'cxl:*' -a -- sleep 3600
perf script | wc -l
trace-cmd record -e cxl sleep 600
trace-cmd report | grep -c cxl_dram

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

perf script | grep cxl_dram | grep -c mem0

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

Служба rasdaemon и постоянная база событий в SQLite

Ручные сессии ftrace хороши для разового диагноза, а непрерывный профиль требует постоянного сборщика. Им служит rasdaemon, служба, которая слушает трассировки ядра и складывает события в базу SQLite. Пакет есть во всех крупных дистрибутивах, а поддержка журналов CXL появилась в ветке 0.8:

apt install rasdaemon
systemctl enable --now rasdaemon
ras-mc-ctl --summary

Команда summary печатает сводку по памяти и накопленным ошибкам, а сырые записи CXL лежат в отдельной базе:

ls -l /var/lib/rasdaemon/ras-mc_event.db
sqlite3 /var/lib/rasdaemon/ras-mc_event.db "select count(*) from cxl_general_media_event;"
sqlite3 /var/lib/rasdaemon/ras-mc_event.db "select * from cxl_dram_event limit 3;"

Первая выборка дает число записей общего медиажурнала с момента создания базы, вторая показывает структуру полей: время, устройство, адрес DPA, тип журнала и флаги. Недельную скорость считают ограничением выборки по времени через поле timestamp. Отдельно ras-mc-ctl --error-counters печатает накопленные исправимые ошибки по каждому модулю, что позволяет сравнить хостовую память и расширители CXL в одной таблице. База переживает перезагрузки, поэтому именно она служит источником для месячных профилей и для сверки с графиками нагрузки.

Перевод сырых событий в метрики скорости деградации с числовыми порогами

Сырое число событий само по себе ни о чем не говорит, профиль строится на скорости и кластеризации. Первый шаг: измерить окно. Пусть за 30 дней аптайма счетчик Total исправимых событий вырос с 14 до 63. Разница в 49 событий дает 49/30 = 1.6 события в сутки. Если за предыдущий месяц скорость была 0.4 события в сутки, рост в четыре раза за один месяц, и это уже повод открыть заявку, даже когда каждая отдельная ошибка исправима. Второй шаг: проверить адреса в записях DRAM-журнала. Серия ошибок, укладывающаяся в диапазон 64 МиБ вокруг одного DPA, почти всегда означает дефектную строку конкретной микросхемы, а рассеянные адреса чаще говорят о проблеме питания или температуры.

Для реакций используют простые правила, выверенные на двух-трех месяцах наблюдений:

  1. прирост исправимых событий AER выше 10 в сутки на одном устройстве в течение трех дней подряд означает заявку вендору и вывод модуля из горячего пула;
  2. любая запись DRAM-журнала с неисправимой ошибкой за сутки требует переноса данных и сервисного окна;
  3. запись класса fatal в любом журнале устройства означает немедленную диагностику без ожидания следующего окна;
  4. повтор ошибок в узком диапазоне DPA в пределах 64 МиБ означает закрепление дефекта и замену модуля.

Смысл порогов подкрепляют бюджетом простоя. Доступность узла 99.95% оставляет 4.38 часа в год, то есть 262 минуты. Одна неисправимая ошибка с перезагрузкой и возвратом узла в строй съедает 10-15 минут, а вместе с диагностикой легко утраивает это время. С запасом в три раза безопасный потолок получается около 5-6 событий в год на узел, поэтому даже редкие записи неисправимых ошибок в журналах CXL оправдывают срочную проверку.

Лабораторная проверка конвейера через EINJ и poison-список устройства

Прежде чем доверять профилю, канал стоит испытать синтетикой. Интерфейс EINJ из подсистемы ACPI APEI позволяет сгенерировать ошибку памяти программно, если прошивка платформы его поддерживает. На тестовом стенде проверяют доступность интерфейса и список поддерживаемых типов:

mount -t debugfs none /sys/kernel/debug
cat /sys/kernel/debug/apei/einj/available_error_type

Значение 0x10 соответствует исправимой ошибке памяти, а маска в param2 ограничивает область внедрения одним адресом:

echo 0x10 > /sys/kernel/debug/apei/einj/error_type
echo 0x273b1e00 > /sys/kernel/debug/apei/einj/param1
echo 0xffffffff > /sys/kernel/debug/apei/einj/param2
echo 1 > /sys/kernel/debug/apei/einj/error_inject

После внедрения наблюдают за журналом в реальном времени:

journalctl -kf | grep -iE "aer|edac|cxl|memory error"

Если строки появились, а счетчик aer_dev_correctable или EDAC-счетчик увеличился, конвейер работает целиком. Второй способ проверяет именно CXL-часть: устройство умеет выдавать список поврежденных адресов по запросу. Запись в специальный файл запускает опрос, и результат приходит через точку cxl_poison:

echo 1 > /sys/bus/cxl/devices/mem0/trigger_poison_list
cat /sys/kernel/tracing/trace_pipe | grep cxl_poison

Пустой poison-список на новом модуле, пара записей после месяцев работы и десятки записей на деградирующем устройстве образуют третью ось профиля, независимую от счетчиков AER.

Остается перевести разовые замеры в постоянный мониторинг, потому что собранные однажды числа быстро устаревают. Самый простой путь: тот же скрипт с cron, что пишет счетчики AER, дополнительно опрашивает базу rasdaemon и складывает три значения в файл для системы метрик, например для экспортера Prometheus в формате textfile. Числа из примеров выше дают готовую схему: Total по исправимым событиям AER, число записей DRAM-журнала за сутки и число записей в poison-списке. Дальше строится график вместе с нагрузкой, и кривые начинают разговаривать. Всплеск событий на фоне спокойной нагрузки чаще означает физическую деградацию, а рост событий, повторяющий кривую нагрузки, указывает на ошибку прошивки или конфликты адресов в топологии. Через два-три месяца у каждого устройства появляется собственная линия с характерным уровнем, и решение о замене модуля перестает быть гаданием: пороги уже выверены на реальных числах, а не взяты из чужих таблиц. Так разрозненные строки в dmesg превращаются в управляемый процесс, где каждое событие учтено, а каждая заявка вендору подкреплена статистикой.