Источник: Linux Block I/O debugging
14 октября 2023
Случайно я наткнулся на блог и видео Танеля Педера (Tanel Poder). Материалы превосходные, если вы хотите заняться низкоуровневой отладкой ввода-вывода в Linux — особенно рекомендую следующий пост в блоге с соответствующим видео:
- https://tanelpoder.com/posts/high-performance-block-io-on-linux/
- https://tanelpoder.com/posts/11m-iops-with-10-ssds-on-amd-threadripper-pro-workstation/
Вдохновлённый детальностью его анализа, я сам на какое-то время окунулся в изучение блочного ввода-вывода Linux. В этом посте я собираю полезные сведения и инструменты для отладки хранилищ в Linux — в основном как записную книжку для себя. Возможно, это будет полезно и вам.
В BPF Compiler Collection входит огромный набор инструментов для перехвата и наблюдения за различными точками взаимодействия userspace и ядра. Неважно, хотите ли вы отлаживать TCP-соединения, управление памятью или блочный ввода-вывод — здесь вы что-нибудь найдёте. Коллекция доступна в большинстве дистрибутивов как bcc-tools, однако скрипты часто не находятся в $PATH, и их нужно вызывать вручную из /usr/share/bcc/tools/. Все инструменты используют небольшие BPF-программы, которые загружаются в ядро, чтобы отслеживать (probe) функцию или событие и собирать о них информацию. Если вам интересно, как это делается, посмотрите классный учебник bcc для Python-разработчиков.
Memory Latency Checker полезен для проверки пропускной способности и задержек на пути от CPU к RAM. Особенно в NUMA-системах это может представлять большой интерес для поиска узких мест, поскольку пропускная способность CPU может быть ограничена, если задержка доступа к памяти слишком высока. См. также numactl и lstopo.
Что касается физического расположения компонентов машины, есть инструменты вроде lshw или lspci, которые дают хорошую информацию. Кроме того, команда lstopo --of ascii из пакета hwloc может нарисовать в терминале картину топологии оборудования.
Инструменты на 0x.tools интересны для отладки приложений в Linux. Пока я нечасто ими пользовался, но там есть хорошие примеры того, как с помощью perf получить визуализацию в виде дерева, показывающую, в каких местах кода ядра CPU проводит больше всего времени.
perf record -g -F 2 -a -o perf_log
perf report -i perf_logdstat — преемник известных инструментов вроде vmstat, iostat и ifstat. Он стремится унифицировать интерфейс, упростить использование и добавить больше информации. Полное руководство см. на https://linux.die.net/man/1/dstat. Для отладки хранилища я часто использую dstat -pcmrd, чтобы видеть IOPS и пропускную способность, вот так:
[root@test /tmp]# dstat -pcmrd
---procs--- ----total-usage---- ------memory-usage----- --io/total- -dsk/total-
run blk new|usr sys idl wai stl| used free buf cach| read writ| read writ
1.0 0 | | 594M 257M 2172k 2670M| |
1.0 0 0| 1 55 42 0 0| 594M 185M 2172k 2742M| 0 184 | 0 120M
1.0 0 0| 1 57 41 0 0| 590M 131M 2172k 2801M| 0 175 | 0 164M
1.0 0 0| 1 57 41 0 0| 587M 121M 2172k 2812M|1.00 195 |4096B 164M
1.0 0 3.0| 1 57 40 0 0| 584M 111M 2172k 2826M|1.00 234 |4095B 160M
1.0 0 0| 3 57 41 0 0| 582M 104M 2172k 2836M| 0 222 | 0 148M
1.0 0 0| 0 62 30 6 0| 578M 109M 2172k 2835M| 0 460 | 0 365M
Здесь видно, что один процесс выполняет довольно большой объём операций записи (IOPS), который занимает более половины CPU в коде ядра.
Хотя приложение отправляет ядру операции ввода-вывода с определённым размером блока, эти операции могут быть разбиты блочным слоем (block layer) ещё до того, как попадут на сами диски. Я до сих пор не знаю точно, когда именно это происходит, но предполагаю, что это делается для выравнивания операций с физическими размерами блоков. В любом случае это может изменить результаты вашего теста, особенно если вы измеряете пропускную способность или IOPS при разных размерах блоков.
Хороший способ проанализировать это — инструмент bitesize из BCC.
[root@test]# ./bitesize
Tracing block I/O... Hit Ctrl-C to end. ^C
Process Name = dd
Kbytes : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 1000 |***** |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 0 | |
64 -> 127 : 1000 |***** |
128 -> 255 : 0 | |
256 -> 511 : 3000 |***************** |
512 -> 1023 : 3000 |***************** |
1024 -> 2047 : 7000 |****************************************|
Другой метод — протестировать с заданным размером блока и проверить количество выполненных IOPS. Это, конечно, сложнее на загруженных системах. Следующий скрипт перебирает разные размеры блоков и отображает IOPS с помощью dstat:
#/bin/bash
trap "echo exiting...; exit" SIGINT
IOSIZES=${1:-512 1024 2048 4k i8k 16k 32k 64k 512k 1M 4M}
for i in ${IOSIZES}; do
echo "testing with blocksize of $i"
echo "-----------------------------"
dd oflag=direct if=/dev/urandom of=/test.img bs=$i &
timeout 5 dstat -pcmrd
killall -9 dd
echo
done