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

от автора

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

Для диагностики модулей есть хорошая программа 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 дня) до первого прохода!

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="memtestx64.efi.6.20" memtest_kernel="memtest64.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 

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

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

./vm-manage.sh check

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

./vm-manage.sh stop

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

Выводы

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

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

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

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/776268/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *