diff options
author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-07-25 07:43:04 +0200 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-07-25 07:43:04 +0200 |
commit | 314820c9e892d8f41ba4db300ec96770d9c8294b (patch) | |
tree | 3d5c59a429239b180c77e57f998a78d3f2b79827 /tools/testing | |
parent | Input: xpad - add Andamiro Pump It Up pad (diff) | |
parent | Input: add MELFAS mms114 touchscreen driver (diff) | |
download | linux-314820c9e892d8f41ba4db300ec96770d9c8294b.tar.xz linux-314820c9e892d8f41ba4db300ec96770d9c8294b.zip |
Merge branch 'next' into for-linus
Diffstat (limited to 'tools/testing')
19 files changed, 2380 insertions, 6 deletions
diff --git a/tools/testing/ktest/examples/README b/tools/testing/ktest/examples/README new file mode 100644 index 000000000000..a12d295a09d8 --- /dev/null +++ b/tools/testing/ktest/examples/README @@ -0,0 +1,32 @@ +This directory contains example configs to use ktest for various tasks. +The configs still need to be customized for your environment, but it +is broken up by task which makes it easier to understand how to set up +ktest. + +The configs are based off of real working configs but have been modified +and commented to show more generic use cases that are more helpful for +developers. + +crosstests.conf - this config shows an example of testing a git repo against + lots of different architectures. It only does build tests, but makes + it easy to compile test different archs. You can download the arch + cross compilers from: + http://kernel.org/pub/tools/crosstool/files/bin/x86_64/ + +test.conf - A generic example of a config. This is based on an actual config + used to perform real testing. + +kvm.conf - A example of a config that is used to test a virtual guest running + on a host. + +snowball.conf - An example config that was used to demo ktest.pl against + a snowball ARM board. + +include/ - The include directory holds default configs that can be + included into other configs. This is a real use example that shows how + to reuse configs for various machines or set ups. The files here + are included by other config files, where the other config files define + options and variables that will make the included config work for the + given environment. + + diff --git a/tools/testing/ktest/examples/crosstests.conf b/tools/testing/ktest/examples/crosstests.conf new file mode 100644 index 000000000000..46736604c26c --- /dev/null +++ b/tools/testing/ktest/examples/crosstests.conf @@ -0,0 +1,260 @@ +# +# Example config for cross compiling +# +# In this config, it is expected that the tool chains from: +# +# http://kernel.org/pub/tools/crosstool/files/bin/x86_64/ +# +# running on a x86_64 system have been downloaded and installed into: +# +# /usr/local/ +# +# such that the compiler binaries are something like: +# +# /usr/local/gcc-4.5.2-nolibc/mips-linux/bin/mips-linux-gcc +# +# Some of the archs will use gcc-4.5.1 instead of gcc-4.5.2 +# this config uses variables to differentiate them. +# +# Comments describe some of the options, but full descriptions of +# options are described in the samples.conf file. + +# ${PWD} is defined by ktest.pl to be the directory that the user +# was in when they executed ktest.pl. It may be better to hardcode the +# path name here. THIS_DIR is the variable used through out the config file +# in case you want to change it. + +THIS_DIR := ${PWD} + +# Update the BUILD_DIR option to the location of your git repo you want to test. +BUILD_DIR = ${THIS_DIR}/linux.git + +# The build will go into this directory. It will be created when you run the test. +OUTPUT_DIR = ${THIS_DIR}/cross-compile + +# The build will be compiled with -j8 +BUILD_OPTIONS = -j8 + +# The test will not stop when it hits a failure. +DIE_ON_FAILURE = 0 + +# If you want to have ktest.pl store the failure somewhere, uncomment this option +# and change the directory where ktest should store the failures. +#STORE_FAILURES = ${THIS_DIR}/failures + +# The log file is stored in the OUTPUT_DIR called cross.log +# If you enable this, you need to create the OUTPUT_DIR. It wont be created for you. +LOG_FILE = ${OUTPUT_DIR}/cross.log + +# The log file will be cleared each time you run ktest. +CLEAR_LOG = 1 + +# As some archs do not build with the defconfig, they have been marked +# to be ignored. If you want to test them anyway, change DO_FAILED to 1. +# If a test that has been marked as DO_FAILED passes, then you should change +# that test to be DO_DEFAULT + +DO_FAILED := 0 +DO_DEFAULT := 1 + +# By setting both DO_FAILED and DO_DEFAULT to zero, you can pick a single +# arch that you want to test. (uncomment RUN and chose your arch) +#RUN := m32r + +# At the bottom of the config file exists a bisect test. You can update that +# test and set DO_FAILED and DO_DEFAULT to zero, and uncomment this variable +# to run the bisect on the arch. +#RUN := bisect + +# By default all tests will be running gcc 4.5.2. Some tests are using 4.5.1 +# and they select that in the test. +# Note: GCC_VER is declared as on option and not a variable ('=' instead of ':=') +# This is important. A variable is used only in the config file and if it is set +# it stays that way for the rest of the config file until it is change again. +# Here we want GCC_VER to remain persistent and change for each test, as it is used in +# the MAKE_CMD. By using '=' instead of ':=' we achieve our goal. + +GCC_VER = 4.5.2 +MAKE_CMD = PATH=/usr/local/gcc-${GCC_VER}-nolibc/${CROSS}/bin:$PATH CROSS_COMPILE=${CROSS}- make ARCH=${ARCH} + +# all tests are only doing builds. +TEST_TYPE = build + +# If you want to add configs on top of the defconfig, you can add those configs into +# the add-config file and uncomment this option. This is useful if you want to test +# all cross compiles with PREEMPT set, or TRACING on, etc. +#ADD_CONFIG = ${THIS_DIR}/add-config + +# All tests are using defconfig +BUILD_TYPE = defconfig + +# The test names will have the arch and cross compiler used. This will be shown in +# the results. +TEST_NAME = ${ARCH} ${CROSS} + +# alpha +TEST_START IF ${RUN} == alpha || ${DO_DEFAULT} +# Notice that CROSS and ARCH are also options and not variables (again '=' instead +# of ':='). This is because TEST_NAME and MAKE_CMD wil use them for each test. +# Only options are available during runs. Variables are only present in parsing the +# config file. +CROSS = alpha-linux +ARCH = alpha + +# arm +TEST_START IF ${RUN} == arm || ${DO_DEFAULT} +CROSS = arm-unknown-linux-gnueabi +ARCH = arm + +# black fin +TEST_START IF ${RUN} == bfin || ${DO_DEFAULT} +CROSS = bfin-uclinux +ARCH = blackfin +BUILD_OPTIONS = -j8 vmlinux + +# cris - FAILS? +TEST_START IF ${RUN} == cris || ${RUN} == cris64 || ${DO_FAILED} +CROSS = cris-linux +ARCH = cris + +# cris32 - not right arch? +TEST_START IF ${RUN} == cris || ${RUN} == cris32 || ${DO_FAILED} +CROSS = crisv32-linux +ARCH = cris + +# ia64 +TEST_START IF ${RUN} == ia64 || ${DO_DEFAULT} +CROSS = ia64-linux +ARCH = ia64 + +# frv +TEST_START IF ${RUN} == frv || ${DO_FAILED} +CROSS = frv-linux +ARCH = frv +GCC_VER = 4.5.1 + +# h8300 - failed make defconfig?? +TEST_START IF ${RUN} == h8300 || ${DO_FAILED} +CROSS = h8300-elf +ARCH = h8300 +GCC_VER = 4.5.1 + +# m68k fails with error? +TEST_START IF ${RUN} == m68k || ${DO_DEFAULT} +CROSS = m68k-linux +ARCH = m68k + +# mips64 +TEST_START IF ${RUN} == mips || ${RUN} == mips64 || ${DO_DEFAULT} +CROSS = mips64-linux +ARCH = mips + +# mips32 +TEST_START IF ${RUN} == mips || ${RUN} == mips32 || ${DO_DEFAULT} +CROSS = mips-linux +ARCH = mips + +# m32r +TEST_START IF ${RUN} == m32r || ${DO_FAILED} +CROSS = m32r-linux +ARCH = m32r +GCC_VER = 4.5.1 +BUILD_OPTIONS = -j8 vmlinux + +# parisc64 failed? +TEST_START IF ${RUN} == hppa || ${RUN} == hppa64 || ${DO_FAILED} +CROSS = hppa64-linux +ARCH = parisc + +# parisc +TEST_START IF ${RUN} == hppa || ${RUN} == hppa32 || ${DO_FAILED} +CROSS = hppa-linux +ARCH = parisc + +# ppc +TEST_START IF ${RUN} == ppc || ${RUN} == ppc32 || ${DO_DEFAULT} +CROSS = powerpc-linux +ARCH = powerpc + +# ppc64 +TEST_START IF ${RUN} == ppc || ${RUN} == ppc64 || ${DO_DEFAULT} +CROSS = powerpc64-linux +ARCH = powerpc + +# s390 +TEST_START IF ${RUN} == s390 || ${DO_DEFAULT} +CROSS = s390x-linux +ARCH = s390 + +# sh +TEST_START IF ${RUN} == sh || ${DO_DEFAULT} +CROSS = sh4-linux +ARCH = sh + +# sparc64 +TEST_START IF ${RUN} == sparc || ${RUN} == sparc64 || ${DO_DEFAULT} +CROSS = sparc64-linux +ARCH = sparc64 + +# sparc +TEST_START IF ${RUN} == sparc || ${RUN} == sparc32 || ${DO_DEFAULT} +CROSS = sparc-linux +ARCH = sparc + +# xtensa failed +TEST_START IF ${RUN} == xtensa || ${DO_FAILED} +CROSS = xtensa-linux +ARCH = xtensa + +# UML +TEST_START IF ${RUN} == uml || ${DO_DEFAULT} +MAKE_CMD = make ARCH=um SUBARCH=x86_64 +ARCH = uml +CROSS = + +TEST_START IF ${RUN} == x86 || ${RUN} == i386 || ${DO_DEFAULT} +MAKE_CMD = make ARCH=i386 +ARCH = i386 +CROSS = + +TEST_START IF ${RUN} == x86 || ${RUN} == x86_64 || ${DO_DEFAULT} +MAKE_CMD = make ARCH=x86_64 +ARCH = x86_64 +CROSS = + +################################# + +# This is a bisect if needed. You need to give it a MIN_CONFIG that +# will be the config file it uses. Basically, just copy the created defconfig +# for the arch someplace and point MIN_CONFIG to it. +TEST_START IF ${RUN} == bisect +MIN_CONFIG = ${THIS_DIR}/min-config +CROSS = s390x-linux +ARCH = s390 +TEST_TYPE = bisect +BISECT_TYPE = build +BISECT_GOOD = v3.1 +BISECT_BAD = v3.2 +CHECKOUT = v3.2 + +################################# + +# These defaults are needed to keep ktest.pl from complaining. They are +# ignored because the test does not go pass the build. No install or +# booting of the target images. + +DEFAULTS +MACHINE = crosstest +SSH_USER = root +BUILD_TARGET = cross +TARGET_IMAGE = image +POWER_CYCLE = cycle +CONSOLE = console +LOCALVERSION = version +GRUB_MENU = grub + +REBOOT_ON_ERROR = 0 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 +REBOOT_ON_SUCCESS = 0 + diff --git a/tools/testing/ktest/examples/include/bisect.conf b/tools/testing/ktest/examples/include/bisect.conf new file mode 100644 index 000000000000..009bea65bfb6 --- /dev/null +++ b/tools/testing/ktest/examples/include/bisect.conf @@ -0,0 +1,90 @@ +# +# This example shows the bisect tests (git bisect and config bisect) +# + + +# The config that includes this file may define a RUN_TEST +# variable that will tell this config what test to run. +# (what to set the TEST option to). +# +DEFAULTS IF NOT DEFINED RUN_TEST +# Requires that hackbench is in the PATH +RUN_TEST := ${SSH} hackbench 50 + + +# Set TEST to 'bisect' to do a normal git bisect. You need +# to modify the options below to make it bisect the exact +# commits you are interested in. +# +TEST_START IF ${TEST} == bisect +TEST_TYPE = bisect +# You must set the commit that was considered good (git bisect good) +BISECT_GOOD = v3.3 +# You must set the commit that was considered bad (git bisect bad) +BISECT_BAD = HEAD +# It's best to specify the branch to checkout before starting the bisect. +CHECKOUT = origin/master +# This can be build, boot, or test. Here we are doing a bisect +# that requires to run a test to know if the bisect was good or bad. +# The test should exit with 0 on good, non-zero for bad. But see +# the BISECT_RET_* options in samples.conf to override this. +BISECT_TYPE = test +TEST = ${RUN_TEST} +# It is usually a good idea to confirm that the GOOD and the BAD +# commits are truly good and bad respectively. Having BISECT_CHECK +# set to 1 will check both that the good commit works and the bad +# commit fails. If you only want to check one or the other, +# set BISECT_CHECK to 'good' or to 'bad'. +BISECT_CHECK = 1 +#BISECT_CHECK = good +#BISECT_CHECK = bad + +# Usually it's a good idea to specify the exact config you +# want to use throughout the entire bisect. Here we placed +# it in the directory we called ktest.pl from and named it +# 'config-bisect'. +MIN_CONFIG = ${THIS_DIR}/config-bisect +# By default, if we are doing a BISECT_TYPE = test run but the +# build or boot fails, ktest.pl will do a 'git bisect skip'. +# Uncomment the below option to make ktest stop testing on such +# an error. +#BISECT_SKIP = 0 +# Now if you had BISECT_SKIP = 0 and the test fails, you can +# examine what happened and then do 'git bisect log > /tmp/replay' +# Set BISECT_REPLAY to /tmp/replay and ktest.pl will run the +# 'git bisect replay /tmp/replay' before continuing the bisect test. +#BISECT_REPLAY = /tmp/replay +# If you used BISECT_REPLAY after the bisect test failed, you may +# not want to continue the bisect on that commit that failed. +# By setting BISECT_START to a new commit. ktest.pl will checkout +# that commit after it has performed the 'git bisect replay' but +# before it continues running the bisect test. +#BISECT_START = 2545eb6198e7e1ec50daa0cfc64a4cdfecf24ec9 + +# Now if you don't trust ktest.pl to make the decisions for you, then +# set BISECT_MANUAL to 1. This will cause ktest.pl not to decide +# if the commit was good or bad. Instead, it will ask you to tell +# it if the current commit was good. In the mean time, you could +# take the result, load it on any machine you want. Run several tests, +# or whatever you feel like. Then, when you are happy, you can tell +# ktest if you think it was good or not and ktest.pl will continue +# the git bisect. You can even change what commit it is currently at. +#BISECT_MANUAL = 1 + + +# One of the unique tests that ktest does is the config bisect. +# Currently (which hopefully will be fixed soon), the bad config +# must be a superset of the good config. This is because it only +# searches for a config that causes the target to fail. If the +# good config is not a subset of the bad config, or if the target +# fails because of a lack of a config, then it will not find +# the config for you. +TEST_START IF ${TEST} == config-bisect +TEST_TYPE = config_bisect +# set to build, boot, test +CONFIG_BISECT_TYPE = boot +# Set the config that is considered bad. +CONFIG_BISECT = ${THIS_DIR}/config-bad +# This config is optional. By default it uses the +# MIN_CONFIG as the good config. +CONFIG_BISECT_GOOD = ${THIS_DIR}/config-good diff --git a/tools/testing/ktest/examples/include/defaults.conf b/tools/testing/ktest/examples/include/defaults.conf new file mode 100644 index 000000000000..323a552ce642 --- /dev/null +++ b/tools/testing/ktest/examples/include/defaults.conf @@ -0,0 +1,157 @@ +# This file holds defaults for most the tests. It defines the options that +# are most common to tests that are likely to be shared. +# +# Note, after including this file, a config file may override any option +# with a DEFAULTS OVERRIDE section. +# + +# For those cases that use the same machine to boot a 64 bit +# and a 32 bit version. The MACHINE is the DNS name to get to the +# box (usually different if it was 64 bit or 32 bit) but the +# BOX here is defined as a variable that will be the name of the box +# itself. It is useful for calling scripts that will power cycle +# the box, as only one script needs to be created to power cycle +# even though the box itself has multiple operating systems on it. +# By default, BOX and MACHINE are the same. + +DEFAULTS IF NOT DEFINED BOX +BOX := ${MACHINE} + + +# Consider each box as 64 bit box, unless the config including this file +# has defined BITS = 32 + +DEFAULTS IF NOT DEFINED BITS +BITS := 64 + + +DEFAULTS + +# THIS_DIR is used through out the configs and defaults to ${PWD} which +# is the directory that ktest.pl was called from. + +THIS_DIR := ${PWD} + + +# to orginize your configs, having each machine save their configs +# into a separate directly is useful. +CONFIG_DIR := ${THIS_DIR}/configs/${MACHINE} + +# Reset the log before running each test. +CLEAR_LOG = 1 + +# As installing kernels usually requires root privilege, default the +# user on the target as root. It is also required that the target +# allows ssh to root from the host without asking for a password. + +SSH_USER = root + +# For accesing the machine, we will ssh to root@machine. +SSH := ssh ${SSH_USER}@${MACHINE} + +# Update this. The default here is ktest will ssh to the target box +# and run a script called 'run-test' located on that box. +TEST = ${SSH} run-test + +# Point build dir to the git repo you use +BUILD_DIR = ${THIS_DIR}/linux.git + +# Each machine will have its own output build directory. +OUTPUT_DIR = ${THIS_DIR}/build/${MACHINE} + +# Yes this config is focused on x86 (but ktest works for other archs too) +BUILD_TARGET = arch/x86/boot/bzImage +TARGET_IMAGE = /boot/vmlinuz-test + +# have directory for the scripts to reboot and power cycle the boxes +SCRIPTS_DIR := ${THIS_DIR}/scripts + +# You can have each box/machine have a script to power cycle it. +# Name your script <box>-cycle. +POWER_CYCLE = ${SCRIPTS_DIR}/${BOX}-cycle + +# This script is used to power off the box. +POWER_OFF = ${SCRIPTS_DIR}/${BOX}-poweroff + +# Keep your test kernels separate from your other kernels. +LOCALVERSION = -test + +# The /boot/grub/menu.lst is searched for the line: +# title Test Kernel +# and ktest will use that kernel to reboot into. +# For grub2 or other boot loaders, you need to set BOOT_TYPE +# to 'script' and define other ways to load the kernel. +# See snowball.conf example. +# +GRUB_MENU = Test Kernel + +# The kernel build will use this option. +BUILD_OPTIONS = -j8 + +# Keeping the log file with the output dir is convenient. +LOG_FILE = ${OUTPUT_DIR}/${MACHINE}.log + +# Each box should have their own minum configuration +# See min-config.conf +MIN_CONFIG = ${CONFIG_DIR}/config-min + +# For things like randconfigs, there may be configs you find that +# are already broken, or there may be some configs that you always +# want set. Uncomment ADD_CONFIG and point it to the make config files +# that set the configs you want to keep on (or off) in your build. +# ADD_CONFIG is usually something to add configs to all machines, +# where as, MIN_CONFIG is specific per machine. +#ADD_CONFIG = ${THIS_DIR}/config-broken ${THIS_DIR}/config-general + +# To speed up reboots for bisects and patchcheck, instead of +# waiting 60 seconds for the console to be idle, if this line is +# seen in the console output, ktest will know the good kernel has +# finished rebooting and it will be able to continue the tests. +REBOOT_SUCCESS_LINE = ${MACHINE} login: + +# The following is different ways to end the test. +# by setting the variable REBOOT to: none, error, fail or +# something else, ktest will power cycle or reboot the target box +# at the end of the tests. +# +# REBOOT := none +# Don't do anything at the end of the test. +# +# REBOOT := error +# Reboot the box if ktest detects an error +# +# REBOOT := fail +# Do not stop on failure, and after all tests are complete +# power off the box (for both success and error) +# This is good to run over a weekend and you don't want to waste +# electricity. +# + +DEFAULTS IF ${REBOOT} == none +REBOOT_ON_SUCCESS = 0 +REBOOT_ON_ERROR = 0 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 + +DEFAULTS ELSE IF ${REBOOT} == error +REBOOT_ON_SUCCESS = 0 +REBOOT_ON_ERROR = 1 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 + +DEFAULTS ELSE IF ${REBOOT} == fail +REBOOT_ON_SUCCESS = 0 +POWEROFF_ON_ERROR = 1 +POWEROFF_ON_SUCCESS = 1 +POWEROFF_AFTER_HALT = 120 +DIE_ON_FAILURE = 0 + +# Store the failure information into this directory +# such as the .config, dmesg, and build log. +STORE_FAILURES = ${THIS_DIR}/failures + +DEFAULTS ELSE +REBOOT_ON_SUCCESS = 1 +REBOOT_ON_ERROR = 1 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 diff --git a/tools/testing/ktest/examples/include/min-config.conf b/tools/testing/ktest/examples/include/min-config.conf new file mode 100644 index 000000000000..c703cc46d151 --- /dev/null +++ b/tools/testing/ktest/examples/include/min-config.conf @@ -0,0 +1,60 @@ +# +# This file has some examples for creating a MIN_CONFIG. +# (A .config file that is the minimum for a machine to boot, or +# to boot and make a network connection.) +# +# A MIN_CONFIG is very useful as it is the minimum configuration +# needed to boot a given machine. You can debug someone else's +# .config by only setting the configs in your MIN_CONFIG. The closer +# your MIN_CONFIG is to the true minimum set of configs needed to +# boot your machine, the closer the config you test with will be +# to the users config that had the failure. +# +# The make_min_config test allows you to create a MIN_CONFIG that +# is truly the minimum set of configs needed to boot a box. +# +# In this example, the final config will reside in +# ${CONFIG_DIR}/config-new-min and ${CONFIG_DIR}/config-new-min-net. +# Just move one to the location you have set for MIN_CONFIG. +# +# The first test creates a MIN_CONFIG that will be the minimum +# configuration to boot ${MACHINE} and be able to ssh to it. +# +# The second test creates a MIN_CONFIG that will only boot +# the target and most likely will not let you ssh to it. (Notice +# how the second test uses the first test's result to continue with. +# This is because the second test config is a subset of the first). +# +# The ${CONFIG_DIR}/config-skip (and -net) will hold the configs +# that ktest.pl found would not boot the target without them set. +# The config-new-min holds configs that ktest.pl could not test +# directly because another config that was needed to boot the box +# selected them. Sometimes it is possible that this file will hold +# the true minimum configuration. You can test to see if this is +# the case by running the boot test with BOOT_TYPE = allnoconfig and +# setting setting the MIN_CONFIG to ${CONFIG_DIR}/config-skip. If the +# machine still boots, then you can use the config-skip as your MIN_CONFIG. +# +# These tests can run for several hours (and perhaps days). +# It's OK to kill the test with a Ctrl^C. By restarting without +# modifying this config, ktest.pl will notice that the config-new-min(-net) +# exists, and will use that instead as the starting point. +# The USE_OUTPUT_MIN_CONFIG is set to 1 to keep ktest.pl from asking +# you if you want to use the OUTPUT_MIN_CONFIG as the starting point. +# By using the OUTPUT_MIN_CONFIG as the starting point will allow ktest.pl to +# start almost where it left off. +# +TEST_START IF ${TEST} == min-config +TEST_TYPE = make_min_config +OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net +IGNORE_CONFIG = ${CONFIG_DIR}/config-skip-net +MIN_CONFIG_TYPE = test +TEST = ${SSH} echo hi +USE_OUTPUT_MIN_CONFIG = 1 + +TEST_START IF ${TEST} == min-config && ${MULTI} +TEST_TYPE = make_min_config +OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min +IGNORE_CONFIG = ${CONFIG_DIR}/config-skip +MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net +USE_OUTPUT_MIN_CONFIG = 1 diff --git a/tools/testing/ktest/examples/include/patchcheck.conf b/tools/testing/ktest/examples/include/patchcheck.conf new file mode 100644 index 000000000000..339d3e1700ff --- /dev/null +++ b/tools/testing/ktest/examples/include/patchcheck.conf @@ -0,0 +1,74 @@ +# patchcheck.conf +# +# This contains a test that takes two git commits and will test each +# commit between the two. The build test will look at what files the +# commit has touched, and if any of those files produce a warning, then +# the build will fail. + + +# PATCH_START is the commit to begin with and PATCH_END is the commit +# to end with (inclusive). This is similar to doing a git rebase -i PATCH_START~1 +# and then testing each commit and doing a git rebase --continue. +# You can use a SHA1, a git tag, or anything that git will accept for a checkout + +PATCH_START := HEAD~3 +PATCH_END := HEAD + +# Change PATCH_CHECKOUT to be the branch you want to test. The test will +# do a git checkout of this branch before starting. Obviously both +# PATCH_START and PATCH_END must be in this branch (and PATCH_START must +# be contained by PATCH_END). + +PATCH_CHECKOUT := test/branch + +# Usually it's a good idea to have a set config to use for testing individual +# patches. +PATCH_CONFIG := ${CONFIG_DIR}/config-patchcheck + +# Change PATCH_TEST to run some test for each patch. Each commit that is +# tested, after it is built and installed on the test machine, this command +# will be executed. Usually what is done is to ssh to the target box and +# run some test scripts. If you just want to boot test your patches +# comment PATCH_TEST out. +PATCH_TEST := ${SSH} "/usr/local/bin/ktest-test-script" + +DEFAULTS IF DEFINED PATCH_TEST +PATCH_TEST_TYPE := test + +DEFAULTS ELSE +PATCH_TEST_TYPE := boot + +# If for some reason a file has a warning that one of your patches touch +# but you do not care about it, set IGNORE_WARNINGS to that commit(s) +# (space delimited) +#IGNORE_WARNINGS = 39eaf7ef884dcc44f7ff1bac803ca2a1dcf43544 6edb2a8a385f0cdef51dae37ff23e74d76d8a6ce + +# If you are running a multi test, and the test failed on the first +# test but on, say the 5th patch. If you want to restart on the +# fifth patch, set PATCH_START1. This will make the first test start +# from this commit instead of the PATCH_START commit. +# Note, do not change this option. Just define PATCH_START1 in the +# top config (the one you pass to ktest.pl), and this will use it, +# otherwise it will just use PATCH_START if PATCH_START1 is not defined. +DEFAULTS IF NOT DEFINED PATCH_START1 +PATCH_START1 := ${PATCH_START} + +TEST_START IF ${TEST} == patchcheck +TEST_TYPE = patchcheck +MIN_CONFIG = ${PATCH_CONFIG} +TEST = ${PATCH_TEST} +PATCHCHECK_TYPE = ${PATCH_TEST_TYPE} +PATCHCHECK_START = ${PATCH_START1} +PATCHCHECK_END = ${PATCH_END} +CHECKOUT = ${PATCH_CHECKOUT} + +TEST_START IF ${TEST} == patchcheck && ${MULTI} +TEST_TYPE = patchcheck +MIN_CONFIG = ${PATCH_CONFIG} +TEST = ${PATCH_TEST} +PATCHCHECK_TYPE = ${PATCH_TEST_TYPE} +PATCHCHECK_START = ${PATCH_START} +PATCHCHECK_END = ${PATCH_END} +CHECKOUT = ${PATCH_CHECKOUT} +# Use multi to test different compilers? +MAKE_CMD = CC=gcc-4.5.1 make diff --git a/tools/testing/ktest/examples/include/tests.conf b/tools/testing/ktest/examples/include/tests.conf new file mode 100644 index 000000000000..4fdb811bd810 --- /dev/null +++ b/tools/testing/ktest/examples/include/tests.conf @@ -0,0 +1,74 @@ +# +# This is an example of various tests that you can run +# +# The variable TEST can be of boot, build, randconfig, or test. +# +# Note that TEST is a variable created with ':=' and only exists +# throughout the config processing (not during the tests itself). +# +# The TEST option (defined with '=') is used to tell ktest.pl +# what test to run after a successful boot. The TEST option is +# persistent into the test runs. +# + +# The config that includes this file may define a BOOT_TYPE +# variable that tells this config what type of boot test to run. +# If it's not defined, the below DEFAULTS will set the default +# to 'oldconfig'. +# +DEFAULTS IF NOT DEFINED BOOT_TYPE +BOOT_TYPE := oldconfig + +# The config that includes this file may define a RUN_TEST +# variable that will tell this config what test to run. +# (what to set the TEST option to). +# +DEFAULTS IF NOT DEFINED RUN_TEST +# Requires that hackbench is in the PATH +RUN_TEST := ${SSH} hackbench 50 + + +# If TEST is set to 'boot' then just build a kernel and boot +# the target. +TEST_START IF ${TEST} == boot +TEST_TYPE = boot +# Notice how we set the BUILD_TYPE option to the BOOT_TYPE variable. +BUILD_TYPE = ${BOOT_TYPE} +# Do not do a make mrproper. +BUILD_NOCLEAN = 1 + +# If you only want to build the kernel, and perhaps install +# and test it yourself, then just set TEST to build. +TEST_START IF ${TEST} == build +TEST_TYPE = build +BUILD_TYPE = ${BOOT_TYPE} +BUILD_NOCLEAN = 1 + +# Build, install, boot and test with a randconfg 10 times. +# It is important that you have set MIN_CONFIG in the config +# that includes this file otherwise it is likely that the +# randconfig will not have the neccessary configs needed to +# boot your box. This version of the test requires a min +# config that has enough to make sure the target has network +# working. +TEST_START ITERATE 10 IF ${TEST} == randconfig +MIN_CONFIG = ${CONFIG_DIR}/config-min-net +TEST_TYPE = test +BUILD_TYPE = randconfig +TEST = ${RUN_TEST} + +# This is the same as above, but only tests to a boot prompt. +# The MIN_CONFIG used here does not need to have networking +# working. +TEST_START ITERATE 10 IF ${TEST} == randconfig && ${MULTI} +TEST_TYPE = boot +BUILD_TYPE = randconfig +MIN_CONFIG = ${CONFIG_DIR}/config-min +MAKE_CMD = make + +# This builds, installs, boots and tests the target. +TEST_START IF ${TEST} == test +TEST_TYPE = test +BUILD_TYPE = ${BOOT_TYPE} +TEST = ${RUN_TEST} +BUILD_NOCLEAN = 1 diff --git a/tools/testing/ktest/examples/kvm.conf b/tools/testing/ktest/examples/kvm.conf new file mode 100644 index 000000000000..831c7c5395f1 --- /dev/null +++ b/tools/testing/ktest/examples/kvm.conf @@ -0,0 +1,88 @@ +# +# This config is an example usage of ktest.pl with a kvm guest +# +# The guest is called 'Guest' and this would be something that +# could be run on the host to test a virtual machine target. + +MACHINE = Guest + + +# Use virsh to read the serial console of the guest +CONSOLE = virsh console ${MACHINE} + +#*************************************# +# This part is the same as test.conf # +#*************************************# + +# The include files will set up the type of test to run. Just set TEST to +# which test you want to run. +# +# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config +# +# See the include/*.conf files that define these tests +# +TEST := patchcheck + +# Some tests may have more than one test to run. Define MULTI := 1 to run +# the extra tests. +MULTI := 0 + +# In case you want to differentiate which type of system you are testing +BITS := 64 + +# REBOOT = none, error, fail, empty +# See include/defaults.conf +REBOOT := empty + + +# The defaults file will set up various settings that can be used by all +# machine configs. +INCLUDE include/defaults.conf + + +#*************************************# +# Now we are different from test.conf # +#*************************************# + + +# The example here assumes that Guest is running a Fedora release +# that uses dracut for its initfs. The POST_INSTALL will be executed +# after the install of the kernel and modules are complete. +# +POST_INSTALL = ${SSH} /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION + +# Guests sometimes get stuck on reboot. We wait 3 seconds after running +# the reboot command and then do a full power-cycle of the guest. +# This forces the guest to restart. +# +POWERCYCLE_AFTER_REBOOT = 3 + +# We do the same after the halt command, but this time we wait 20 seconds. +POWEROFF_AFTER_HALT = 20 + + +# As the defaults.conf file has a POWER_CYCLE option already defined, +# and options can not be defined in the same section more than once +# (all DEFAULTS sections are considered the same). We use the +# DEFAULTS OVERRIDE to tell ktest.pl to ignore the previous defined +# options, for the options set in the OVERRIDE section. +# +DEFAULTS OVERRIDE + +# Instead of using the default POWER_CYCLE option defined in +# defaults.conf, we use virsh to cycle it. To do so, we destroy +# the guest, wait 5 seconds, and then start it up again. +# Crude, but effective. +# +POWER_CYCLE = virsh destroy ${MACHINE}; sleep 5; virsh start ${MACHINE} + + +DEFAULTS + +# The following files each handle a different test case. +# Having them included allows you to set up more than one machine and share +# the same tests. +INCLUDE include/patchcheck.conf +INCLUDE include/tests.conf +INCLUDE include/bisect.conf +INCLUDE include/min-config.conf diff --git a/tools/testing/ktest/examples/snowball.conf b/tools/testing/ktest/examples/snowball.conf new file mode 100644 index 000000000000..a82a3c5bc2b7 --- /dev/null +++ b/tools/testing/ktest/examples/snowball.conf @@ -0,0 +1,53 @@ +# This example was used to boot the snowball ARM board. +# See http://people.redhat.com/srostedt/ktest-embedded-2012/ + +# PWD is a ktest.pl variable that will result in the process working +# directory that ktest.pl is executed in. + +# THIS_DIR is automatically assigned the PWD of the path that generated +# the config file. It is best to use this variable when assigning other +# directory paths within this directory. This allows you to easily +# move the test cases to other locations or to other machines. +# +THIS_DIR := /home/rostedt/work/demo/ktest-embed +LOG_FILE = ${OUTPUT_DIR}/snowball.log +CLEAR_LOG = 1 +MAKE_CMD = PATH=/usr/local/gcc-4.5.2-nolibc/arm-unknown-linux-gnueabi/bin:$PATH CROSS_COMPILE=arm-unknown-linux-gnueabi- make ARCH=arm +ADD_CONFIG = ${THIS_DIR}/addconfig + +SCP_TO_TARGET = echo "don't do scp" + +TFTPBOOT := /var/lib/tftpboot +TFTPDEF := ${TFTPBOOT}/snowball-default +TFTPTEST := ${OUTPUT_DIR}/${BUILD_TARGET} + +SWITCH_TO_GOOD = cp ${TFTPDEF} ${TARGET_IMAGE} +SWITCH_TO_TEST = cp ${TFTPTEST} ${TARGET_IMAGE} + +# Define each test with TEST_START +# The config options below it will override the defaults +TEST_START SKIP +TEST_TYPE = boot +BUILD_TYPE = u8500_defconfig +BUILD_NOCLEAN = 1 + +TEST_START +TEST_TYPE = make_min_config +OUTPUT_MIN_CONFIG = ${THIS_DIR}/config.newmin +START_MIN_CONFIG = ${THIS_DIR}/config.orig +IGNORE_CONFIG = ${THIS_DIR}/config.ignore +BUILD_NOCLEAN = 1 + + +DEFAULTS +LOCALVERSION = -test +POWER_CYCLE = echo use the thumb luke; read a +CONSOLE = cat ${THIS_DIR}/snowball-cat +REBOOT_TYPE = script +SSH_USER = root +BUILD_OPTIONS = -j8 uImage +BUILD_DIR = ${THIS_DIR}/linux.git +OUTPUT_DIR = ${THIS_DIR}/snowball-build +MACHINE = snowball +TARGET_IMAGE = /var/lib/tftpboot/snowball-image +BUILD_TARGET = arch/arm/boot/uImage diff --git a/tools/testing/ktest/examples/test.conf b/tools/testing/ktest/examples/test.conf new file mode 100644 index 000000000000..b725210efb7f --- /dev/null +++ b/tools/testing/ktest/examples/test.conf @@ -0,0 +1,62 @@ +# +# Generic config for a machine +# + +# Name your machine (the DNS name, what you ssh to) +MACHINE = foo + +# BOX can be different than foo, if the machine BOX has +# multiple partitions with different systems installed. For example, +# you may have a i386 and x86_64 installation on a test box. +# If this is the case, MACHINE defines the way to connect to the +# machine, which may be different between which system the machine +# is booting into. BOX is used for the scripts to reboot and power cycle +# the machine, where it does not matter which system the machine boots into. +# +#BOX := bar + +# Define a way to read the console +CONSOLE = stty -F /dev/ttyS0 115200 parodd; cat /dev/ttyS0 + +# The include files will set up the type of test to run. Just set TEST to +# which test you want to run. +# +# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config +# +# See the include/*.conf files that define these tests +# +TEST := patchcheck + +# Some tests may have more than one test to run. Define MULTI := 1 to run +# the extra tests. +MULTI := 0 + +# In case you want to differentiate which type of system you are testing +BITS := 64 + +# REBOOT = none, error, fail, empty +# See include/defaults.conf +REBOOT := empty + +# The defaults file will set up various settings that can be used by all +# machine configs. +INCLUDE include/defaults.conf + +# In case you need to add a patch for a bisect or something +#PRE_BUILD = patch -p1 < ${THIS_DIR}/fix.patch + +# Reset the repo after the build and remove all 'test' modules from the target +# Notice that DO_POST_BUILD is a variable (defined by ':=') and POST_BUILD +# is the option (defined by '=') + +DO_POST_BUILD := git reset --hard +POST_BUILD = ${SSH} 'rm -rf /lib/modules/*-test*'; ${DO_POST_BUILD} + +# The following files each handle a different test case. +# Having them included allows you to set up more than one machine and share +# the same tests. +INCLUDE include/patchcheck.conf +INCLUDE include/tests.conf +INCLUDE include/bisect.conf +INCLUDE include/min-config.conf + diff --git a/tools/testing/ktest/ktest.pl b/tools/testing/ktest/ktest.pl index 95d6a6f7c33a..292b13ad03f5 100755 --- a/tools/testing/ktest/ktest.pl +++ b/tools/testing/ktest/ktest.pl @@ -39,6 +39,7 @@ my %default = ( "CLEAR_LOG" => 0, "BISECT_MANUAL" => 0, "BISECT_SKIP" => 1, + "MIN_CONFIG_TYPE" => "boot", "SUCCESS_LINE" => "login:", "DETECT_TRIPLE_FAULT" => 1, "NO_INSTALL" => 0, @@ -66,6 +67,7 @@ my %default = ( my $ktest_config; my $version; +my $have_version = 0; my $machine; my $ssh_user; my $tmpdir; @@ -106,6 +108,8 @@ my $minconfig; my $start_minconfig; my $start_minconfig_defined; my $output_minconfig; +my $minconfig_type; +my $use_output_minconfig; my $ignore_config; my $ignore_errors; my $addconfig; @@ -183,6 +187,9 @@ my %force_config; # do not force reboots on config problems my $no_reboot = 1; +# reboot on success +my $reboot_success = 0; + my %option_map = ( "MACHINE" => \$machine, "SSH_USER" => \$ssh_user, @@ -202,6 +209,8 @@ my %option_map = ( "MIN_CONFIG" => \$minconfig, "OUTPUT_MIN_CONFIG" => \$output_minconfig, "START_MIN_CONFIG" => \$start_minconfig, + "MIN_CONFIG_TYPE" => \$minconfig_type, + "USE_OUTPUT_MIN_CONFIG" => \$use_output_minconfig, "IGNORE_CONFIG" => \$ignore_config, "TEST" => \$run_test, "ADD_CONFIG" => \$addconfig, @@ -1699,10 +1708,12 @@ sub install { sub get_version { # get the release name + return if ($have_version); doprint "$make kernelrelease ... "; $version = `$make kernelrelease | tail -1`; chomp($version); doprint "$version\n"; + $have_version = 1; } sub start_monitor_and_boot { @@ -1825,6 +1836,9 @@ sub build { my $save_no_reboot = $no_reboot; $no_reboot = 1; + # Calculate a new version from here. + $have_version = 0; + if (defined($pre_build)) { my $ret = run_command $pre_build; if (!$ret && defined($pre_build_die) && @@ -1884,6 +1898,9 @@ sub build { undef $redirect; if (defined($post_build)) { + # Because a post build may change the kernel version + # do it now. + get_version; my $ret = run_command $post_build; if (!$ret && defined($post_build_die) && $post_build_die) { @@ -2192,7 +2209,7 @@ sub run_bisect { } # Are we looking for where it worked, not failed? - if ($reverse_bisect) { + if ($reverse_bisect && $ret >= 0) { $ret = !$ret; } @@ -3116,6 +3133,12 @@ sub test_this_config { sub make_min_config { my ($i) = @_; + my $type = $minconfig_type; + if ($type ne "boot" && $type ne "test") { + fail "Invalid MIN_CONFIG_TYPE '$minconfig_type'\n" . + " make_min_config works only with 'boot' and 'test'\n" and return; + } + if (!defined($output_minconfig)) { fail "OUTPUT_MIN_CONFIG not defined" and return; } @@ -3125,8 +3148,15 @@ sub make_min_config { # that instead. if (-f $output_minconfig && !$start_minconfig_defined) { print "$output_minconfig exists\n"; - if (read_yn " Use it as minconfig?") { + if (!defined($use_output_minconfig)) { + if (read_yn " Use it as minconfig?") { + $start_minconfig = $output_minconfig; + } + } elsif ($use_output_minconfig > 0) { + doprint "Using $output_minconfig as MIN_CONFIG\n"; $start_minconfig = $output_minconfig; + } else { + doprint "Set to still use MIN_CONFIG as starting point\n"; } } @@ -3275,6 +3305,11 @@ sub make_min_config { build "oldconfig" or $failed = 1; if (!$failed) { start_monitor_and_boot or $failed = 1; + + if ($type eq "test" && !$failed) { + do_run_test or $failed = 1; + } + end_monitor; } @@ -3469,6 +3504,9 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { # Do not reboot on failing test options $no_reboot = 1; + $reboot_success = 0; + + $have_version = 0; $iteration = $i; @@ -3554,9 +3592,11 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { die "failed to checkout $checkout"; } + $no_reboot = 0; + # A test may opt to not reboot the box if ($reboot_on_success) { - $no_reboot = 0; + $reboot_success = 1; } if ($test_type eq "bisect") { @@ -3600,7 +3640,7 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { if ($opt{"POWEROFF_ON_SUCCESS"}) { halt; -} elsif ($opt{"REBOOT_ON_SUCCESS"} && !do_not_reboot) { +} elsif ($opt{"REBOOT_ON_SUCCESS"} && !do_not_reboot && $reboot_success) { reboot_to_good; } elsif (defined($switch_to_good)) { # still need to get to the good kernel diff --git a/tools/testing/ktest/sample.conf b/tools/testing/ktest/sample.conf index b682456afda8..cf362b3d1ec9 100644 --- a/tools/testing/ktest/sample.conf +++ b/tools/testing/ktest/sample.conf @@ -158,7 +158,7 @@ # # TEST_START IF (DEFINED ALL_TESTS || ${MYTEST} == boottest) && ${MACHINE} == gandalf # -# Notice the use of paranthesis. Without any paranthesis the above would be +# Notice the use of parentheses. Without any parentheses the above would be # processed the same as: # # TEST_START IF DEFINED ALL_TESTS || (${MYTEST} == boottest && ${MACHINE} == gandalf) @@ -1105,10 +1105,26 @@ # and will not be tested again in later runs. # (optional) # +# MIN_CONFIG_TYPE can be either 'boot' or 'test'. With 'boot' it will +# test if the created config can just boot the machine. If this is +# set to 'test', then the TEST option must be defined and the created +# config will not only boot the target, but also make sure that the +# config lets the test succeed. This is useful to make sure the final +# config that is generated allows network activity (ssh). +# (optional) +# +# USE_OUTPUT_MIN_CONFIG set this to 1 if you do not want to be prompted +# about using the OUTPUT_MIN_CONFIG as the MIN_CONFIG as the starting +# point. Set it to 0 if you want to always just use the given MIN_CONFIG. +# If it is not defined, it will prompt you to pick which config +# to start with (MIN_CONFIG or OUTPUT_MIN_CONFIG). +# # Example: # # TEST_TYPE = make_min_config # OUTPUT_MIN_CONFIG = /path/to/config-new-min # START_MIN_CONFIG = /path/to/config-min # IGNORE_CONFIG = /path/to/config-tested +# MIN_CONFIG_TYPE = test +# TEST = ssh ${USER}@${MACHINE} echo hi # diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 28bc57ee757c..a4162e15c25f 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -1,4 +1,4 @@ -TARGETS = breakpoints vm +TARGETS = breakpoints kcmp mqueue vm all: for TARGET in $(TARGETS); do \ diff --git a/tools/testing/selftests/kcmp/Makefile b/tools/testing/selftests/kcmp/Makefile new file mode 100644 index 000000000000..dc79b86ea65c --- /dev/null +++ b/tools/testing/selftests/kcmp/Makefile @@ -0,0 +1,29 @@ +uname_M := $(shell uname -m 2>/dev/null || echo not) +ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/) +ifeq ($(ARCH),i386) + ARCH := X86 + CFLAGS := -DCONFIG_X86_32 -D__i386__ +endif +ifeq ($(ARCH),x86_64) + ARCH := X86 + CFLAGS := -DCONFIG_X86_64 -D__x86_64__ +endif + +CFLAGS += -I../../../../arch/x86/include/generated/ +CFLAGS += -I../../../../include/ +CFLAGS += -I../../../../usr/include/ +CFLAGS += -I../../../../arch/x86/include/ + +all: +ifeq ($(ARCH),X86) + gcc $(CFLAGS) kcmp_test.c -o run_test +else + echo "Not an x86 target, can't build kcmp selftest" +endif + +run-tests: all + ./kcmp_test + +clean: + rm -fr ./run_test + rm -fr ./test-file diff --git a/tools/testing/selftests/kcmp/kcmp_test.c b/tools/testing/selftests/kcmp/kcmp_test.c new file mode 100644 index 000000000000..358cc6bfa35d --- /dev/null +++ b/tools/testing/selftests/kcmp/kcmp_test.c @@ -0,0 +1,94 @@ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <linux/unistd.h> +#include <linux/kcmp.h> + +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> + +static long sys_kcmp(int pid1, int pid2, int type, int fd1, int fd2) +{ + return syscall(__NR_kcmp, pid1, pid2, type, fd1, fd2); +} + +int main(int argc, char **argv) +{ + const char kpath[] = "kcmp-test-file"; + int pid1, pid2; + int fd1, fd2; + int status; + + fd1 = open(kpath, O_RDWR | O_CREAT | O_TRUNC, 0644); + pid1 = getpid(); + + if (fd1 < 0) { + perror("Can't create file"); + exit(1); + } + + pid2 = fork(); + if (pid2 < 0) { + perror("fork failed"); + exit(1); + } + + if (!pid2) { + int pid2 = getpid(); + int ret; + + fd2 = open(kpath, O_RDWR, 0644); + if (fd2 < 0) { + perror("Can't open file"); + exit(1); + } + + /* An example of output and arguments */ + printf("pid1: %6d pid2: %6d FD: %2ld FILES: %2ld VM: %2ld " + "FS: %2ld SIGHAND: %2ld IO: %2ld SYSVSEM: %2ld " + "INV: %2ld\n", + pid1, pid2, + sys_kcmp(pid1, pid2, KCMP_FILE, fd1, fd2), + sys_kcmp(pid1, pid2, KCMP_FILES, 0, 0), + sys_kcmp(pid1, pid2, KCMP_VM, 0, 0), + sys_kcmp(pid1, pid2, KCMP_FS, 0, 0), + sys_kcmp(pid1, pid2, KCMP_SIGHAND, 0, 0), + sys_kcmp(pid1, pid2, KCMP_IO, 0, 0), + sys_kcmp(pid1, pid2, KCMP_SYSVSEM, 0, 0), + + /* This one should fail */ + sys_kcmp(pid1, pid2, KCMP_TYPES + 1, 0, 0)); + + /* This one should return same fd */ + ret = sys_kcmp(pid1, pid2, KCMP_FILE, fd1, fd1); + if (ret) { + printf("FAIL: 0 expected but %d returned\n", ret); + ret = -1; + } else + printf("PASS: 0 returned as expected\n"); + + /* Compare with self */ + ret = sys_kcmp(pid1, pid1, KCMP_VM, 0, 0); + if (ret) { + printf("FAIL: 0 expected but %li returned\n", ret); + ret = -1; + } else + printf("PASS: 0 returned as expected\n"); + + exit(ret); + } + + waitpid(pid2, &status, P_ALL); + + return 0; +} diff --git a/tools/testing/selftests/mqueue/.gitignore b/tools/testing/selftests/mqueue/.gitignore new file mode 100644 index 000000000000..d8d42377205a --- /dev/null +++ b/tools/testing/selftests/mqueue/.gitignore @@ -0,0 +1,2 @@ +mq_open_tests +mq_perf_tests diff --git a/tools/testing/selftests/mqueue/Makefile b/tools/testing/selftests/mqueue/Makefile new file mode 100644 index 000000000000..54c0aad2b47c --- /dev/null +++ b/tools/testing/selftests/mqueue/Makefile @@ -0,0 +1,10 @@ +all: + gcc -O2 -lrt mq_open_tests.c -o mq_open_tests + gcc -O2 -lrt -lpthread -lpopt -o mq_perf_tests mq_perf_tests.c + +run_tests: + ./mq_open_tests /test1 + ./mq_perf_tests + +clean: + rm -f mq_open_tests mq_perf_tests diff --git a/tools/testing/selftests/mqueue/mq_open_tests.c b/tools/testing/selftests/mqueue/mq_open_tests.c new file mode 100644 index 000000000000..711cc2923047 --- /dev/null +++ b/tools/testing/selftests/mqueue/mq_open_tests.c @@ -0,0 +1,492 @@ +/* + * This application is Copyright 2012 Red Hat, Inc. + * Doug Ledford <dledford@redhat.com> + * + * mq_open_tests is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * mq_open_tests is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For the full text of the license, see <http://www.gnu.org/licenses/>. + * + * mq_open_tests.c + * Tests the various situations that should either succeed or fail to + * open a posix message queue and then reports whether or not they + * did as they were supposed to. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <mqueue.h> + +static char *usage = +"Usage:\n" +" %s path\n" +"\n" +" path Path name of the message queue to create\n" +"\n" +" Note: this program must be run as root in order to enable all tests\n" +"\n"; + +char *DEF_MSGS = "/proc/sys/fs/mqueue/msg_default"; +char *DEF_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_default"; +char *MAX_MSGS = "/proc/sys/fs/mqueue/msg_max"; +char *MAX_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_max"; + +int default_settings; +struct rlimit saved_limits, cur_limits; +int saved_def_msgs, saved_def_msgsize, saved_max_msgs, saved_max_msgsize; +int cur_def_msgs, cur_def_msgsize, cur_max_msgs, cur_max_msgsize; +FILE *def_msgs, *def_msgsize, *max_msgs, *max_msgsize; +char *queue_path; +mqd_t queue = -1; + +static inline void __set(FILE *stream, int value, char *err_msg); +void shutdown(int exit_val, char *err_cause, int line_no); +static inline int get(FILE *stream); +static inline void set(FILE *stream, int value); +static inline void getr(int type, struct rlimit *rlim); +static inline void setr(int type, struct rlimit *rlim); +void validate_current_settings(); +static inline void test_queue(struct mq_attr *attr, struct mq_attr *result); +static inline int test_queue_fail(struct mq_attr *attr, struct mq_attr *result); + +static inline void __set(FILE *stream, int value, char *err_msg) +{ + rewind(stream); + if (fprintf(stream, "%d", value) < 0) + perror(err_msg); +} + + +void shutdown(int exit_val, char *err_cause, int line_no) +{ + static int in_shutdown = 0; + + /* In case we get called recursively by a set() call below */ + if (in_shutdown++) + return; + + seteuid(0); + + if (queue != -1) + if (mq_close(queue)) + perror("mq_close() during shutdown"); + if (queue_path) + /* + * Be silent if this fails, if we cleaned up already it's + * expected to fail + */ + mq_unlink(queue_path); + if (default_settings) { + if (saved_def_msgs) + __set(def_msgs, saved_def_msgs, + "failed to restore saved_def_msgs"); + if (saved_def_msgsize) + __set(def_msgsize, saved_def_msgsize, + "failed to restore saved_def_msgsize"); + } + if (saved_max_msgs) + __set(max_msgs, saved_max_msgs, + "failed to restore saved_max_msgs"); + if (saved_max_msgsize) + __set(max_msgsize, saved_max_msgsize, + "failed to restore saved_max_msgsize"); + if (exit_val) + error(exit_val, errno, "%s at %d", err_cause, line_no); + exit(0); +} + +static inline int get(FILE *stream) +{ + int value; + rewind(stream); + if (fscanf(stream, "%d", &value) != 1) + shutdown(4, "Error reading /proc entry", __LINE__ - 1); + return value; +} + +static inline void set(FILE *stream, int value) +{ + int new_value; + + rewind(stream); + if (fprintf(stream, "%d", value) < 0) + return shutdown(5, "Failed writing to /proc file", + __LINE__ - 1); + new_value = get(stream); + if (new_value != value) + return shutdown(5, "We didn't get what we wrote to /proc back", + __LINE__ - 1); +} + +static inline void getr(int type, struct rlimit *rlim) +{ + if (getrlimit(type, rlim)) + shutdown(6, "getrlimit()", __LINE__ - 1); +} + +static inline void setr(int type, struct rlimit *rlim) +{ + if (setrlimit(type, rlim)) + shutdown(7, "setrlimit()", __LINE__ - 1); +} + +void validate_current_settings() +{ + int rlim_needed; + + if (cur_limits.rlim_cur < 4096) { + printf("Current rlimit value for POSIX message queue bytes is " + "unreasonably low,\nincreasing.\n\n"); + cur_limits.rlim_cur = 8192; + cur_limits.rlim_max = 16384; + setr(RLIMIT_MSGQUEUE, &cur_limits); + } + + if (default_settings) { + rlim_needed = (cur_def_msgs + 1) * (cur_def_msgsize + 1 + + 2 * sizeof(void *)); + if (rlim_needed > cur_limits.rlim_cur) { + printf("Temporarily lowering default queue parameters " + "to something that will work\n" + "with the current rlimit values.\n\n"); + set(def_msgs, 10); + cur_def_msgs = 10; + set(def_msgsize, 128); + cur_def_msgsize = 128; + } + } else { + rlim_needed = (cur_max_msgs + 1) * (cur_max_msgsize + 1 + + 2 * sizeof(void *)); + if (rlim_needed > cur_limits.rlim_cur) { + printf("Temporarily lowering maximum queue parameters " + "to something that will work\n" + "with the current rlimit values in case this is " + "a kernel that ties the default\n" + "queue parameters to the maximum queue " + "parameters.\n\n"); + set(max_msgs, 10); + cur_max_msgs = 10; + set(max_msgsize, 128); + cur_max_msgsize = 128; + } + } +} + +/* + * test_queue - Test opening a queue, shutdown if we fail. This should + * only be called in situations that should never fail. We clean up + * after ourselves and return the queue attributes in *result. + */ +static inline void test_queue(struct mq_attr *attr, struct mq_attr *result) +{ + int flags = O_RDWR | O_EXCL | O_CREAT; + int perms = DEFFILEMODE; + + if ((queue = mq_open(queue_path, flags, perms, attr)) == -1) + shutdown(1, "mq_open()", __LINE__); + if (mq_getattr(queue, result)) + shutdown(1, "mq_getattr()", __LINE__); + if (mq_close(queue)) + shutdown(1, "mq_close()", __LINE__); + queue = -1; + if (mq_unlink(queue_path)) + shutdown(1, "mq_unlink()", __LINE__); +} + +/* + * Same as test_queue above, but failure is not fatal. + * Returns: + * 0 - Failed to create a queue + * 1 - Created a queue, attributes in *result + */ +static inline int test_queue_fail(struct mq_attr *attr, struct mq_attr *result) +{ + int flags = O_RDWR | O_EXCL | O_CREAT; + int perms = DEFFILEMODE; + + if ((queue = mq_open(queue_path, flags, perms, attr)) == -1) + return 0; + if (mq_getattr(queue, result)) + shutdown(1, "mq_getattr()", __LINE__); + if (mq_close(queue)) + shutdown(1, "mq_close()", __LINE__); + queue = -1; + if (mq_unlink(queue_path)) + shutdown(1, "mq_unlink()", __LINE__); + return 1; +} + +int main(int argc, char *argv[]) +{ + struct mq_attr attr, result; + + if (argc != 2) { + fprintf(stderr, "Must pass a valid queue name\n\n"); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + /* + * Although we can create a msg queue with a non-absolute path name, + * unlink will fail. So, if the name doesn't start with a /, add one + * when we save it. + */ + if (*argv[1] == '/') + queue_path = strdup(argv[1]); + else { + queue_path = malloc(strlen(argv[1]) + 2); + if (!queue_path) { + perror("malloc()"); + exit(1); + } + queue_path[0] = '/'; + queue_path[1] = 0; + strcat(queue_path, argv[1]); + } + + if (getuid() != 0) { + fprintf(stderr, "Not running as root, but almost all tests " + "require root in order to modify\nsystem settings. " + "Exiting.\n"); + exit(1); + } + + /* Find out what files there are for us to make tweaks in */ + def_msgs = fopen(DEF_MSGS, "r+"); + def_msgsize = fopen(DEF_MSGSIZE, "r+"); + max_msgs = fopen(MAX_MSGS, "r+"); + max_msgsize = fopen(MAX_MSGSIZE, "r+"); + + if (!max_msgs) + shutdown(2, "Failed to open msg_max", __LINE__); + if (!max_msgsize) + shutdown(2, "Failed to open msgsize_max", __LINE__); + if (def_msgs || def_msgsize) + default_settings = 1; + + /* Load up the current system values for everything we can */ + getr(RLIMIT_MSGQUEUE, &saved_limits); + cur_limits = saved_limits; + if (default_settings) { + saved_def_msgs = cur_def_msgs = get(def_msgs); + saved_def_msgsize = cur_def_msgsize = get(def_msgsize); + } + saved_max_msgs = cur_max_msgs = get(max_msgs); + saved_max_msgsize = cur_max_msgsize = get(max_msgsize); + + /* Tell the user our initial state */ + printf("\nInitial system state:\n"); + printf("\tUsing queue path:\t\t%s\n", queue_path); + printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", saved_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", saved_limits.rlim_max); + printf("\tMaximum Message Size:\t\t%d\n", saved_max_msgsize); + printf("\tMaximum Queue Size:\t\t%d\n", saved_max_msgs); + if (default_settings) { + printf("\tDefault Message Size:\t\t%d\n", saved_def_msgsize); + printf("\tDefault Queue Size:\t\t%d\n", saved_def_msgs); + } else { + printf("\tDefault Message Size:\t\tNot Supported\n"); + printf("\tDefault Queue Size:\t\tNot Supported\n"); + } + printf("\n"); + + validate_current_settings(); + + printf("Adjusted system state for testing:\n"); + printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", cur_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", cur_limits.rlim_max); + printf("\tMaximum Message Size:\t\t%d\n", cur_max_msgsize); + printf("\tMaximum Queue Size:\t\t%d\n", cur_max_msgs); + if (default_settings) { + printf("\tDefault Message Size:\t\t%d\n", cur_def_msgsize); + printf("\tDefault Queue Size:\t\t%d\n", cur_def_msgs); + } + + printf("\n\nTest series 1, behavior when no attr struct " + "passed to mq_open:\n"); + if (!default_settings) { + test_queue(NULL, &result); + printf("Given sane system settings, mq_open without an attr " + "struct succeeds:\tPASS\n"); + if (result.mq_maxmsg != cur_max_msgs || + result.mq_msgsize != cur_max_msgsize) { + printf("Kernel does not support setting the default " + "mq attributes,\nbut also doesn't tie the " + "defaults to the maximums:\t\t\tPASS\n"); + } else { + set(max_msgs, ++cur_max_msgs); + set(max_msgsize, ++cur_max_msgsize); + test_queue(NULL, &result); + if (result.mq_maxmsg == cur_max_msgs && + result.mq_msgsize == cur_max_msgsize) + printf("Kernel does not support setting the " + "default mq attributes and\n" + "also ties system wide defaults to " + "the system wide maximums:\t\t" + "FAIL\n"); + else + printf("Kernel does not support setting the " + "default mq attributes,\n" + "but also doesn't tie the defaults to " + "the maximums:\t\t\tPASS\n"); + } + } else { + printf("Kernel supports setting defaults separately from " + "maximums:\t\tPASS\n"); + /* + * While we are here, go ahead and test that the kernel + * properly follows the default settings + */ + test_queue(NULL, &result); + printf("Given sane values, mq_open without an attr struct " + "succeeds:\t\tPASS\n"); + if (result.mq_maxmsg != cur_def_msgs || + result.mq_msgsize != cur_def_msgsize) + printf("Kernel supports setting defaults, but does " + "not actually honor them:\tFAIL\n\n"); + else { + set(def_msgs, ++cur_def_msgs); + set(def_msgsize, ++cur_def_msgsize); + /* In case max was the same as the default */ + set(max_msgs, ++cur_max_msgs); + set(max_msgsize, ++cur_max_msgsize); + test_queue(NULL, &result); + if (result.mq_maxmsg != cur_def_msgs || + result.mq_msgsize != cur_def_msgsize) + printf("Kernel supports setting defaults, but " + "does not actually honor them:\t" + "FAIL\n"); + else + printf("Kernel properly honors default setting " + "knobs:\t\t\t\tPASS\n"); + } + set(def_msgs, cur_max_msgs + 1); + cur_def_msgs = cur_max_msgs + 1; + set(def_msgsize, cur_max_msgsize + 1); + cur_def_msgsize = cur_max_msgsize + 1; + if (cur_def_msgs * (cur_def_msgsize + 2 * sizeof(void *)) >= + cur_limits.rlim_cur) { + cur_limits.rlim_cur = (cur_def_msgs + 2) * + (cur_def_msgsize + 2 * sizeof(void *)); + cur_limits.rlim_max = 2 * cur_limits.rlim_cur; + setr(RLIMIT_MSGQUEUE, &cur_limits); + } + if (test_queue_fail(NULL, &result)) { + if (result.mq_maxmsg == cur_max_msgs && + result.mq_msgsize == cur_max_msgsize) + printf("Kernel properly limits default values " + "to lesser of default/max:\t\tPASS\n"); + else + printf("Kernel does not properly set default " + "queue parameters when\ndefaults > " + "max:\t\t\t\t\t\t\t\tFAIL\n"); + } else + printf("Kernel fails to open mq because defaults are " + "greater than maximums:\tFAIL\n"); + set(def_msgs, --cur_def_msgs); + set(def_msgsize, --cur_def_msgsize); + cur_limits.rlim_cur = cur_limits.rlim_max = cur_def_msgs * + cur_def_msgsize; + setr(RLIMIT_MSGQUEUE, &cur_limits); + if (test_queue_fail(NULL, &result)) + printf("Kernel creates queue even though defaults " + "would exceed\nrlimit setting:" + "\t\t\t\t\t\t\t\tFAIL\n"); + else + printf("Kernel properly fails to create queue when " + "defaults would\nexceed rlimit:" + "\t\t\t\t\t\t\t\tPASS\n"); + } + + /* + * Test #2 - open with an attr struct that exceeds rlimit + */ + printf("\n\nTest series 2, behavior when attr struct is " + "passed to mq_open:\n"); + cur_max_msgs = 32; + cur_max_msgsize = cur_limits.rlim_max >> 4; + set(max_msgs, cur_max_msgs); + set(max_msgsize, cur_max_msgsize); + attr.mq_maxmsg = cur_max_msgs; + attr.mq_msgsize = cur_max_msgsize; + if (test_queue_fail(&attr, &result)) + printf("Queue open in excess of rlimit max when euid = 0 " + "succeeded:\t\tFAIL\n"); + else + printf("Queue open in excess of rlimit max when euid = 0 " + "failed:\t\tPASS\n"); + attr.mq_maxmsg = cur_max_msgs + 1; + attr.mq_msgsize = 10; + if (test_queue_fail(&attr, &result)) + printf("Queue open with mq_maxmsg > limit when euid = 0 " + "succeeded:\t\tPASS\n"); + else + printf("Queue open with mq_maxmsg > limit when euid = 0 " + "failed:\t\tFAIL\n"); + attr.mq_maxmsg = 1; + attr.mq_msgsize = cur_max_msgsize + 1; + if (test_queue_fail(&attr, &result)) + printf("Queue open with mq_msgsize > limit when euid = 0 " + "succeeded:\t\tPASS\n"); + else + printf("Queue open with mq_msgsize > limit when euid = 0 " + "failed:\t\tFAIL\n"); + attr.mq_maxmsg = 65536; + attr.mq_msgsize = 65536; + if (test_queue_fail(&attr, &result)) + printf("Queue open with total size > 2GB when euid = 0 " + "succeeded:\t\tFAIL\n"); + else + printf("Queue open with total size > 2GB when euid = 0 " + "failed:\t\t\tPASS\n"); + seteuid(99); + attr.mq_maxmsg = cur_max_msgs; + attr.mq_msgsize = cur_max_msgsize; + if (test_queue_fail(&attr, &result)) + printf("Queue open in excess of rlimit max when euid = 99 " + "succeeded:\t\tFAIL\n"); + else + printf("Queue open in excess of rlimit max when euid = 99 " + "failed:\t\tPASS\n"); + attr.mq_maxmsg = cur_max_msgs + 1; + attr.mq_msgsize = 10; + if (test_queue_fail(&attr, &result)) + printf("Queue open with mq_maxmsg > limit when euid = 99 " + "succeeded:\t\tFAIL\n"); + else + printf("Queue open with mq_maxmsg > limit when euid = 99 " + "failed:\t\tPASS\n"); + attr.mq_maxmsg = 1; + attr.mq_msgsize = cur_max_msgsize + 1; + if (test_queue_fail(&attr, &result)) + printf("Queue open with mq_msgsize > limit when euid = 99 " + "succeeded:\t\tFAIL\n"); + else + printf("Queue open with mq_msgsize > limit when euid = 99 " + "failed:\t\tPASS\n"); + attr.mq_maxmsg = 65536; + attr.mq_msgsize = 65536; + if (test_queue_fail(&attr, &result)) + printf("Queue open with total size > 2GB when euid = 99 " + "succeeded:\t\tFAIL\n"); + else + printf("Queue open with total size > 2GB when euid = 99 " + "failed:\t\t\tPASS\n"); + + shutdown(0,"",0); +} diff --git a/tools/testing/selftests/mqueue/mq_perf_tests.c b/tools/testing/selftests/mqueue/mq_perf_tests.c new file mode 100644 index 000000000000..2fadd4b97045 --- /dev/null +++ b/tools/testing/selftests/mqueue/mq_perf_tests.c @@ -0,0 +1,741 @@ +/* + * This application is Copyright 2012 Red Hat, Inc. + * Doug Ledford <dledford@redhat.com> + * + * mq_perf_tests is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * mq_perf_tests is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For the full text of the license, see <http://www.gnu.org/licenses/>. + * + * mq_perf_tests.c + * Tests various types of message queue workloads, concentrating on those + * situations that invole large message sizes, large message queue depths, + * or both, and reports back useful metrics about kernel message queue + * performance. + * + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <signal.h> +#include <pthread.h> +#include <sched.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <mqueue.h> +#include <popt.h> + +static char *usage = +"Usage:\n" +" %s [-c #[,#..] -f] path\n" +"\n" +" -c # Skip most tests and go straight to a high queue depth test\n" +" and then run that test continuously (useful for running at\n" +" the same time as some other workload to see how much the\n" +" cache thrashing caused by adding messages to a very deep\n" +" queue impacts the performance of other programs). The number\n" +" indicates which CPU core we should bind the process to during\n" +" the run. If you have more than one physical CPU, then you\n" +" will need one copy per physical CPU package, and you should\n" +" specify the CPU cores to pin ourself to via a comma separated\n" +" list of CPU values.\n" +" -f Only usable with continuous mode. Pin ourself to the CPUs\n" +" as requested, then instead of looping doing a high mq\n" +" workload, just busy loop. This will allow us to lock up a\n" +" single CPU just like we normally would, but without actually\n" +" thrashing the CPU cache. This is to make it easier to get\n" +" comparable numbers from some other workload running on the\n" +" other CPUs. One set of numbers with # CPUs locked up running\n" +" an mq workload, and another set of numbers with those same\n" +" CPUs locked away from the test workload, but not doing\n" +" anything to trash the cache like the mq workload might.\n" +" path Path name of the message queue to create\n" +"\n" +" Note: this program must be run as root in order to enable all tests\n" +"\n"; + +char *MAX_MSGS = "/proc/sys/fs/mqueue/msg_max"; +char *MAX_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_max"; + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define MAX_CPUS 64 +char *cpu_option_string; +int cpus_to_pin[MAX_CPUS]; +int num_cpus_to_pin; +pthread_t cpu_threads[MAX_CPUS]; +pthread_t main_thread; +cpu_set_t *cpu_set; +int cpu_set_size; +int cpus_online; + +#define MSG_SIZE 16 +#define TEST1_LOOPS 10000000 +#define TEST2_LOOPS 100000 +int continuous_mode; +int continuous_mode_fake; + +struct rlimit saved_limits, cur_limits; +int saved_max_msgs, saved_max_msgsize; +int cur_max_msgs, cur_max_msgsize; +FILE *max_msgs, *max_msgsize; +int cur_nice; +char *queue_path = "/mq_perf_tests"; +mqd_t queue = -1; +struct mq_attr result; +int mq_prio_max; + +const struct poptOption options[] = { + { + .longName = "continuous", + .shortName = 'c', + .argInfo = POPT_ARG_STRING, + .arg = &cpu_option_string, + .val = 'c', + .descrip = "Run continuous tests at a high queue depth in " + "order to test the effects of cache thrashing on " + "other tasks on the system. This test is intended " + "to be run on one core of each physical CPU while " + "some other CPU intensive task is run on all the other " + "cores of that same physical CPU and the other task " + "is timed. It is assumed that the process of adding " + "messages to the message queue in a tight loop will " + "impact that other task to some degree. Once the " + "tests are performed in this way, you should then " + "re-run the tests using fake mode in order to check " + "the difference in time required to perform the CPU " + "intensive task", + .argDescrip = "cpu[,cpu]", + }, + { + .longName = "fake", + .shortName = 'f', + .argInfo = POPT_ARG_NONE, + .arg = &continuous_mode_fake, + .val = 0, + .descrip = "Tie up the CPUs that we would normally tie up in" + "continuous mode, but don't actually do any mq stuff, " + "just keep the CPU busy so it can't be used to process " + "system level tasks as this would free up resources on " + "the other CPU cores and skew the comparison between " + "the no-mqueue work and mqueue work tests", + .argDescrip = NULL, + }, + { + .longName = "path", + .shortName = 'p', + .argInfo = POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, + .arg = &queue_path, + .val = 'p', + .descrip = "The name of the path to use in the mqueue " + "filesystem for our tests", + .argDescrip = "pathname", + }, + POPT_AUTOHELP + POPT_TABLEEND +}; + +static inline void __set(FILE *stream, int value, char *err_msg); +void shutdown(int exit_val, char *err_cause, int line_no); +void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context); +void sig_action(int signum, siginfo_t *info, void *context); +static inline int get(FILE *stream); +static inline void set(FILE *stream, int value); +static inline int try_set(FILE *stream, int value); +static inline void getr(int type, struct rlimit *rlim); +static inline void setr(int type, struct rlimit *rlim); +static inline void open_queue(struct mq_attr *attr); +void increase_limits(void); + +static inline void __set(FILE *stream, int value, char *err_msg) +{ + rewind(stream); + if (fprintf(stream, "%d", value) < 0) + perror(err_msg); +} + + +void shutdown(int exit_val, char *err_cause, int line_no) +{ + static int in_shutdown = 0; + int errno_at_shutdown = errno; + int i; + + /* In case we get called by multiple threads or from an sighandler */ + if (in_shutdown++) + return; + + for (i = 0; i < num_cpus_to_pin; i++) + if (cpu_threads[i]) { + pthread_kill(cpu_threads[i], SIGUSR1); + pthread_join(cpu_threads[i], NULL); + } + + if (queue != -1) + if (mq_close(queue)) + perror("mq_close() during shutdown"); + if (queue_path) + /* + * Be silent if this fails, if we cleaned up already it's + * expected to fail + */ + mq_unlink(queue_path); + if (saved_max_msgs) + __set(max_msgs, saved_max_msgs, + "failed to restore saved_max_msgs"); + if (saved_max_msgsize) + __set(max_msgsize, saved_max_msgsize, + "failed to restore saved_max_msgsize"); + if (exit_val) + error(exit_val, errno_at_shutdown, "%s at %d", + err_cause, line_no); + exit(0); +} + +void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context) +{ + if (pthread_self() != main_thread) + pthread_exit(0); + else { + fprintf(stderr, "Caught signal %d in SIGUSR1 handler, " + "exiting\n", signum); + shutdown(0, "", 0); + fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n"); + exit(0); + } +} + +void sig_action(int signum, siginfo_t *info, void *context) +{ + if (pthread_self() != main_thread) + pthread_kill(main_thread, signum); + else { + fprintf(stderr, "Caught signal %d, exiting\n", signum); + shutdown(0, "", 0); + fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n"); + exit(0); + } +} + +static inline int get(FILE *stream) +{ + int value; + rewind(stream); + if (fscanf(stream, "%d", &value) != 1) + shutdown(4, "Error reading /proc entry", __LINE__); + return value; +} + +static inline void set(FILE *stream, int value) +{ + int new_value; + + rewind(stream); + if (fprintf(stream, "%d", value) < 0) + return shutdown(5, "Failed writing to /proc file", __LINE__); + new_value = get(stream); + if (new_value != value) + return shutdown(5, "We didn't get what we wrote to /proc back", + __LINE__); +} + +static inline int try_set(FILE *stream, int value) +{ + int new_value; + + rewind(stream); + fprintf(stream, "%d", value); + new_value = get(stream); + return new_value == value; +} + +static inline void getr(int type, struct rlimit *rlim) +{ + if (getrlimit(type, rlim)) + shutdown(6, "getrlimit()", __LINE__); +} + +static inline void setr(int type, struct rlimit *rlim) +{ + if (setrlimit(type, rlim)) + shutdown(7, "setrlimit()", __LINE__); +} + +/** + * open_queue - open the global queue for testing + * @attr - An attr struct specifying the desired queue traits + * @result - An attr struct that lists the actual traits the queue has + * + * This open is not allowed to fail, failure will result in an orderly + * shutdown of the program. The global queue_path is used to set what + * queue to open, the queue descriptor is saved in the global queue + * variable. + */ +static inline void open_queue(struct mq_attr *attr) +{ + int flags = O_RDWR | O_EXCL | O_CREAT | O_NONBLOCK; + int perms = DEFFILEMODE; + + queue = mq_open(queue_path, flags, perms, attr); + if (queue == -1) + shutdown(1, "mq_open()", __LINE__); + if (mq_getattr(queue, &result)) + shutdown(1, "mq_getattr()", __LINE__); + printf("\n\tQueue %s created:\n", queue_path); + printf("\t\tmq_flags:\t\t\t%s\n", result.mq_flags & O_NONBLOCK ? + "O_NONBLOCK" : "(null)"); + printf("\t\tmq_maxmsg:\t\t\t%d\n", result.mq_maxmsg); + printf("\t\tmq_msgsize:\t\t\t%d\n", result.mq_msgsize); + printf("\t\tmq_curmsgs:\t\t\t%d\n", result.mq_curmsgs); +} + +void *fake_cont_thread(void *arg) +{ + int i; + + for (i = 0; i < num_cpus_to_pin; i++) + if (cpu_threads[i] == pthread_self()) + break; + printf("\tStarted fake continuous mode thread %d on CPU %d\n", i, + cpus_to_pin[i]); + while (1) + ; +} + +void *cont_thread(void *arg) +{ + char buff[MSG_SIZE]; + int i, priority; + + for (i = 0; i < num_cpus_to_pin; i++) + if (cpu_threads[i] == pthread_self()) + break; + printf("\tStarted continuous mode thread %d on CPU %d\n", i, + cpus_to_pin[i]); + while (1) { + while (mq_send(queue, buff, sizeof(buff), 0) == 0) + ; + mq_receive(queue, buff, sizeof(buff), &priority); + } +} + +#define drain_queue() \ + while (mq_receive(queue, buff, MSG_SIZE, &prio_in) == MSG_SIZE) + +#define do_untimed_send() \ + do { \ + if (mq_send(queue, buff, MSG_SIZE, prio_out)) \ + shutdown(3, "Test send failure", __LINE__); \ + } while (0) + +#define do_send_recv() \ + do { \ + clock_gettime(clock, &start); \ + if (mq_send(queue, buff, MSG_SIZE, prio_out)) \ + shutdown(3, "Test send failure", __LINE__); \ + clock_gettime(clock, &middle); \ + if (mq_receive(queue, buff, MSG_SIZE, &prio_in) != MSG_SIZE) \ + shutdown(3, "Test receive failure", __LINE__); \ + clock_gettime(clock, &end); \ + nsec = ((middle.tv_sec - start.tv_sec) * 1000000000) + \ + (middle.tv_nsec - start.tv_nsec); \ + send_total.tv_nsec += nsec; \ + if (send_total.tv_nsec >= 1000000000) { \ + send_total.tv_sec++; \ + send_total.tv_nsec -= 1000000000; \ + } \ + nsec = ((end.tv_sec - middle.tv_sec) * 1000000000) + \ + (end.tv_nsec - middle.tv_nsec); \ + recv_total.tv_nsec += nsec; \ + if (recv_total.tv_nsec >= 1000000000) { \ + recv_total.tv_sec++; \ + recv_total.tv_nsec -= 1000000000; \ + } \ + } while (0) + +struct test { + char *desc; + void (*func)(int *); +}; + +void const_prio(int *prio) +{ + return; +} + +void inc_prio(int *prio) +{ + if (++*prio == mq_prio_max) + *prio = 0; +} + +void dec_prio(int *prio) +{ + if (--*prio < 0) + *prio = mq_prio_max - 1; +} + +void random_prio(int *prio) +{ + *prio = random() % mq_prio_max; +} + +struct test test2[] = { + {"\n\tTest #2a: Time send/recv message, queue full, constant prio\n", + const_prio}, + {"\n\tTest #2b: Time send/recv message, queue full, increasing prio\n", + inc_prio}, + {"\n\tTest #2c: Time send/recv message, queue full, decreasing prio\n", + dec_prio}, + {"\n\tTest #2d: Time send/recv message, queue full, random prio\n", + random_prio}, + {NULL, NULL} +}; + +/** + * Tests to perform (all done with MSG_SIZE messages): + * + * 1) Time to add/remove message with 0 messages on queue + * 1a) with constant prio + * 2) Time to add/remove message when queue close to capacity: + * 2a) with constant prio + * 2b) with increasing prio + * 2c) with decreasing prio + * 2d) with random prio + * 3) Test limits of priorities honored (double check _SC_MQ_PRIO_MAX) + */ +void *perf_test_thread(void *arg) +{ + char buff[MSG_SIZE]; + int prio_out, prio_in; + int i; + clockid_t clock; + pthread_t *t; + struct timespec res, start, middle, end, send_total, recv_total; + unsigned long long nsec; + struct test *cur_test; + + t = &cpu_threads[0]; + printf("\n\tStarted mqueue performance test thread on CPU %d\n", + cpus_to_pin[0]); + mq_prio_max = sysconf(_SC_MQ_PRIO_MAX); + if (mq_prio_max == -1) + shutdown(2, "sysconf(_SC_MQ_PRIO_MAX)", __LINE__); + if (pthread_getcpuclockid(cpu_threads[0], &clock) != 0) + shutdown(2, "pthread_getcpuclockid", __LINE__); + + if (clock_getres(clock, &res)) + shutdown(2, "clock_getres()", __LINE__); + + printf("\t\tMax priorities:\t\t\t%d\n", mq_prio_max); + printf("\t\tClock resolution:\t\t%d nsec%s\n", res.tv_nsec, + res.tv_nsec > 1 ? "s" : ""); + + + + printf("\n\tTest #1: Time send/recv message, queue empty\n"); + printf("\t\t(%d iterations)\n", TEST1_LOOPS); + prio_out = 0; + send_total.tv_sec = 0; + send_total.tv_nsec = 0; + recv_total.tv_sec = 0; + recv_total.tv_nsec = 0; + for (i = 0; i < TEST1_LOOPS; i++) + do_send_recv(); + printf("\t\tSend msg:\t\t\t%d.%ds total time\n", + send_total.tv_sec, send_total.tv_nsec); + nsec = ((unsigned long long)send_total.tv_sec * 1000000000 + + send_total.tv_nsec) / TEST1_LOOPS; + printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); + printf("\t\tRecv msg:\t\t\t%d.%ds total time\n", + recv_total.tv_sec, recv_total.tv_nsec); + nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 + + recv_total.tv_nsec) / TEST1_LOOPS; + printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); + + + for (cur_test = test2; cur_test->desc != NULL; cur_test++) { + printf(cur_test->desc); + printf("\t\t(%d iterations)\n", TEST2_LOOPS); + prio_out = 0; + send_total.tv_sec = 0; + send_total.tv_nsec = 0; + recv_total.tv_sec = 0; + recv_total.tv_nsec = 0; + printf("\t\tFilling queue..."); + fflush(stdout); + clock_gettime(clock, &start); + for (i = 0; i < result.mq_maxmsg - 1; i++) { + do_untimed_send(); + cur_test->func(&prio_out); + } + clock_gettime(clock, &end); + nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) * + 1000000000) + (end.tv_nsec - start.tv_nsec); + printf("done.\t\t%lld.%llds\n", nsec / 1000000000, + nsec % 1000000000); + printf("\t\tTesting..."); + fflush(stdout); + for (i = 0; i < TEST2_LOOPS; i++) { + do_send_recv(); + cur_test->func(&prio_out); + } + printf("done.\n"); + printf("\t\tSend msg:\t\t\t%d.%ds total time\n", + send_total.tv_sec, send_total.tv_nsec); + nsec = ((unsigned long long)send_total.tv_sec * 1000000000 + + send_total.tv_nsec) / TEST2_LOOPS; + printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); + printf("\t\tRecv msg:\t\t\t%d.%ds total time\n", + recv_total.tv_sec, recv_total.tv_nsec); + nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 + + recv_total.tv_nsec) / TEST2_LOOPS; + printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); + printf("\t\tDraining queue..."); + fflush(stdout); + clock_gettime(clock, &start); + drain_queue(); + clock_gettime(clock, &end); + nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) * + 1000000000) + (end.tv_nsec - start.tv_nsec); + printf("done.\t\t%lld.%llds\n", nsec / 1000000000, + nsec % 1000000000); + } + return 0; +} + +void increase_limits(void) +{ + cur_limits.rlim_cur = RLIM_INFINITY; + cur_limits.rlim_max = RLIM_INFINITY; + setr(RLIMIT_MSGQUEUE, &cur_limits); + while (try_set(max_msgs, cur_max_msgs += 10)) + ; + cur_max_msgs = get(max_msgs); + while (try_set(max_msgsize, cur_max_msgsize += 1024)) + ; + cur_max_msgsize = get(max_msgsize); + if (setpriority(PRIO_PROCESS, 0, -20) != 0) + shutdown(2, "setpriority()", __LINE__); + cur_nice = -20; +} + +int main(int argc, char *argv[]) +{ + struct mq_attr attr; + char *option, *next_option; + int i, cpu; + struct sigaction sa; + poptContext popt_context; + char rc; + void *retval; + + main_thread = pthread_self(); + num_cpus_to_pin = 0; + + if (sysconf(_SC_NPROCESSORS_ONLN) == -1) { + perror("sysconf(_SC_NPROCESSORS_ONLN)"); + exit(1); + } + cpus_online = min(MAX_CPUS, sysconf(_SC_NPROCESSORS_ONLN)); + cpu_set = CPU_ALLOC(cpus_online); + if (cpu_set == NULL) { + perror("CPU_ALLOC()"); + exit(1); + } + cpu_set_size = CPU_ALLOC_SIZE(cpus_online); + CPU_ZERO_S(cpu_set_size, cpu_set); + + popt_context = poptGetContext(NULL, argc, (const char **)argv, + options, 0); + + while ((rc = poptGetNextOpt(popt_context)) > 0) { + switch (rc) { + case 'c': + continuous_mode = 1; + option = cpu_option_string; + do { + next_option = strchr(option, ','); + if (next_option) + *next_option = '\0'; + cpu = atoi(option); + if (cpu >= cpus_online) + fprintf(stderr, "CPU %d exceeds " + "cpus online, ignoring.\n", + cpu); + else + cpus_to_pin[num_cpus_to_pin++] = cpu; + if (next_option) + option = ++next_option; + } while (next_option && num_cpus_to_pin < MAX_CPUS); + /* Double check that they didn't give us the same CPU + * more than once */ + for (cpu = 0; cpu < num_cpus_to_pin; cpu++) { + if (CPU_ISSET_S(cpus_to_pin[cpu], cpu_set_size, + cpu_set)) { + fprintf(stderr, "Any given CPU may " + "only be given once.\n"); + exit(1); + } else + CPU_SET_S(cpus_to_pin[cpu], + cpu_set_size, cpu_set); + } + break; + case 'p': + /* + * Although we can create a msg queue with a + * non-absolute path name, unlink will fail. So, + * if the name doesn't start with a /, add one + * when we save it. + */ + option = queue_path; + if (*option != '/') { + queue_path = malloc(strlen(option) + 2); + if (!queue_path) { + perror("malloc()"); + exit(1); + } + queue_path[0] = '/'; + queue_path[1] = 0; + strcat(queue_path, option); + free(option); + } + break; + } + } + + if (continuous_mode && num_cpus_to_pin == 0) { + fprintf(stderr, "Must pass at least one CPU to continuous " + "mode.\n"); + poptPrintUsage(popt_context, stderr, 0); + exit(1); + } else if (!continuous_mode) { + num_cpus_to_pin = 1; + cpus_to_pin[0] = cpus_online - 1; + } + + if (getuid() != 0) { + fprintf(stderr, "Not running as root, but almost all tests " + "require root in order to modify\nsystem settings. " + "Exiting.\n"); + exit(1); + } + + max_msgs = fopen(MAX_MSGS, "r+"); + max_msgsize = fopen(MAX_MSGSIZE, "r+"); + if (!max_msgs) + shutdown(2, "Failed to open msg_max", __LINE__); + if (!max_msgsize) + shutdown(2, "Failed to open msgsize_max", __LINE__); + + /* Load up the current system values for everything we can */ + getr(RLIMIT_MSGQUEUE, &saved_limits); + cur_limits = saved_limits; + saved_max_msgs = cur_max_msgs = get(max_msgs); + saved_max_msgsize = cur_max_msgsize = get(max_msgsize); + errno = 0; + cur_nice = getpriority(PRIO_PROCESS, 0); + if (errno) + shutdown(2, "getpriority()", __LINE__); + + /* Tell the user our initial state */ + printf("\nInitial system state:\n"); + printf("\tUsing queue path:\t\t\t%s\n", queue_path); + printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", saved_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", saved_limits.rlim_max); + printf("\tMaximum Message Size:\t\t\t%d\n", saved_max_msgsize); + printf("\tMaximum Queue Size:\t\t\t%d\n", saved_max_msgs); + printf("\tNice value:\t\t\t\t%d\n", cur_nice); + printf("\n"); + + increase_limits(); + + printf("Adjusted system state for testing:\n"); + if (cur_limits.rlim_cur == RLIM_INFINITY) { + printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t(unlimited)\n"); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t(unlimited)\n"); + } else { + printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", + cur_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", + cur_limits.rlim_max); + } + printf("\tMaximum Message Size:\t\t\t%d\n", cur_max_msgsize); + printf("\tMaximum Queue Size:\t\t\t%d\n", cur_max_msgs); + printf("\tNice value:\t\t\t\t%d\n", cur_nice); + printf("\tContinuous mode:\t\t\t(%s)\n", continuous_mode ? + (continuous_mode_fake ? "fake mode" : "enabled") : + "disabled"); + printf("\tCPUs to pin:\t\t\t\t%d", cpus_to_pin[0]); + for (cpu = 1; cpu < num_cpus_to_pin; cpu++) + printf(",%d", cpus_to_pin[cpu]); + printf("\n"); + + sa.sa_sigaction = sig_action_SIGUSR1; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGHUP); + sigaddset(&sa.sa_mask, SIGINT); + sigaddset(&sa.sa_mask, SIGQUIT); + sigaddset(&sa.sa_mask, SIGTERM); + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGUSR1, &sa, NULL) == -1) + shutdown(1, "sigaction(SIGUSR1)", __LINE__); + sa.sa_sigaction = sig_action; + if (sigaction(SIGHUP, &sa, NULL) == -1) + shutdown(1, "sigaction(SIGHUP)", __LINE__); + if (sigaction(SIGINT, &sa, NULL) == -1) + shutdown(1, "sigaction(SIGINT)", __LINE__); + if (sigaction(SIGQUIT, &sa, NULL) == -1) + shutdown(1, "sigaction(SIGQUIT)", __LINE__); + if (sigaction(SIGTERM, &sa, NULL) == -1) + shutdown(1, "sigaction(SIGTERM)", __LINE__); + + if (!continuous_mode_fake) { + attr.mq_flags = O_NONBLOCK; + attr.mq_maxmsg = cur_max_msgs; + attr.mq_msgsize = MSG_SIZE; + open_queue(&attr); + } + for (i = 0; i < num_cpus_to_pin; i++) { + pthread_attr_t thread_attr; + void *thread_func; + + if (continuous_mode_fake) + thread_func = &fake_cont_thread; + else if (continuous_mode) + thread_func = &cont_thread; + else + thread_func = &perf_test_thread; + + CPU_ZERO_S(cpu_set_size, cpu_set); + CPU_SET_S(cpus_to_pin[i], cpu_set_size, cpu_set); + pthread_attr_init(&thread_attr); + pthread_attr_setaffinity_np(&thread_attr, cpu_set_size, + cpu_set); + if (pthread_create(&cpu_threads[i], &thread_attr, thread_func, + NULL)) + shutdown(1, "pthread_create()", __LINE__); + pthread_attr_destroy(&thread_attr); + } + + if (!continuous_mode) { + pthread_join(cpu_threads[0], &retval); + shutdown((long)retval, "perf_test_thread()", __LINE__); + } else { + while (1) + sleep(1); + } + shutdown(0, "", 0); +} |