blob: 0c1fba0c5f4ea6c3ab6b461863327300be776223 [file] [log] [blame] [edit]
#!/bin/sh
#
# Author: Viresh Kumar <viresh.kumar@linaro.org>
#
# This script is used for isolating $1 (comma separated list of CPUs) CPUs from
# other kernel background activities.
#
# This runs task on the isolated CPUs and Figures out if CPUs are
# isolated or not by reading 'cat /proc/interrupts' for all interrupts.
# SCRIPT ARGUMENTS
# $1: CPUs to isolate (default 1), pass comma separated list here
# $2: number of samples to take (default 1)
# $3: Min Isolation Time Expected in seconds (default 10)
# Script arguments
ISOL_CPUS=1 # CPU to isolate, default 1. Comma-separated list of CPUs.
SAMPLE_COUNT=1 # How many samples to be taken
MIN_ISOLATION=10 # Minimum isolation expected
# Global variables
STRESS_DURATION=5000 # Run task for this duration
NON_ISOL_CPUS="0" # CPU not to isolate, zero will always be there as we can't stop ticks on boot CPU.
DEBUG_SCRIPT=1 # Print debug messages, set 0 if not required
TASK="stress" # Single threaded task to Run on Isolated CPUs
FUNC="all" # Perform complete isolation test by default
SCRIPT_VERSION=1.0 # To track release version
FILE="cat /proc/interrupts" # File to monitor for isolation
QUIRKS="" # IPI mask variable for known x86 IPI quirk, default 'null'
RESULT="PASS"
# Variables to keep an eye on total interrupt counts
old_count=0
new_count=0
# ROUTINES
# ------------------------------------
# Print debug messages, set DEBUG_SCRIPT to 0 if not required
isdebug() {
if [ $DEBUG_SCRIPT -eq 1 ]; then
$*
fi
}
# Calls routine $1 for each Isolated CPU with parameter CPU-number
for_each_isol_cpu() {
for i in `echo $ISOL_CPUS | sed 's/,/ /g'`; do
$1 $i
done
}
# Get rid of cpufreq-timer activities, pass CPU number in $1
cpufreq_fix_governor() {
# Remove governor's background timers, i.e. use performance governor
if [ -d /sys/devices/system/cpu/cpu$1/cpufreq ]; then
echo performance > /sys/devices/system/cpu/cpu$1/cpufreq/scaling_governor
fi
}
# dump all interrupts on standard output
dump_interrupts() {
[ ! $1 ] && printf "\nInitial dump of /proc/interrupts\n"
[ $1 ] && printf "\n\nInterrupted: new dump of /proc/interrupts\n"
echo "----------------------------------------------"
cat /proc/interrupts
printf "\n\n"
}
# Check $1 is isol cpu or not
is_isol_cpu() {
for i in `echo $ISOL_CPUS | sed 's/,/ /g'`; do
if [ $i = $1 ]
then
echo 1 # isol cpu found
fi
done
echo 0 # non-isol cpu
}
# update list of all non-ISOL CPUs
update_non_isol_cpus() {
total_cpus=`nproc --all --ignore=1` #ignore CPU 0 as we already have that
cpu=1
while [ $cpu -le $total_cpus ]
do
[ "$(is_isol_cpu $cpu)" == 0 ] && NON_ISOL_CPUS="$NON_ISOL_CPUS,$cpu"
let cpu=cpu+1
done
isdebug echo "Isolate: CPU "$ISOL_CPUS" and leave others: "$NON_ISOL_CPUS
isdebug echo ""
}
# Find total number of interrupts for
# - one CPU, pass cpu number as parameter
# - all CPUs, pass "ALL" as parameter
total_interrupts() {
(([ $QUIRKS ] && $FILE | egrep -v $QUIRKS) || $FILE) |
awk -v isolate_cpu="$1" '
BEGIN {
line=0;
}
# Find total CPUs, do only on first row
NR==1 {
cpus = NF;
next;
}
# Fill array with interrupt counts
{
for (cpu = 0; cpu < cpus; cpu++) {
irqs[cpu, line] = $(cpu+2);
}
line++;
}
# Count total interrupts:
END {
for (cpu = 0; cpu < cpus; cpu++) {
for (j = 0; j < line; j++) {
count[cpu] += irqs[cpu,j];
# for debugging
# printf "%d: %d: %d\n",cpu, j,irqs[cpu,j];
}
if (isolate_cpu == "ALL")
printf "%d ",count[cpu]
else if (cpu == isolate_cpu)
printf "%d ",count[cpu]
}
printf "\n"
}
# File to process
'
}
# Create per-cpu data plane cpuset
create_dplane_cpuset() {
# Create per-cpu cpuset and set important fields
[ -d /dev/cpuset/dplane/cpu$1 ] || mkdir /dev/cpuset/dplane/cpu$1
echo 0 > /dev/cpuset/dplane/cpu$1/$CPUSET_PREFIX"mems"
echo $1 > /dev/cpuset/dplane/cpu$1/$CPUSET_PREFIX"cpus"
echo 0 > /dev/cpuset/dplane/cpu$1/$CPUSET_PREFIX"sched_load_balance"
# exit early in case of non-stress app
if [ "stress" != "$TASK" ]; then
return
fi
# Move shell to isolated CPU
echo $$ > /dev/cpuset/dplane/cpu$1/tasks
# Start single cpu bound task
stress -q --cpu 1 --timeout $STRESS_DURATION &
# Move shell back to control plane CPU
echo $$ > /dev/cpuset/cplane/tasks
}
# Remove per-cpu cpusets
remove_dplane_cpuset() {
rmdir /dev/cpuset/dplane/cpu$1
}
# Update sysfs tunables to isolate CPU
update_sysfs_tunables() {
# Call cpufreq_fix_governor for each isolated CPU
for_each_isol_cpu cpufreq_fix_governor
# Affine all irqs to CPU0
for i in `find /proc/irq/* -name smp_affinity`; do
echo 1 > $i > /dev/null;
done
# Try to disable sched_tick_max_deferment
if [ -d /sys/kernel/debug -a -f /sys/kernel/debug/sched_tick_max_deferment ]; then
echo -1 > /sys/kernel/debug/sched_tick_max_deferment
echo "sched_tick_max_deferment set to:" `cat /sys/kernel/debug/sched_tick_max_deferment`
else
sysctl -e kernel.sched_tick_max_deferment=-1
fi
# Move bdi writeback workqueues to CPU0
echo 1 > /sys/bus/workqueue/devices/writeback/cpumask
# Delay the annoying vmstat timer far away (in seconds)
sysctl vm.stat_interval=1000
# Delay the annoying vmstat timer far away (in centiseconds)
sysctl vm.dirty_writeback_centisecs=100000
# Delay the annoying vmstat timer far away (in centiseconds)
sysctl vm.dirty_expire_centisecs=100000
# Shutdown nmi watchdog as it uses perf events
sysctl -w kernel.watchdog=0
}
# routine to isolate a CPU
isolate_cpu() {
isdebug echo ""
isdebug echo "Started Isolating CPUs - via CPUSETS"
isdebug echo "------------------------------------"
isdebug echo ""
# Update list of non isol CPUs
update_non_isol_cpus
# Update sysfs tunables
update_sysfs_tunables
# Check that we have cpusets enabled in the kernel
if ! grep -q -s cpuset /proc/filesystems ; then
echo "Error: Kernel is lacking support for cpuset!"
exit 1
fi
# make sure that the /dev/cpuset dir exits
# and mount the cpuset filesystem if needed
[ -d /dev/cpuset ] || mkdir /dev/cpuset
mount | grep /dev/cpuset > /dev/null || mount -t cpuset none /dev/cpuset
# Create 2 cpusets. One control plane and one data plane
[ -d /dev/cpuset/cplane ] || mkdir /dev/cpuset/cplane
[ -d /dev/cpuset/dplane ] || mkdir /dev/cpuset/dplane
# check if platform needs a prefix for cpuset
[ -f /dev/cpuset/cplane/cpus ] && CPUSET_PREFIX=""
[ -f /dev/cpuset/cplane/cpuset.cpus ] && CPUSET_PREFIX="cpuset."
# Give same mems to both
echo 0 > /dev/cpuset/cplane/$CPUSET_PREFIX"mems"
echo 0 > /dev/cpuset/dplane/$CPUSET_PREFIX"mems"
# Setup the cplane domain: CPU0
echo $NON_ISOL_CPUS > /dev/cpuset/cplane/$CPUSET_PREFIX"cpus"
# Setup the NOHZ domain: CPU1
echo $ISOL_CPUS > /dev/cpuset/dplane/$CPUSET_PREFIX"cpus"
# Try to move all processes in top set to the cplane set.
for pid in `cat /dev/cpuset/tasks`; do
if [ -d /proc/$pid ]; then
echo $pid > /dev/cpuset/cplane/tasks 2>/dev/null
if [ $? != 0 ]; then
isdebug echo -n "Cannot move PID $pid: "
isdebug echo "$(cat /proc/$pid/status | grep ^Name | cut -f2)"
fi
fi
done
# Disable load balancing on top level (otherwise the child-sets' setting won't take effect.)
echo 0 > /dev/cpuset/$CPUSET_PREFIX"sched_load_balance"
# Enable load balancing withing the cplane domain
echo 1 > /dev/cpuset/cplane/$CPUSET_PREFIX"sched_load_balance"
# But disallow load balancing within the NOHZ domain
echo 0 > /dev/cpuset/dplane/$CPUSET_PREFIX"sched_load_balance"
# Quiesce CPU: i.e. migrate timers/hrtimers away
echo 1 > /dev/cpuset/dplane/$CPUSET_PREFIX"quiesce"
# Restart $ISOL_CPUS to migrate all tasks to CPU0
# Commented-out: as we should get good numbers without this HACK
# echo 0 > /sys/devices/system/cpu/cpu$ISOL_CPUS/online
# echo 1 > /sys/devices/system/cpu/cpu$ISOL_CPUS/online
# Call create_dplane_cpuset for each isolated CPU
for_each_isol_cpu create_dplane_cpuset
}
# Count total number of interrupts for all isolated CPUs
count_interrupts_on_isol_cpus() {
temp=($*)
count=0
for i in `echo $ISOL_CPUS | sed 's/,/ /g'`; do
count=$(( $count + ${temp[i]} ))
done
echo $count
}
# Scan all interrupts again and find total for isolated-cores
refresh_interrupts() {
# Get interrupt count for all CPUs
interrupts=($(total_interrupts "ALL"))
# Find total count of all interrupts on isol CPUs
new_count=$(count_interrupts_on_isol_cpus ${interrupts[@]})
[ $1 ] && isdebug echo "Counts for all CPUs: ${interrupts[@]}, total isol-cpus interrupts: $new_count"
}
# Sense infinite isolation
sense_infinite_isolation() {
# process interrupts
refresh_interrupts "print"
old_count=$new_count
# Get time as a UNIX timestamp (seconds elapsed since Jan 1, 1970 0:00 UTC)
T1="$(date +%s)"
while [ $new_count -eq $old_count ]
do
# process interrupts
refresh_interrupts
ps h -C $TASK -o pid > /dev/null
if [ $? != 0 ]; then
T2="$(date +%s)"
T=$(($T2-$T1))
echo "Quitting. Infinite Isolation detected: No interrupts for last: $T seconds"
echo "test_case_id:Min-isolation $MIN_ISOLATION secs result:$RESULT measurement:$T units:secs"
exit
fi
done
# Interrupted, dump interrupts
isdebug dump_interrupts 1
}
# routine to report CPU isolation time
get_isolation_duration() {
isdebug echo ""
isdebug echo ""
isdebug echo "Capture Isolation time"
isdebug echo "----------------------"
isdebug echo "No. of samples requested:" $SAMPLE_COUNT", min isolation required:" $MIN_ISOLATION
isdebug echo ""
isdebug dump_interrupts
# Get time as a UNIX timestamp (seconds elapsed since Jan 1, 1970 0:00 UTC)
T2="$(date +%s)"
x=0; AVG=0; MIN=99999999; MAX=0
while [ $x -lt $SAMPLE_COUNT ]
do
let x=x+1
T1=$T2
isdebug echo "Start Time in seconds: ${T1}"
# sometimes there are two or more continuous ticks, skip them by sleeping for 100 ms.
sleep .1
# Sense infinite isolation
sense_infinite_isolation
T2="$(date +%s)"
T=$(($T2-$T1))
isdebug echo "End Time in seconds: ${T2}, time diff: "
echo "$T seconds"
isdebug echo ""
# Calculations to show results
let AVG=AVG+T
if [ $T -lt $MIN_ISOLATION -a $RESULT="PASS" ]; then
RESULT="FAIL"
fi
# Record minimum and maximum isolation
if [ $T -lt $MIN ]; then
MIN=$T
fi
if [ $T -gt $MAX ]; then
MAX=$T
fi
done
let AVG=AVG/$SAMPLE_COUNT
isdebug echo "Result:"
echo "test_case_id:Min-isolation "$MIN_ISOLATION" secs result:"$RESULT" measurement:"$AVG" units:secs"
echo "Min isolation is: "$MIN", Max isolation is: "$MAX" and Average isolation time is: "$AVG
isdebug echo ""
}
# Clear/remove all CPUsets, kill all instances of 'stess'
clear_cpusets() {
isdebug echo ""
isdebug echo "Started cleaning CPUSETS"
isdebug echo "------------------------"
isdebug echo ""
#
# Cleanup
#
# kill all instances of task
for i in `ps | grep $TASK | sed 's/^\ *//g' | cut -d' ' -f1`;
do
kill -9 $i;
done
# Try to move all from cplane back to root
for pid in `cat /dev/cpuset/cplane/tasks`; do
if [ -d /proc/$pid ]; then
echo $pid > /dev/cpuset/tasks 2>/dev/null
if [ $? != 0 ]; then
isdebug echo -n "Cannot move PID $pid: "
isdebug echo "$(cat /proc/$pid/status | grep ^Name | cut -f2)"
fi
fi
done
# Remove the CPUsets
for_each_isol_cpu remove_dplane_cpuset
# delay required b/w cpu* cpusets and dplane cpuset for some reason,
# other wise we get this: Device or resource busy
sleep .1
rmdir /dev/cpuset/cplane
rmdir /dev/cpuset/dplane
}
# Execution starts from HERE
USAGE="Usage: $0 [-hvq] [-ctfsd args] [-h <help>] [-v <script version>] [-q <quirks: X86_IPI>][-c <Comma separated isol cpulist (default cpu1)>] [-t <Task name for isolation (default stress)>] [-f <Function type options: isolate, duration, clear, all (default all)>] [-s <Number of samples to take (default 1)>] [-d <Min Isolation duration expected in seconds (default 10)>]"
# Run isolation test for $FUNC
run_func()
{
isdebug echo ""
isdebug echo "Function type: $FUNC"
case "$FUNC" in
"isolate")
isolate_cpu
;;
"duration")
get_isolation_duration
;;
"clear")
clear_cpusets
;;
"nonisol_list")
update_non_isol_cpus
;;
"all")
isolate_cpu
get_isolation_duration
clear_cpusets
;;
*)
echo "Invalid [-f] function type"
;;
esac
}
# Parse isol arguments
parse_arguments()
{
while getopts hvc:t:f:s:d:q: arguments 2>/dev/null
do
case $arguments in
h) # --help
echo "$USAGE"
exit 0
;;
v) # --script version
isdebug echo "$0 Version $SCRIPT_VERSION"
exit 0
;;
c) # --cpu (comma separated isol cpulist, default cpu1)
ISOL_CPUS=$OPTARG
;;
t) # --task (task to run, default stress)
TASK=$OPTARG
;;
f) # --func_type (Function to perform: Isolate, Duration,
# Clear, Nonisol_list, all. default all)
FUNC=$OPTARG
;;
s) # --sample_count (no of samples to take, default 1)
SAMPLE_COUNT=$OPTARG
;;
d) # --duration (min isolation time duration, default 10)
MIN_ISOLATION=$OPTARG
;;
q) # --quirk
# Known Quirks:
# - X86_IPI: Spurious IPI's break isolation
if [ $OPTARG = X86_IPI ]; then
QUIRKS="interrupts|RTR|TLB|MCE|MCP|ERR|MIS"
isdebug echo "Enabled Quriks: $OPTARG"
else
echo "Invalid Quirk passed. Valid options: X86_IPI"
fi
;;
\?) # getopts issues an error message
echo "$USAGE "
exit 1
;;
esac
done
}
# Parse isol arguments
parse_arguments $@
# Run isolation test for requested functionality
run_func