Узкие места сетевой подсистемы Linux. Тюнинг сети в Linux. Настройка производительности сети в модели NAPI и с прерываниями.

Узкие места сетевой подсистемы Linux. Тюнинг сети в Linux. Настройка производительности сети в модели NAPI и с прерываниями.

Кольцевой буфер

Кольцевые буферы, совместно используются драйвером устройства и сетевой картой. TX – есть передача данных, а RX – получение данных в кольцевом буфере. Как следует из названия, переполнение буфера просто перезаписывает существующие данные. Есть два способа переместить данные от сетевой карты до ядра: аппаратные прерывания и программные прерывания, названные SoftIRQs.

Кольцевой буфер RX используется, чтобы сохранить входящие пакеты, пока они не могут быть обработаны драйвером устройства. Драйвер устройства опустошает буфер RX, обычно через SoftIRQs, который помещает входящие пакеты в структуру данных ядра, названную sk_buff или «skb», чтобы начать свой путь через ядро и до приложения, которому принадлежит соответствующий сокет. Кольцевой буфер TX используется для хранения исходящих пакетов, которые предназначенные для отправки по проводам.

Эти кольцевые буферы находятся у основания стека и являются критическим моментом, в который может произойти удаление (drop) пакетов, что негативно влияет на производительность сети.

Прерывания и обработчики прерываний

Прерывания от аппаратных средств известны как прерывания «top-half».

Сетевые карты, как правило, работают с кольцевыми буферами (DMA ring buffer) организованными в памяти, разделяемой с процессором. Каждый входящий пакет размещается в следующем доступном буфере кольца. (DMA - Direct Memory Access (Прямой доступ к памяти) — режим обмена данными между устройствами или же между устройством и основной памятью, в котором центральный процессор (ЦП) не участвует). После этого требуется сообщить системе о появлении нового пакета и передать данные дальше, в специально выделенный буфер (Linux выделяет такие буферы для каждого пакета). Для этой цели в Linux используется механизм прерываний: прерывание генерируется всякий раз, когда новый пакет поступает в систему. Чаще используется отложенные прерывания (см. в статье Linux, принципы работы с сетевой подсистемой ). В ядро Linux начиная с версии ядра 2.6 был добавлен так называемый NAPI (New API), в котором метод прерываний сочетается с методом опроса. Сначала сетевая карта работает в режиме прерываний, но как только пакет поступает на сетевой интерфейс, она регистрирует себя в poll-списке и отключает прерывания. Система периодически проверяет список на наличие новых устройств и забирает пакеты для дальнейшей обработки. Как только пакеты обработаны, карта будет удалена из списка, а прерывания включатся снова.

Жесткие прерывания можно увидеть в /proc/interrupts, где у каждой очереди есть vector прерывания в 1-м столбце. Каждой очереди RX и TX присвоен уникальный vector, который сообщает обработчику прерываний, относительно какого NIC/queue пришло прерывание. Столбцы представляют количество входящих прерываний:

#egrep "CPU0|eth1" /proc/interrupts

           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7      

 45:   28324194          0          0          0          0          0          0          0   PCI-MSI-edge      eth1

 

SoftIRQs

Иизвестны как прерывания «bottom-half», запросы программного прерывания (SoftIRQs), являются подпрограммами ядра, которые планируется запустить в то время, когда другие задачи не должны быть прерваны. Цель SoftIRQ состоит в извлечении данных из кольцевых буферов. Эти подпрограммы, выполненные в форме процессов ksoftirqd/cpu-number и, вызывают специфичные для драйвера функции кода.

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

SoftIRQs можно контролировать следующим образом. Каждый столбец есть ЦП:

# watch -n1 grep RX /proc/softirqs

# watch -n1 grep TX /proc/softirqs

 

NAPI Polling

Про метод поллинга читаем статью Linux, принципы работы с сетевой подсистемой

 

Поиск узкого места

Отбрасывание пакетов и переполнени границ (packet drops и overruns) обычно происходит, когда буфер RX сетевой карты не может достаточно быстро опустошиться ядром. Когда скорость, с которой данные поступают из сети превышает скорость, с которой ядро забирает на обработку пакеты, сетевая карта начинает отбрасывать входящие пакеты, т.к. буфер NIC (сетевой карты) полон, и увеличивает счетчик удаления.Соответствующий счетчик можно увидеть в ethtool статистике. Основные критерии здесь - прерывания и SoftIRQs.

Для примера вывод статистики ethtool:

# ethtool -S eth3

rx_errors: 0

tx_errors: 0

rx_dropped: 0

tx_dropped: 0

rx_length_errors: 0

rx_over_errors: 3295

rx_crc_errors: 0

rx_frame_errors: 0

rx_fifo_errors: 3295

rx_missed_errors: 3295

 

Существуют различные инструменты, доступные для поиска проблемной области. Следует исследовать:

• Уровень встроенного ПО адаптера

- Следим за статистикой ethtool -S ethX

• Уровень драйвера адаптера

• Ядро Linux, IRQs или SoftIRQs

- Проверяем /proc/interrupts и /proc/net/softnet_stat

• Уровни протокола IP, TCP, UDP

- Используем netstat -s и смотрим счетчики ошибок

 

Вот некоторые типичные примеры узких мест:

  • Прерывания (IRQs) неправильно сбалансированы. В некоторых случаях служба irqbalance может работать неправильно или не работает вообще. Проверьте /proc/interrupts и удостоверьтесь, что прерывания распределены на разные ядра ЦП. Обратитесь к irqbalance руководству, или вручную сбалансируйте IRQs. В следующем примере прерывания становятся обработанными только одним процессором:

# egrep “CPU0|eth2” /proc/interrupts

CPU0 CPU1 CPU2 CPU3 CPU4 CPU5

105: 1430000 0 0 0 0 0 IR-PCI-MSI-edge eth2-rx-0

106: 1200000 0 0 0 0 0 IR-PCI-MSI-edge eth2-rx-1

107: 1399999 0 0 0 0 0 IR-PCI-MSI-edge eth2-rx-2

108: 1350000 0 0 0 0 0 IR-PCI-MSI-edge eth2-rx-3

109: 80000 0 0 0 0 0 IR-PCI-MSI-edge eth2-tx

 

 

  • Посмотрите, увеличивается ли какая-либо из колонок помимо 1-й колонки в /proc/net/softnet_stat. В следующем примере счетчик большой для CPU0, и budget должен быть увеличен:

# cat /proc/net/softnet_stat

0073d76b 00000000 000049ae 00000000 00000000 00000000 00000000 00000000 00000000 00000000

000000d2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

0000015c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 

 

  • SoftIRQs не может получать достаточное количество процессорного времени для опроса адаптера. Используйте инструменты, такие как sar, mpstat или top, чтобы определить, что отнимает много процессорного времени.
  • Используйте ethtool -S ethX, чтобы проверить определенный адаптер:

# ethtool -S eth3

rx_over_errors: 399

rx_fifo_errors: 399

rx_missed_errors: 399

 

  • Увеличение приемного буфера сокета приложения или использование буфера автоподстройки.
  • Использование большого/малого TCP или UDP размера пакетов.

 

Настройка производительности

SoftIRQ

Если выполнение программных прерываний не выполняются достаточно долго, то темп роста входящих данных может превысить возможность ядра опустошить буфер. В результате буферы NIC переполнятся, и трафик будет потерян. Иногда, необходимо увеличить длительность работы SoftIRQs (программных прерываний) с CPU. За это отвечает netdev_budget. Значение по умолчанию 300. Параметр заставит процесс SoftIRQ обработать 300 пакетов от NIC перед тем как отпустить CPU:

# sysctl net.core.netdev_budget

net.core.netdev_budget = 300

Это значение может быть удвоено, если 3-й столбец в /proc/net/softnet_stat увеличивается, что указывает, на то, что SoftIRQ не получил достаточно процессорного времени. Маленькие инкременты нормальны и не требуют настройки.

# cat softnet_stat

0073d76b 00000000 000049ae 00000000 00000000 00000000 00000000 00000000 00000000 00000000

000000d2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

0000015c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

0000002a 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

 

IRQ Balance

Балансировщик прерываний - это сервис, который может автоматически сбалансировать прерывания между ядрами процессора, основанного на реальном времени состояния системы.

Прерывания также можно раскидать по ядрам вручную.

Чтобы это сделать нужно выполнить команду echo N > /proc/irq/X/smp_affinity, где N - маска процессора (определяет какому процессору достанется прерывание), а X - номер прерывания, виден в первом столбце вывода /proc/interrupts. Чтобы определить маску процессора, нужно возвести 2 в степень cpu_N (номер процессора) и перевести в шестнадцатиричную систему. При помощи bc вычисляется так: echo "obase=16; $[2 ** $cpu_N]" | bc. Например:

#CPU0
echo 1 > /proc/irq/57/smp_affinity
echo 1 > /proc/irq/66/smp_affinity

#CPU1
echo 2 > /proc/irq/58/smp_affinity
echo 2 > /proc/irq/67/smp_affinity 

#CPU2
echo 4 > /proc/irq/59/smp_affinity
echo 4 > /proc/irq/68/smp_affinity

#CPU3
echo 8 > /proc/irq/60/smp_affinity
echo 8 > /proc/irq/69/smp_affinity

Для того, чтобы при старте системы прерывания сами настраивались, можно сделать скрипт и настроить его запуск /etc/rc.d/rc.local.

 

 Ethernet flow control

Паузы фреймов - управление потоком уровня Ethernet между адаптером и портом коммутатора. Адаптер передаст “кадры паузы”, когда RX или буферы TX станут полными. Коммутатор остановит поток данных в течение определенного промежутка времени в порядке миллисекунд. Этого времени обычно достаточно, чтобы позволить ядру опустошить интерфейсные буферы, таким образом предотвращая переполнение буфера и последующие пакетные отбрасывания или переполнения. В идеале, коммутатор буферизует входящие данные в течение такой паузы.

Однако, важно понимать, что этот уровень контроля потока только между коммутатором и адаптером. Если пакеты отброшены, более высокие уровни, такие как TCP или приложение в случае UDP и/или многоадресно переданы, должен инициировать восстановление.

Важно! Фреймы паузы и flow control (управление потоком) должны быть включены и на NIC и на порте коммутатора.

# ethtool -a eth3

Pause parameters for eth3:

Autonegotiate: off

RX: off

TX: off

# ethtool -A eth3 rx on

# ethtool -A eth3 tx on

# ethtool -a eth3

Pause parameters for eth3:

Autonegotiate: off

RX: on

TX: on

 

 

Interrupt Coalescence (отложенные прерывания)

Отложенные прерывания рассказывают нам о количестве трафика, который будет получать сетевой интерфейс, или времи, которое проходит после приема трафика, перед выдачей жесткого прерывания. Слишком ранние прерывания или слишком частые приводят к снижению производительности системы, так как ядро останавливается (или «перебивает») запущенное задание для обработки запроса прерывания от аппаратного обеспечения. Слишком поздее прерывание может привести к тому, что пакеты достаточно долго не будут обработаны ядром с сетевой карты. Большие объемы трафика могут переписать предыдущие пакеты трафика, т.е. потеря пакетов.

Самые современные NIC и драйверы поддерживают IC, и многие позволяют драйверу автоматически модерировать количество прерываний, сгенерированных аппаратными средствами. Настройки IC обычно включают два основных компонента, время и количество пакетов. Время в мкс - сколько NIC будет ожидать прежде, чем прервать ядро, а число пакетов – сколько пакетов будет ждать в приемном буфере прежде чем сработает прерывание. Отложенные прерывания NIC можно посмотреть, используя команду ethtool -c ethX и настроить через ethtool -C ethX. Адаптивный режим позволяет карте автомодерировать IC. В адаптивном режиме, драйвер инспектирует трафик и ядро настраивает прерывания на лету, предотвращая потерю пакетов.

# ethtool -c eth3

Coalesce parameters for eth3:

Adaptive RX: on TX: off

stats-block-usecs: 0

sample-interval: 0

pkt-rate-low: 400000

pkt-rate-high: 450000

rx-usecs: 16

rx-frames: 44

rx-usecs-irq: 0

rx-frames-irq: 0

Следующая команда выключает адаптивный IC, и говорит адаптеру о прерывании ядра

сразу после приема любого трафика:

# ethtool -C eth3 adaptive-rx off rx-usecs 0 rx-frames 0

 

Очередь адаптера (The Adapter Queue)

Netdev_max_backlog - очередь в ядре Linux, где трафик хранится после получения от сетевого адаптера, но перед обработкой с помощью стеков протоколов (IP, TCP, и т.д.). Для каждого ядра процессора существует одна очередь. Очередь может расти автоматически до максимального значения, указанного в netdev_max_backlog. Функция ядра netif_receive_skb () находит соответствующий ЦП для пакета и ставит пакеты в очереди того ЦП. Если очередь для этого  процессора будет полна и уже в максимальном размере, пакеты будут отброшены. Для того, чтобы тюнить это место необходимо убедиться что оно реально нужно. В /proc/net/softnet_stat в втором столбце есть счетчик, который увеличивается когда очередь переполняется. Каждая строка файла softnet_stat представляет собой ядро процессора, начиная с CPU0.

1-й столбец - количество кадров, полученных с помощью обработчика прерывания.

2-й столбец - количество фреймов, отброшенных из-за превышения netdev_max_backlog..

3-й столбец - число раз ksoftirqd исчерпал netdev_budget или процессорное время, когда нужно было еще поработать.

# cat softnet_stat

0073d76b 00000000 000049ae 00000000 00000000 00000000 00000000 00000000 00000000 00000000

000000d2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

0000015c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

0000002a 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

В текущем выводе netdev_max_backlog не нуждается в тюнинге, т.к. очередь не переполняется и нет потерь соответственно. Если же наблюдаются приащения в второй колонке, тогде увеличиваем значение очереди и снова смотрим в этот файл. Если увеличения этого значения мы видим что скорость приращений в втором столбце уменьшается то можно еще увеличить значение backlog. Повторяем этот процесс пока перестанет расти цифры в столбце 2.

Backlog изменить можно такой командой:

#sysctl -w net.core.netdev_max_backlog=X

 

Adapter RX and TX Buffer

Буфер адаптера по умолчанию обычно установлен в меньшем размере, чем максимальный. Часто, увеличить размер буфера приема RX вполне достаточно, чтобы предотвратить потери пакетов, так как это может приводит к тому, что у ядра будет немного больше времени, чтобы опустошить буфер. В результате, это может предотвратить возможные потери пакетов.

Буферы можно посмотреть так:

# ethtool -g eth3

Ring parameters for eth3:

Pre-set maximums:

RX: 8192

 RX Mini: 0

 RX Jumbo: 0

TX: 8192

Current hardware settings:

RX: 1024

RX Mini: 0

RX Jumbo: 0

TX: 512

Тут видим что кольцевой буфер RX входящих пакетов равен 1024 дескрипторам в оперативной памяти, и можно увеличить до 8192.

 

Очередь передачи (Adapter Transmit Queue Length)

Длина очереди передачи определяет количество пакетов, которые могут быть поставлены в очередь перед передачей. Значение по умолчанию 1000 - обычно достаточно для сегодняшней скорости до 10 Гбит/с или даже 40 Гбит/с сетей. Однако, если число ошибок передачи увеличиваются на адаптере, то значение можно удвоить. Используйте ip -slink, чтобы увидеть, если есть какие-то потери на очереди TX для адаптера.

Увеличить длину очереди можно так:

# ip link set dev em1 txqueuelen 2000

 

TCP Window Scaling (масштабирование окна TCP)

Размер TCP окна (TCP Window Size) – количество октетов (начиная с номера подтверждения), которое принимающая сторона готова принять в настоящий момент без подтверждения. На стадии установления соединения рабочая станция и сервер обмениваются значениями максимального размера TCP окна (TCP Window Size), которые присутствуют в пакете. Например, если размер окна получателя равен 16384 байта, то отправитель может отправить 16384 байта без остановки. Принимая во внимание, что максимальная длина сегмента (MSS) может быть 1460 байт, то отправитель сможет передать данный объем в 12 фреймах, и затем будет ждать подтверждение доставки от получателя и информации по обновлению размера окна. Если процесс прошел без ошибок, то размер окна может быть увеличен. Таким образом, реализуется размер скользящего окна в стеке протокола TCP. В современных версиях операционных систем можно увеличить размер окна TCP Window Size и включить динамическое изменение окна в зависимости от состояния канала связи. Динамическое увеличение и уменьшение размера окна является непрерывным процессом в TCP и определяет оптимальный размер окна для каждого сеанса. В очень эффективных сетях размеры окна могут стать очень большими, потому что данные не теряются.

Масштабирование Окна TCP включено по умолчанию:

# sysctl net.ipv4.tcp_window_scaling net.ipv4.tcp_window_scaling = 1

 

TCP буфер

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

sk_rmem_alloc = { counter = 121948 },

sk_wmem_alloc = { counter = 553 },

sk_omem_alloc = { counter = 0 },

где:

sk_rmem_alloc – очередь получения

sk_wmem_alloc – очередь передачи

sk_omem_alloc - out-of-order queue

Существует также sk_rcvbuf переменная, которая является пределом, измеренный в байтах, что сокет может получить. В этом случае sk_rcvbuf = 125336.

Из приведенного выше вывода можно вычислить, что очередь получения почти полна. Когда sk_rmem_alloc > sk_rcvbuf то буфер начинает рушится, т.е. наблюдаются потери пакетов. Выполните следующую команду, чтобы определить, происодит это или нет:

# netstat -sn | egrep "prune|collap"; sleep 30; netstat -sn | egrep "prune|collap"

17671 packets pruned from receive queue because of socket buffer overrun

18671 packets pruned from receive queue because of socket buffer overrun

Если счетчик обрезки пакетов растет, то требуется тюнинг.

tcp_rmem: У настраиваемой памяти сокета есть три значения, описывающих минимальное, значение по умолчанию и максимальное значения в байтах. Чтобы просмотреть эти настройки и увеличить:

# sysctl net.ipv4.tcp_rmem

4096 87380 4194304

# sysctl -w net.ipv4.tcp_rmem=“16384 349520 16777216”

# sysctl net.core.rmem_max 4194304

# sysctl -w net.core.rmem_max=16777216

 

TCP Listen Backlog: отвечает за размер очереди одновременно ожидающих подключений к сокету, то есть инициированных (SYN - SYN, ACK - ACK), но еще не принятых сервером (established).

Параметр ядра net.core.somaxconn  - максимальное число открытых сокетов, ждущих соединения. Изменяем:

# sysctl net.core.somaxconn

net.core.somaxconn = 128

# sysctl -w net.core.somaxconn=2048

net.core.somaxconn = 2048

# sysctl net.core.somaxconn

net.core.somaxconn = 2048

 

UDP Buffer Tuning: UDP является гораздо менее сложным, чем протокол TCP. Поскольку UDP не содержит надежности сеанса, он не несет ответственности за повторную передачу потерянных пакетов. Там не существует понятия размера окна и потерянные данные не восстанавливается протоколом. Единственная доступная настройка включает в себя увеличение размера приемного буфера. Если netstat -us показывает “packet receive errors” , попробуйте увеличить число буферов для приема. Буферы UDP могут быть настроены таким образом:


# sysctl net.core.rmem_max

124928

 # sysctl -w net.core.rmem_max=16777216

После изменения максимального размера, требуется перезапуск приложения.

 

unix-way