1#!/usr/local/bin/bash 2# Copyright 2017 Google Inc. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16# For a single-queue / no MSI-X virtionet device, sets the IRQ affinities to 17# processor 0. For this virtionet configuration, distributing IRQs to all 18# processors results in comparatively high cpu utilization and comparatively 19# low network bandwidth. 20# 21# For a multi-queue / MSI-X virtionet device, sets the IRQ affinities to the 22# per-IRQ affinity hint. The virtionet driver maps each virtionet TX (RX) queue 23# MSI-X interrupt to a unique single CPU if the number of TX (RX) queues equals 24# the number of online CPUs. The mapping of network MSI-X interrupt vector to 25# CPUs is stored in the virtionet MSI-X interrupt vector affinity hint. This 26# configuration allows network traffic to be spread across the CPUs, giving 27# each CPU a dedicated TX and RX network queue, while ensuring that all packets 28# from a single flow are delivered to the same CPU. 29 30function is_decimal_int() { 31 [ "${1}" -eq "${1}" ] > /dev/null 2>&1 32} 33 34function set_channels() { 35 ethtool -L "${1}" combined "${2}" > /dev/null 2>&1 36} 37 38echo "Running $(basename $0)." 39NET_DEVS=/sys/bus/virtio/drivers/virtio_net/virtio* 40 41# Loop through all the virtionet devices and enable multi-queue 42if [ -x "$(command -v ethtool)" ]; then 43 for dev in $NET_DEVS; do 44 ETH_DEVS=${dev}/net/* 45 for eth_dev in $ETH_DEVS; do 46 eth_dev=$(basename "$eth_dev") 47 if ! errormsg=$(ethtool -l "$eth_dev" 2>&1); then 48 echo "ethtool says that $eth_dev does not support virtionet multiqueue: $errormsg." 49 continue 50 fi 51 num_max_channels=$(ethtool -l "$eth_dev" | grep -m 1 Combined | cut -f2) 52 [ "${num_max_channels}" -eq "1" ] && continue 53 if is_decimal_int "$num_max_channels" && \ 54 set_channels "$eth_dev" "$num_max_channels"; then 55 echo "Set channels for $eth_dev to $num_max_channels." 56 else 57 echo "Could not set channels for $eth_dev to $num_max_channels." 58 fi 59 done 60 done 61else 62 echo "ethtool not found: cannot configure virtionet multiqueue." 63fi 64 65for dev in $NET_DEVS 66do 67 dev=$(basename "$dev") 68 irq_dir=/proc/irq/* 69 for irq in $irq_dir 70 do 71 smp_affinity="${irq}/smp_affinity_list" 72 [ ! -f "${smp_affinity}" ] && continue 73 # Classify this IRQ as virtionet intx, virtionet MSI-X, or non-virtionet 74 # If the IRQ type is virtionet intx, a subdirectory with the same name as 75 # the device will be present. If the IRQ type is virtionet MSI-X, then 76 # a subdirectory of the form <device name>-<input|output>.N will exist. 77 # In this case, N is the input (output) queue number, and is specified as 78 # a decimal integer ranging from 0 to K - 1 where K is the number of 79 # input (output) queues in the virtionet device. 80 virtionet_intx_dir="${irq}/${dev}" 81 virtionet_msix_dir_regex=".*/${dev}-(input|output)\.([0-9]+)$" 82 if [ -d "${virtionet_intx_dir}" ]; then 83 # All virtionet intx IRQs are delivered to CPU 0 84 echo "Setting ${smp_affinity} to 01 for device ${dev}." 85 echo "01" > "${smp_affinity}" 86 continue 87 fi 88 # Not virtionet intx, probe for MSI-X 89 virtionet_msix_found=0 90 for entry in ${irq}/${dev}*; do 91 if [[ "$entry" =~ ${virtionet_msix_dir_regex} ]]; then 92 virtionet_msix_found=1 93 queue_num=${BASH_REMATCH[2]} 94 fi 95 done 96 affinity_hint="${irq}/affinity_hint" 97 [ "$virtionet_msix_found" -eq 0 -o ! -f "${affinity_hint}" ] && continue 98 99 # Set the IRQ CPU affinity to the virtionet-initialized affinity hint 100 echo "Setting ${smp_affinity} to ${queue_num} for device ${dev}." 101 echo "${queue_num}" > "${smp_affinity}" 102 real_affinity=`cat ${smp_affinity}` 103 echo "${smp_affinity}: real affinity ${real_affinity}" 104 done 105done 106 107XPS=/sys/class/net/e*/queues/tx*/xps_cpus 108num_cpus=$(nproc) 109 110num_queues=0 111for q in $XPS; do 112 num_queues=$((num_queues + 1)) 113done 114 115# If we have more CPUs than queues, then stripe CPUs across tx affinity 116# as CPUNumber % queue_count. 117for q in $XPS; do 118 queue_re=".*tx-([0-9]+).*$" 119 if [[ "$q" =~ ${queue_re} ]]; then 120 queue_num=${BASH_REMATCH[1]} 121 fi 122 123 xps=0 124 for cpu in `seq $queue_num $num_queues $((num_cpus - 1))`; do 125 xps=$((xps | (1 << cpu))) 126 done 127 128 # Linux xps_cpus requires a hex number with commas every 32 bits. It ignores 129 # all bits above # cpus, so write a list of comma separated 32 bit hex values 130 # with a comma between dwords. 131 xps_dwords=() 132 for i in $(seq 0 $(((num_cpus - 1) / 32))) 133 do 134 xps_dwords+=(`printf "%08x" $((xps & 0xffffffff))`) 135 done 136 xps_string=$(IFS=, ; echo "${xps_dwords[*]}") 137 138 139 echo ${xps_string} > $q 140 printf "Queue %d XPS=%s for %s\n" $queue_num `cat $q` $q 141done | sort -n -k2 142