Как тестировать память быстрей?

97f3c0061d5967be72b69ef3b40ffc55.png

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

Для диагностики модулей есть хорошая программа memtest86+, но если памяти у нас 1ТБ, то полное тестирование растягивается на несколько дней, а бизнес не может так долго ждать.

Как же быть? В этой публикации я поделюсь опытом тестирования памяти сервера Gigabyte R292–4S0 с СУБД на Enteprice Linux 8 (EL8) и 1 ТБ памяти двумя методами:

  • С EFI загрузкой memtest86+ v7;

  • С автоматизированным созданием сотни libvirt-KVM виртуальных машин с memtest86+ внутри.

Запуск memtest внутри виртуальной машины… «Фу…», — скажут некоторые. И будут неправы:

  • На Хабре описан успешный пример такого запуска в VirtualBox;

  • У меня есть положительный опыт с автоматизированной PXE загрузкой сотни виртуальных машин с memtest в ESXi. Гипервизор с ошибкой в памяти падал за минуты, пока за несколько тестов мы не вычислили неисправный модуль. А с рабочими («боевыми») виртуальными машинами сбой проявлялся раз в неделю;

  • Такое тестирование по моему подсчету охватывает ~97% памяти, и велик шанс того, что именно тут есть сбойные ячейки;

  • Такое тестирование существенно быстрей.

Приступим.

Нам понадобится сам memtest86+, его можно найти на сайте https://memtest.org. Можно было бы попробовать установить его через yum. Но, увы, в тестируемом мной EL8 так установился только memtest86+ v5.01, который EFI загрузку вообще не поддерживает. Нужна версия memtest 6 или новее.

EFI загрузка memtest+

Скачиваем Binary Files (.bin/.efi), распаковываем memtest64.efi в созданную папку /boot/efi/EFI/memtest (/boot/efi — примонтированная ФС VFAT). Мы готовы к EFI-загрузке .

Как загрузить memtest через EFI-загрузчик? На тестируемом сервере Gigabyte во время запуска нажимаем F10, попадаем в меню выбора загрузочного устройства, в нем выбираем EFI-shell.

В EFI-shell нужны следующие команды:

  1. map покажет, какие FAT-совместимые файловые системы у нас есть;

  2. fs0: (с двоеточием) переключится на первую файловую систему (ранее она у нас была смонтирована в /boot/efi);

  3. cd EFI/memtest поменяет текущую папку на ту, куда вы положили memtest (тут даже работает автодополнение TAB, удобно);

  4. ls позволит посмотреть, какие файлы есть в текущей папке;

  5. memtest64.efi — имя скачанного бинарного файла, который нужно запустить.

После этого загрузится сам memtest, в моем случае он еще несколько минут просто так показывал зависший экран и ничего не делал. Видимо, определял объем работы. Затем работа пошла и продолжалась… 95 часов (4 дня) до первого прохода!

0b565d691c1947a6363e9b5d0a538f8f.png

libvirt-KVM загрузка memtest86+

Для загрузки memtest внутри виртуальных машин нам потребуется добавить в наш EL8 дополнительные утилиты для управления kernel-virtual-machine. Запустим сразу libvirtd.service и уберем поднятый внутренний сетевой мост default — сеть нам тут не пригодится.

yum install -y qemu-kvm libvirt virt-install virt-manager virt-viewer qemu-img 
systemctl enable --now libvirtd.service
virsh net-autostart default --disable 
virsh net-destroy default 

Создадим пустую папку, например, /opt/vm-memtest, положим в нее уже знакомый memtest64.efi. Далее в ней же необходимо создать пустой файл empty.

touch empty

В этой же папке создадим скрипт запуска, который будет использовать подобную команду:

virt-install -n memtest1 \
            --memory 12288 \
            --vcpus 2 \
            --disk "none" \
            --network "none" \
            --os-variant "detect=off,name=generic" \
            --location "/opt/vm-memtest,kernel=memtest64.efi,initrd=empty" \
            --noautoconsole \
            --serial file,path=/opt/vm-memtest/console-out-vm-memtest1 \
            --extra-args "console=ttyS0,115200 console=tty0"

Разберем, что она делает:

  • задаст имя новой виртуальной машины — memtest1;

  • выделит 12GB RAM;

  • выделит 2vCPU;

  • не будет выделять диск;

  • не будет подключать сетевые адаптеры;

  • отключит контроль успешной загрузки ОС со стороны virt-install;

  • запустит ядро из /opt/vm-memtest/memtest64.efi с пустым initrd файлом (без него virt-install не получится, empty — это костыль);

  • не будет открывать консоль виртуальной машины в virt-viewer;

  • подключит COM-порт из файла /opt/vm-memtest/console-out-vm-memtest1 (файл будет создан автоматически);

  • ядру ОС виртуальной машины будут переданы параметры загрузки о том, что свой вывод нужно дублировать как в указанный COM-порт (файл), так и на консоль tty0 (к ней можно подключиться через virt-viewer, если захочется).

Объём памяти в 12ГБ появился удвоением (2vcpu в вм) деления количества памяти в системе на общее количество vcpu в 4-х сокетном сервере.

До создания виртуальных машин нужно остановить «боевые» приложения и не забыть, что СУБД (если она у вас есть) должна вернуть свои huge pages в ОС, отключить swap, а еще сбросить все КЭШи из памяти на диск:

swapoff -a
sysctl -w vm.nr_hugepages=0
sync
echo 3 > /proc/sys/vm/drop_caches

Конечный скрипт запуска получился такой:

vm-manage.sh

#!/bin/bash

# Written by Alex Golikov 2023.11.22

## Prepare host to provide KVM service

# yum install -y qemu-kvm libvirt virt-install virt-manager virt-viewer qemu-img 
# systemctl enable --now libvirtd.service
# virsh net-autostart default --disable 
# virsh net-destroy default 

## To get Memory per One vCPU in this system run:
# awk '/MemTotal/{ printf("%.0f\n",$2/1024/'$(grep -c processor /proc/cpuinfo)') }' /proc/meminfo

vcpu_per_vm=2
memory_per_vm=12288
vmprefix="memtest"

## uncomment to limit VM number
#max_vm_number=3

#create lastvm to spend all the rest available memory
lastvm=true

#memtest_kernel="memtest.x64.efi.6.20"
memtest_kernel="memtest.x64.efi"

startvm() {
 
   #avail_mem=$(awk '/MemAvailable/{ printf("%.0f",$2/1024) }' /proc/meminfo)
   avail_mem=$(free -m | awk '/Mem/{print $7}')

   #calculate vm_num (number of VMs to create)
   vm_num=$(($avail_mem / $memory_per_vm))
   [[ -z "${vm_num}" ]] && echo "Unable to calculate vm_num (number of VMs to create)" && exit 1

   #Check vm limit variable
   [[ -n ${max_vm_number} ]] && [[ ${vm_num} -gt ${max_vm_number} ]] && vm_num=${max_vm_number}

   #Check current memtest VMs that have already created before
   current_vm_number=$(virsh list --all| awk '{if($2~/'${vmprefix}'/) {print $2}}' | sed 's|'${vmprefix}'||' | sort -n | tail -1)
   [[ -z "${current_vm_number}" ]] && current_vm_number=0

   echo "Creating ${vm_num} VMs with ${memory_per_vm}MB onboard to test ${avail_mem}MB of RAM."

   for (( i=$((${current_vm_number}+1)) ; i<=$((${vm_num} + ${current_vm_number})); i++ )); do
      echo -e "##############\n\nCreating ${vmprefix}${i}..."
      virt-install -n ${vmprefix}${i} \
                --memory ${memory_per_vm} \
                --vcpus ${vcpu_per_vm} \
                --disk "none" \
                --network "none" \
                --os-variant "detect=off,name=generic" \
                --location "${MyDir},kernel=${memtest_kernel},initrd=empty" \
                --noautoconsole \
                --serial file,path=${MyDir}/console-out-vm-${vmprefix}${i} \
                --extra-args "console=ttyS0,115200 console=tty0"
   done
}


stopvm() {

        for vm in $(virsh list --all | awk '{if($2~/'${vmprefix}'/) print $2}'); do
                echo -e "##############\n\nRemoving $vm"
                virsh destroy $vm 
                virsh undefine $vm 
        done
        echo -e "##############\n\nWork completed"

}


checklogs() {
        for console_out in $(ls -1 console-out-vm-*); do
                echo -en "\n${console_out}        "
                sed -n 's/.*\(Time: [ 0-9:]*\).*\(Pass: [ 0-9]*\).*\(Errors: [0-9]*\).*/\1 \2 \3/p' $console_out
        done
        echo -e "\n\nRun command 'rm -f ${MyDir}/console-out-vm-*' to remove VM console logs"
}

[[ "$1" =~ start|stop|check ]] || { echo "Usage: $0 {start|stop|check}"; exit 1; }

MyDir=$(dirname "$0") && cd "${MyDir}" && MyDir="$(pwd)"

[[ "$1" == "start" ]] && {
        startvm
        [[ "${lastvm}" == "true" ]] && {
              memory_per_vm=$(free -m | awk '/Mem/{print $7 - 100 }')
              #memory_per_vm=$(awk '/MemAvailable/{ printf("%.0f",$2/1024 - 100) }' /proc/meminfo)

              #Create one more VM if we have at least 500MB of RAM left
              [[ "${memory_per_vm}" -gt 500 ]] && {
                   echo "Spending the last ${memory_per_vm}MB"
                   startvm 
              }
        }
        echo -e "##############\n\nWork completed:"
        virsh list --all | awk '{if($2~/'${vmprefix}'/) print $0}'
        free -m
        }
[[ "$1" == "stop"  ]] && stopvm
[[ "$1" == "check" ]] && checklogs

Команда запуска

./vm-manage.sh start 

последовательно запустит почти сотню виртуальных машин:

5c42df784de3b5530789d35e0368c128.png

Команда анализа выводов в консолях виртуальных машин:

./vm-manage.sh check

Команда остановки всех созданных виртуальных машин:

./vm-manage.sh stop

Память во всех запущенных виртуальных машинах была протестирована за 5 часов, что существенно быстрей, чем на barre-metall.

Выводы

Сравним два описанных метода.

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

Об этом можно косвенно судить даже по скорости оборотов вентиляторов сервера. Во время нативного исполнения memtest их скорость отображалась как 10К RPM, а с libvirt они же разогнались до 20K RPM.»Здесь мерилом работы считают усталость», — посмеются некоторые, но в случае с нативным исполнением memtest сервер никак не выдает метрики загрузки своих процессоров; приходится выкручиваться.

Метод тестирования через виртуальные машины охватывает ~97% памяти, нагружает процессоры целиком (зафиксировано с node_exporter), и в большинстве случаев этот результат будет достаточным, чтобы с стресс-тесте отбраковать неисправный модуль. Основной упор на то, что метод должен быстро воспроизводить неисправность. Ведь серия тестов может быть длинной, возможно придется несколько раз менять конфигурацию памяти в сервере перед тем, как мы найдем «виновный» модуль.

Проверка с применением виртуализации потенциально может выявить какие-то проблемы, но она абсолютно не гарантирует, что вся память исправна. Зачем тогда проверять? Затем, что этот метод раскрывает себя хорошо на системах, которые иногда сами сбоят из-за ошибок в памяти. Данный метод позволяет относительно быстро локализовать неисправность, повышая её воспроизводимость.

© Habrahabr.ru