diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d82b032..a3d75a92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,24 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) -#Define ROOTbench source tree +#---Define ROOTbench source tree set(ROOTBENCH_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +#---Include rootbench options +include(RootBenchOptions) + include(AddRootBench) +#---ROOTbench dependencies +find_program(TIME_EXECUTABLE time) +# Testing valid output of /usr/bin/time -v +exec_program(${TIME_EXECUTABLE} ARGS -v ${CMAKE_COMMAND} -E environment OUTPUT_VARIABLE TIME_OUTPUT RETURN_VALUE TIME_EXECUTABLE_VALID) +if(NOT TIME_EXECUTABLE) + if(NOT TIME_EXECUTABLE_VALID) + message(FATAL_ERROR "/usr/bin/time is a requirement for rootbench.git") + endif() +endif() + # You need first to tell CMake where to find the ROOT installation. This can either be the # final ROOT installation or a local build directory. In both cases it is using # the $ROOTSYS environment variable to locate it. @@ -52,13 +65,22 @@ endif() include(GoogleBenchmark) include(PytestBenchmark) +if(flamegraph) + # Check if perf is available in OS + find_program(PERF_EXECUTABLE perf) + if(NOT PERF_EXECUTABLE) + message(WARNING "Perf is not available in your system, please install it.") + set(flamegraph OFF CACHE BOOL "") + else() + include(FlameGraph) + message(STATUS "INFO: CPU & Mem FlameGraph generation option is enabled.") + endif() +endif() + #---Add ROOT include direcories and used compilation flags include_directories(${ROOT_INCLUDE_DIRS}) add_definitions(${ROOT_CXX_FLAGS}) -#---Include rootbench options -include(RootBenchOptions) - #---Enable test coverage ----------------------------------------------------------------------- if(coverage) set(GCC_COVERAGE_COMPILE_FLAGS "-g -fprofile-arcs -ftest-coverage") @@ -87,3 +109,4 @@ add_subdirectory(tools) #---Add the now all the benchmark sub-directories on this repository add_subdirectory(root) +configure_file(${PROJECT_SOURCE_DIR}/tools/.rootrc ${PROJECT_BINARY_DIR} COPYONLY) diff --git a/cmake/modules/AddRootBench.cmake b/cmake/modules/AddRootBench.cmake index 2b0f6144..7f25c24b 100644 --- a/cmake/modules/AddRootBench.cmake +++ b/cmake/modules/AddRootBench.cmake @@ -29,6 +29,19 @@ function(RB_ADD_SETUP_FIXTURE benchmark) endfunction(RB_ADD_SETUP_FIXTURE) +#---------------------------------------------------------------------------- +# function RB_ADD_FLAMEGRAPHCPU_FIXTURE() +#---------------------------------------------------------------------------- +function(RB_ADD_FLAMEGRAPH_FIXTURE benchmark) + cmake_parse_arguments(ARG "" "" "" ${ARGN}) + add_test(NAME rootbench-fixture-flamegraph-${benchmark} + COMMAND ${PROJECT_BINARY_DIR}/tools/flamegraph.sh -d ${PROJECT_BINARY_DIR} -b ${CMAKE_CURRENT_BINARY_DIR}/${benchmark} -c -m) + set_tests_properties(rootbench-fixture-flamegraph-${benchmark} PROPERTIES + ENVIRONMENT PATH=${PROJECT_BINARY_DIR}/FlameGraph-prefix/src/FlameGraph/:$ENV{PATH} + FIXTURES_CLEANUP rootbench-${benchmark}) +endfunction(RB_ADD_FLAMEGRAPH_FIXTURE) + + #---------------------------------------------------------------------------- # function RB_ADD_GBENCHMARK( source1 source2... LIBRARIES libs) #---------------------------------------------------------------------------- @@ -45,10 +58,13 @@ function(RB_ADD_GBENCHMARK benchmark) # to implement because some ROOT components create more than one library. target_link_libraries(${benchmark} ${ARG_LIBRARIES} gbenchmark RBSupport rt) #ROOT_PATH_TO_STRING(mangled_name ${benchmark} PATH_SEPARATOR_REPLACEMENT "-") - #ROOT_ADD_TEST(gbench${mangled_name} - # COMMAND ${benchmark} - # WORKING_DIR ${CMAKE_CURRENT_BINARY_DIR} - # LABELS "benchmark") + if(ARG_POSTCMD) + set(postcmd POSTCMD ${ARG_POSTCMD}) + endif() + if(flamegraph) + set(postcmd ${postcmd} "${PROJECT_SOURCE_DIR}/rootbench-scripts/flamegraph.sh -d ${PROJECT_BINARY_DIR} -b ${CMAKE_CURRENT_BINARY_DIR}/${benchmark} -c -m") + add_dependencies(${benchmark} FlameGraph) + endif() if(${ARG_LABEL} STREQUAL "long") set(${TIMEOUT_VALUE} 1200) elseif($ARG_LABEL STREQUAL "short") @@ -67,6 +83,12 @@ function(RB_ADD_GBENCHMARK benchmark) RB_ADD_SETUP_FIXTURE(${benchmark} SETUP ${ARG_SETUP}) endif() + # Flamegraphs (both mem and cpu) + if(flamegraph) + RB_ADD_FLAMEGRAPH_FIXTURE(${benchmark}) + add_dependencies(${benchmark} flamegraph) + endif() + # Add benchmark as a CTest add_test(NAME rootbench-${benchmark} COMMAND ${benchmark} --benchmark_out_format=csv --benchmark_out=rootbench-gbenchmark-${benchmark}.csv --benchmark_color=false) diff --git a/cmake/modules/FlameGraph.cmake b/cmake/modules/FlameGraph.cmake new file mode 100644 index 00000000..5b3f5042 --- /dev/null +++ b/cmake/modules/FlameGraph.cmake @@ -0,0 +1,14 @@ +include(ExternalProject) + +ExternalProject_Add(FlameGraph + GIT_REPOSITORY "https://github.com/brendangregg/FlameGraph.git" + UPDATE_COMMAND "" + #PATCH_COMMAND patch < ${CMAKE_SOURCE_DIR}/tools/stackcollapse-perf.patch + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + EXCLUDE_FROM_ALL 1 +) + +# Register flamegraph +add_custom_target(flamegraph DEPENDS FlameGraph) diff --git a/cmake/modules/RootBenchOptions.cmake b/cmake/modules/RootBenchOptions.cmake index 9829657d..5311178e 100644 --- a/cmake/modules/RootBenchOptions.cmake +++ b/cmake/modules/RootBenchOptions.cmake @@ -4,4 +4,5 @@ # # TBD: to introduce special function for options (similar to root.git) #---------------------------------------------------------------------------- option(coverage OFF) -option(rootbench-datafiles OFF) \ No newline at end of file +option(rootbench-datafiles OFF) +option(flamegraph "CPU & Mem FlameGraph generation option" OFF) diff --git a/root/roofit/roofit/CMakeLists.txt b/root/roofit/roofit/CMakeLists.txt index a78169b9..ae4bbe53 100644 --- a/root/roofit/roofit/CMakeLists.txt +++ b/root/roofit/roofit/CMakeLists.txt @@ -7,3 +7,4 @@ RB_ADD_GBENCHMARK(RoofitUnBinnedBenchmark RooFitUnBinnedBenchmarks.cxx LABEL long LIBRARIES Core Hist MathCore RIO RooFit RooStats RooFitCore HistFactory) + diff --git a/tools/.rootrc b/tools/.rootrc new file mode 100644 index 00000000..2ebeffdf --- /dev/null +++ b/tools/.rootrc @@ -0,0 +1 @@ +RooFit.Banner: no diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index e18fc0eb..a06535c0 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1 +1,2 @@ configure_file(download_files.sh . COPYONLY) +configure_file(flamegraph.sh . COPYONLY) diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 00000000..d947966c --- /dev/null +++ b/tools/README.md @@ -0,0 +1,35 @@ +## About + +flamegraph.sh is a script that generates Flame Graphs for each benchmark in `rootbench.git`. More information on Flame Graphs can be found [here](http://www.brendangregg.com/flamegraphs.html). + +## Options + +See the USAGE message (--help) for options. + +To generate only CPU or only memory Flame Graphs, use `-c` or `-m` respectively. To generate both CPU and memory Flame Graphs for all benchmarks at once run the following command: +```bash +flamegraph.sh -d path/to/rootbench/build/dir -a -c -m +``` + +To generate Flame Graphs for specific benchmark just run the following command with `-c` or `-m` options or both: + ```bash +flamegraph.sh -d path/to/rootbench/build/dir -b path/to/benchmark +``` + +## Configuration + +If `perf` cannot find symbols in the program try to execute the following commands +```bash +echo 0 > /proc/sys/kernel/kptr_restrict +echo 1 > /proc/sys/kernel/sched_schedstats +``` + +To generate Flame Graphs for each benchmark add `-Dflamegraph=ON` option to your cmake configuration. + +```bash +cmake ../rootbench -Dflamegraph=ON +make -j4 +ctest -V -R rootbench-CLASSNAMEBenchmarks +``` +You can also run the script for Flame Graph generation from your rootbench directory. +The Flame Graphs will be stored in the local rootbench build directory under FlameGraph folder. diff --git a/tools/flamegraph.sh b/tools/flamegraph.sh new file mode 100755 index 00000000..5c4df81b --- /dev/null +++ b/tools/flamegraph.sh @@ -0,0 +1,184 @@ +#!/bin/bash + +BENCHMARKPATTERN="*benchmark*" +MKDIR=$(which mkdir) +BASENAME=$(which basename) +DIRNAME=$(which dirname) +FIND=$(which find) +SED=$(which sed) +PERF=$(which perf) +STACKCOLLAPSE=stackcollapse-perf.pl +FLAMEGRAPH=flamegraph.pl + +usage() { + echo + echo "--------------------------FlameGraph Generator---------------------------" + echo + echo "Usage: $0 [OPTION]..." + echo "This script generates FlameGraphs for benchmarks !!!" + echo "OPTIONS" + echo " -d, --builddir path Create all benchmarks." + echo " -b, --benchmarkfile path Location of ROOT benchmark file" + echo " -a, --all Create all benchmarks." + echo " -c, --cpu Generate CPU FlameGraphs" + echo " -m, --memory Generate Memory FlameGraphs" + echo " -h, --help Display this help and exit" +} + +usage_short() { + echo "Usage: $0 -d | --builddir path [-b | --benchmarkfile filepath] [-a | --all] [-c | --cpu] [-m | --memory] [-h | --help]" + exit 1 +} + +get_bm_fn() { + if [ ! -f $1 ]; then + echo "Can't find the benchmark file" + exit 1 + else + bm_fn_full=$1 + bm_fn=$($BASENAME $bm_fn_full) + fi +} + +get_build_dir() { + if [ ! -d $1 ]; then + echo "Can't find the build directory. Exiting..." + exit 1 + else + build_dir=$1 + flamegraph_base_dir=$build_dir/FlameGraph + fi +} + +get_bm_fn_interactive() { + read -p "Enter benchmark filename: " bm_fn_full + if [ -z $bm_fn_full ]; then + echo "You did not enter any filename. Exiting..." + exit 1 + fi + get_bm_fn $bm_fn_full +} + +perf_record_cpu() { + $PERF record -F 50 --call-graph dwarf $1 --benchmark_filter=${2}$ +} + +perf_script_cpu() { + $PERF script | stackcollapse-perf.pl | flamegraph.pl --title $1 >$2.svg +} + +perf_record_mem() { + $PERF record -F 50 -e page-faults --call-graph dwarf $1 --benchmark_filter=${2}$ +} + +perf_script_mem() { + $PERF script | stackcollapse-perf.pl | flamegraph.pl --color=mem --title=$1 --countname="pages" >$2.svg +} + +perf_rec_scr_cpu() { + perf_record_cpu $1 $2 + perf_script_cpu $2 $3 +} + +perf_rec_scr_mem() { + perf_record_mem $1 $2 + perf_script_mem $2 $3 +} + +get_bm_files() { + bm_file_list=$($FIND $build_dir/root -iname "$BENCHMARKPATTERN" | grep -v "CMakeFiles\|pyroot\|interpreter\|.csv") +} + +run_bm() { + for bm_fn_full in $bm_file_list; do + bm_fn=$($BASENAME $bm_fn_full) + bm_dn=$($DIRNAME $bm_fn_full) + bm_path=$(echo "$bm_dn" | $SED -n "s|^$build_dir/||p") + bm_sub_list=$($bm_fn_full --benchmark_list_tests) + flamegraph_base_dir_mem=${flamegraph_base_dir}/FlameGraph_Memory + flamegraph_base_dir_cpu=${flamegraph_base_dir}/FlameGraph_CPU + outputdir_full_mem=${flamegraph_base_dir_mem}/$bm_path/$bm_fn + outputdir_full_cpu=${flamegraph_base_dir_cpu}/$bm_path/$bm_fn + if [ "$memory" = "y" ]; then + $MKDIR -p $outputdir_full_mem + if [ $? -ne 0 ]; then + echo "Can't create directory $1. Exiting..." + exit 1 + fi + fi + if [ "$cpu" = "y" ]; then + $MKDIR -p $outputdir_full_cpu + if [ $? -ne 0 ]; then + echo "Can't create directory $1. Exiting..." + exit 1 + fi + fi + for bm in $bm_sub_list; do + bm_modified_fn=$(echo "$bm" | $SED "s|[/:]|-|g") #replacing all "/" and ":" with "-" + if [ "$cpu"="y" ]; then + perf_rec_scr_cpu $bm_fn_full $bm ${outputdir_full_cpu}/${bm_modified_fn}_FlameGraph + fi + if [ "$memory"="y" ]; then + perf_rec_scr_mem $bm_fn_full $bm ${outputdir_full_mem}/${bm_modified_fn}_FlameGraph + fi + done + done +} + +##### Main ##### + +[ $# -eq 0 ] && usage_short + +while [ $# -gt 0 ]; do + case "$1" in + -b | --benchmarkfile) + shift + get_bm_fn $1 + ;; + -d | --builddir) + shift + get_build_dir $1 + ;; + + -a | --all) + all=y + ;; + -c | --cpu) + cpu=y + ;; + -m | --memory) + memory=y + ;; + -h | --help) + usage + exit + ;; + *) + usage + exit 1 + ;; + esac + shift +done + +if [ -z $build_dir ]; then + echo "************* Rootbench Build dir is mandatory *****************" + usage_short +fi + +if [ -z $cpu -a -z $memory ]; then + echo "************* Please specify the type of the FlameGraphs *****************" + usage_short +fi + +if [ "$all" = "y" ]; then + get_bm_files + run_bm +else + if [ ! -z $bm_fn_full ]; then + bm_file_list=$bm_fn_full + run_bm + fi +fi + +exit $? diff --git a/tools/stackcollapse-perf.patch b/tools/stackcollapse-perf.patch new file mode 100644 index 00000000..9df69458 --- /dev/null +++ b/tools/stackcollapse-perf.patch @@ -0,0 +1,13 @@ +diff --git a/stackcollapse-perf.pl b/stackcollapse-perf.pl +index e91f7de..fe88660 100755 +--- a/stackcollapse-perf.pl ++++ b/stackcollapse-perf.pl +@@ -80,7 +80,7 @@ my $include_pid = 0; # include process ID with process name + my $include_tid = 0; # include process & thread ID with process name + my $include_addrs = 0; # include raw address where a symbol can't be found + my $tidy_java = 1; # condense Java signatures +-my $tidy_generic = 1; # clean up function names a little ++my $tidy_generic = 0; # clean up function names a little + my $target_pname; # target process name from perf invocation + my $event_filter = ""; # event type filter, defaults to first encountered event + my $event_defaulted = 0; # whether we defaulted to an event (none provided)