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