diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f92994c7..96262f7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,11 +1,30 @@ -name: Build and Test +name: Build and install on: - workflow_dispatch: - push: - branches: [ main ] - pull_request: - branches: [ main ] + workflow_call: + inputs: + # A host of inputs to this workflow are provided that allow various CMake + # options to be changed. + c_compiler: + description: + 'Provide a C compiler to use for the DCMAKE_C_COMPILER CMake option.' + required: true + type: string + cpp_compiler: + description: + 'Provide a C++ compiler to use for the DCMAKE_CXX_COMPILER CMake option.' + required: true + type: string + fortran_compiler: + description: + 'Provide a Fortran compiler to use for the DCMAKE_Fortran_COMPILER CMake option.' + required: true + type: string + shared_libs: + description: + 'Determines the DBUILD_SHARED_LIBS option.' + required: true + type: boolean env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) @@ -13,57 +32,82 @@ env: jobs: build: - # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. - # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - compiler: [ - {c: gcc-10, cpp: g++-10, fortran: gfortran-10}, - {c: clang-12, cpp: clang++-12, fortran: gfortran-10} - ] - + name: "${{ inputs.shared_libs && 'dynamic' || 'static' }}" + # The CMake configure and build commands are platform agnostic and should + # work equally well on Windows or Mac. You can convert this to a matrix + # build if you need cross-platform coverage. See: + # https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - name: Install libomp-devel + - name: Install libomp-devel and mpich # Ensure that the OpenMP development libraries are installed. run: | - sudo apt-get update - sudo apt-get install -y libomp-12-dev + sudo apt update + sudo apt install -y libomp-12-dev + sudo apt install -y mpich + + - name: Setup + # The name of the build directory and uploaded artifact depends on both + # the compiler and the 'shared_libs' input. + run: | + if [[ ${{ inputs.shared_libs }} ]] + then + LINKING=dynamic + else + LINKING=static + fi + COMPILER=${{ inputs.cpp_compiler }} + echo "BUILD_NAME=${COMPILER:0:-3}_${LINKING}_build" >> $GITHUB_ENV + echo "INSTALL_NAME=${COMPILER:0:-3}_${LINKING}_install" >> $GITHUB_ENV - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only + # required if you are using a single-configuration generator such as + # make. See: + # https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: > - cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} - -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cpp }} - -DCMAKE_Fortran_COMPILER=${{ matrix.compiler.fortran }} + cmake -B ${{ github.workspace }}/${{ env.BUILD_NAME }} + -DCMAKE_INSTALL_PREFIX="/home/runner/work/${{ env.INSTALL_NAME }}" + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} + -DCMAKE_C_COMPILER=${{ inputs.c_compiler }} + -DCMAKE_CXX_COMPILER=${{ inputs.cpp_compiler }} + -DCMAKE_Fortran_COMPILER=${{ inputs.fortran_compiler }} -DBUILD_TESTS=ON -DBUILD_FORTRAN_TESTS=OFF -DINCLUDE_GTEST=ON -DWARNINGS_AS_ERRORS=ON - -DUSE_SANITIZERS=ON + -DUSE_SANITIZERS=OFF -DENABLE_DOXYGEN=OFF + -DBUILD_SHARED_LIBS=${{ inputs.shared_libs }} - name: Build # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + run: cmake --build ${{ github.workspace }}/${{ env.BUILD_NAME }} --config ${{ env.BUILD_TYPE }} - # Run tests on a single thread - - name: "Test (1 thread)" - env: - OMP_NUM_THREADS: 1 - working-directory: ${{github.workspace}}/build - run: ctest -VV -C ${{env.BUILD_TYPE}} + - name: Perform installation + # Install the public headers and profiler libraries. + run: cmake --install ${{ github.workspace }}/${{ env.BUILD_NAME }} --config ${{ env.BUILD_TYPE }} + + - name: Tar build & install files + # Massively speeds up the subsequent artifact uploads. + run: | + tar czvf ${{ env.BUILD_NAME }}.tar.gz ${{ github.workspace }}/${{ env.BUILD_NAME }}/* + tar czvf ${{ env.INSTALL_NAME }}.tar.gz /home/runner/work/${{ env.INSTALL_NAME }}/* - # Run tests on 4 threads - - name: "Test (4 threads)" - env: - OMP_NUM_THREADS: 4 - working-directory: ${{github.workspace}}/build - run: ctest -VV -C ${{env.BUILD_TYPE}} + - name: Upload build artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: ${{ env.BUILD_NAME }} + path: ${{ env.BUILD_NAME }}.tar.gz + retention-days: 1 + if-no-files-found: error + - name: Upload install artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: ${{ env.INSTALL_NAME }} + path: ${{ env.INSTALL_NAME }}.tar.gz + retention-days: 1 + if-no-files-found: error diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9cc64f6b..b6f4272c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -7,7 +7,7 @@ on: paths: - 'src/**.h' - 'src/**.cpp' - - 'tests/**' + - 'tests/unit_tests/**' - '.github/workflows/coverage.yml' env: diff --git a/.github/workflows/deploy_tests.yml b/.github/workflows/deploy_tests.yml new file mode 100644 index 00000000..2bfdf711 --- /dev/null +++ b/.github/workflows/deploy_tests.yml @@ -0,0 +1,75 @@ +name: Deploy all tests + +on: + workflow_dispatch: + push: + branches: [ main ] + paths-ignore: + - '.github/workflows/documentation.yml' + - '.github/workflows/coverage.yml' + pull_request: + branches: [ main ] + paths-ignore: + - '.github/workflows/documentation.yml' + - '.github/workflows/coverage.yml' + +env: + # Define the compiler matrix here as it is regularly reused. Additional + # configurations can easily be added by adding a new row of entries that + # follow the same format as shown. + COMPILER_MATRIX: > + [ {"c": "gcc-10", "cpp": "g++-10", "fortran": "gfortran-10" }, + {"c": "clang-12", "cpp": "clang++-12", "fortran": "gfortran-10" } ] + +jobs: + + setup: + runs-on: ubuntu-20.04 + outputs: + compiler_matrix: ${{ env.COMPILER_MATRIX }} + steps: + - name: Setup + run: echo "Adding matrix to this jobs output for use in subsequent jobs" + + build-install: + # The reusable build workflow is called for each compiler configuration + # and for both static and dynamic linking. + name: "Build/install: ${{ matrix.compiler.cpp }}" + needs: [ setup ] + strategy: + matrix: + compiler: ${{ fromJSON(needs.setup.outputs.compiler_matrix) }} + shared_libs: [ true, false ] + uses: ./.github/workflows/build.yml + with: + c_compiler: ${{ matrix.compiler.c }} + cpp_compiler: ${{ matrix.compiler.cpp }} + fortran_compiler: ${{ matrix.compiler.fortran }} + shared_libs: ${{ matrix.shared_libs }} + + unit_test: + # Unit tests ran for the same compiler configurations as before, but only + # the dynamic build is tested. + name: "Unit test: ${{ matrix.compiler.cpp }}" + needs: [ setup, build-install ] + strategy: + matrix: + compiler: ${{ fromJSON(needs.setup.outputs.compiler_matrix) }} + uses: ./.github/workflows/unit.yml + with: + cpp_compiler: ${{ matrix.compiler.cpp }} + + system_test: + # System test is compiled and ran for all compiler configurations, using + # both the static and dynamic artifacts. + name: "System test: ${{ matrix.compiler.cpp }}" + needs: [ setup, build-install ] + strategy: + matrix: + compiler: ${{ fromJSON(needs.setup.outputs.compiler_matrix) }} + shared_libs: [ true, false ] + uses: ./.github/workflows/system.yml + with: + cpp_compiler: ${{ matrix.compiler.cpp }} + fortran_compiler: ${{ matrix.compiler.fortran }} + shared_libs: ${{ matrix.shared_libs}} \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 09c57fcc..02e0ea12 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -42,17 +42,18 @@ jobs: - name: Update Doxygen Pages run: | - mkdir -p ${{github.workspace}}/docs/documentation - cp -rf ${{github.workspace}}/build/html/* ${{github.workspace}}/docs/documentation/. + mkdir -p ${{ github.workspace }}/docs/documentation + cp -rf ${{ github.workspace }}/build/html/* ${{ github.workspace }}/docs/documentation/. git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git config --global user.email 'github-actions[bot]' + git remote update git add --force docs/documentation git stash - git remote update git checkout gh-pages - rm -rf docs/documentation + git rm -r --ignore-unmatch docs/documentation/ + rm -rf docs/documentation/ git stash pop git reset git add --force docs/documentation - git commit -a --allow-empty -m "Update Doxygen documentation" + git commit --allow-empty -m "Update Doxygen documentation" git push diff --git a/.github/workflows/system.yml b/.github/workflows/system.yml new file mode 100644 index 00000000..c13e5d62 --- /dev/null +++ b/.github/workflows/system.yml @@ -0,0 +1,81 @@ +name: Run system tests + +on: + workflow_call: + inputs: + cpp_compiler: + description: + 'Name of the C++ compiler used to build and install beforehand.' + required: true + type: string + fortran_compiler: + description: + 'Name of the fortran compiler used to build and install.' + required: true + type: string + shared_libs: + description: + 'Determines whether the static or dynamic binaries are downloaded.' + required: true + type: boolean + +jobs: + run: + name: "${{ inputs.shared_libs && 'dynamic' || 'static' }}" + runs-on: ubuntu-20.04 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v3 + + - name: Install libomp and mpich + run: | + sudo apt update + sudo apt install -y libomp-12-dev + sudo apt install -y mpich + + - name: Prepare to download correct artifact + run: | + if [[ ${{ inputs.shared_libs }} ]] + then + LINKING=dynamic + else + LINKING=static + fi + COMPILER=${{ inputs.cpp_compiler }} + echo "INSTALL_NAME=${COMPILER:0:-3}_${LINKING}_install" >> $GITHUB_ENV + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: ${{ env.INSTALL_NAME }} + + - name: Unzip and setup PROF variable + run: | + tar xzvf ${{ env.INSTALL_NAME }}.tar.gz -C / + PROF="/home/runner/work/${{ env.INSTALL_NAME }}" + ls -al $PROF + echo "PROF=$PROF" >> $GITHUB_ENV + + - name: Compile and run C++ test + # Test installed libraries using a C++ and fortran system test. Both use + # 2 OpenMP threads on 4 MPI ranks. + env: + OMP_NUM_THREADS: 2 + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PROF/lib + mpicxx -o systest -cxx=${{ inputs.cpp_compiler }} -std=c++17 \ + -L${PROF}/lib -I${PROF}/include ./tests/system_tests/system_test.cpp \ + -lprofiler -fopenmp + mpirun -n 4 ./systest + + - name: Compile and run Fortran test + env: + OMP_NUM_THREADS: 2 + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PROF/lib + mpifort -fc=${{ inputs.fortran_compiler }} -o test -pedantic \ + -L${PROF}/lib -I${PROF}/include ./tests/system_tests/system_test.f90 \ + -lprofiler_f -lprofiler_c -lprofiler -fopenmp -lstdc++ + mpirun -n 4 ./test diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 00000000..2a00bd7a --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,47 @@ +name: Run unit tests + +on: + workflow_call: + inputs: + cpp_compiler: + description: + 'Name of the C++ compiler used to build beforehand.' + required: true + type: string + +env: + BUILD_TYPE: Debug + +jobs: + run: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - name: Setup + # This time only the compiler matters, unit tests are only ran for the + # dynamic build directories. + run: | + COMPILER=${{ inputs.cpp_compiler }} + echo "BUILD_NAME=${COMPILER:0:-3}_dynamic_build" >> $GITHUB_ENV + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: ${{ env.BUILD_NAME }} + + - name: Unzip + run: tar xzvf ${{ env.BUILD_NAME }}.tar.gz -C / + + - name: Run Unit Tests (1 thread) + # Do single and multi threaded unit test runs + env: + OMP_NUM_THREADS: 1 + working-directory: ${{ github.workspace }}/${{ env.BUILD_NAME}} + run: ctest -VV -C ${{ env.BUILD_TYPE }} + + - name: Run Unit Tests (4 threads) + env: + OMP_NUM_THREADS: 4 + working-directory: ${{ github.workspace}}/${{ env.BUILD_NAME}} + run: ctest -VV -C ${{ env.BUILD_TYPE }} diff --git a/documentation/Doxygen/Profiler.md b/documentation/Doxygen/Profiler.md index 85e38a87..08382622 100644 --- a/documentation/Doxygen/Profiler.md +++ b/documentation/Doxygen/Profiler.md @@ -22,35 +22,63 @@ Argument | Options (Default **Bold**)| Description `-DBUILD_SHARED_LIBS` | **ON** / OFF | Determines whether the libraries are linked statically (`OFF`) or dynamically (`ON`). -# Metrics {#metrics} +# Metrics {#Metrics} The metrics to be included are going to be based on Performance Optimisation and Productivity (POP) Standard Metrics for Parallel Performance Analysis. -## Load Balance {#lb} - -~~~~~~~~~~~~~~~~~~~~~~~cpp -class A { - private: - int a_; - - public: - // Constructor - A(int a):a_(a){}; - - // Destructor - ~A; - - // Getter - int get_a(){return a_;} -}; -~~~~~~~~~~~~~~~~~~~~~~~ - -## Communication Efficiency {#comme} +### Load Balance TBD -## Unit Test Coverage {#coverage} +### Communication Efficiency -Unit test coverage (generated by Gcov and Lcov) can be found here. \ No newline at end of file +TBD + +# Testing {#Testing} + +## Unit Test Coverage + +A detailed unit test coverage report (generated by Gcov and Lcov) can be found here. + +## System Tests + +The `tests/system_tests` directory contains one C++ and one Fortran system test. The idea behind these is to regularly test the installation of the public header files and libraries. Both are included in the CI testing, but for compiling and running these manually see the "Running Manually" section below. + +Both tests also serve as examples of how calls to the profiler's API are made in their respective language. + +### Running Manually + +The requirements and supported compilers can be found in the top-level repository README. + +After checking out the profiler's repository and navigating to the root directory, an installation can be done by doing the following: + +``` +mkdir build +cd build +cmake .. +make +make install +``` +By default the `include` and `lib` directories will appear in your build directory but this can be changed via the `-DCMAKE_INSTALL_PREFIX` cmake option. + +It's now possible to compile and run one of the two system tests, the steps for doing so are outlined below depending on whether you want to run `system_test.cpp` or `system_test.f90`. + +> Note: both examples assume you're still in the build directory. The paths to the system tests and the "include"/"lib" directories ought to be altered if necessary. + +### C++ + +``` +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib +mpicxx -o test ../tests/system_tests/system_test.cpp -I./include -L./lib -lprofiler -fopenmp +mpirun -n 4 ./test +``` + +### Fortran + +``` +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib +mpifort -o test ../tests/system_tests/system_test.cpp -I./include -L./lib -lprofiler_f -lprofiler_c -fopenmp +mpirun -n 4 ./test +``` \ No newline at end of file diff --git a/tests/system_tests/CMakeLists.txt b/tests/system_tests/CMakeLists.txt deleted file mode 100644 index 4c18ee24..00000000 --- a/tests/system_tests/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# ------------------------------------------------------------------------------ -# (c) Crown copyright 2022 Met Office. All rights reserved. -# The file LICENCE, distributed with this code, contains details of the terms -# under which the code may be used. -# ------------------------------------------------------------------------------ diff --git a/tests/system_tests/system_test.cpp b/tests/system_tests/system_test.cpp new file mode 100644 index 00000000..446503a2 --- /dev/null +++ b/tests/system_tests/system_test.cpp @@ -0,0 +1,42 @@ +/*----------------------------------------------------------------------------*\ + (c) Crown copyright 2022 Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. +\*----------------------------------------------------------------------------*/ + +#include +#include "profiler.h" +#include +#include + +int main() { + + // Initialise MPI + MPI_Init(NULL,NULL); + MPI_Comm comm = MPI_COMM_WORLD; + + // Get current MPI rank + int crank; + MPI_Comm_rank(comm, &crank); + + // Begin OpenMP region + #pragma omp parallel default(none) shared(std::cout,prof,crank) + { + // Get current OMP thread + int cthread = omp_get_thread_num(); + + // Profile a simple print statement + auto prof_print = prof.start("print statement"); + #pragma omp critical + { + std::cout << "MPI rank " << crank << ", OMP thread " << cthread << std::endl; + } + prof.stop(prof_print); + } + + // Finish + prof.write(); + MPI_Finalize(); + + return 0; +} diff --git a/tests/system_tests/system_test.f90 b/tests/system_tests/system_test.f90 new file mode 100644 index 00000000..b5ea55c4 --- /dev/null +++ b/tests/system_tests/system_test.f90 @@ -0,0 +1,47 @@ +!------------------------------------------------------------------------------- +! (c) Crown copyright 2022 Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!------------------------------------------------------------------------------- + +program main + use profiler_mod + use omp_lib + use mpi + + implicit none + + integer :: ierr + integer :: comm + integer :: crank + integer :: cthread + + integer (kind=pik) :: prof_print + + ! Initialise MPI + call MPI_Init(ierr) + comm = MPI_COMM_WORLD + + ! Get current MPI rank + call MPI_Comm_rank(comm, crank, ierr) + + ! Begin OpenMP region + !$OMP PARALLEL DEFAULT(NONE) SHARED(crank,prof_print) PRIVATE(cthread) + + ! Get current OMP thread + cthread = omp_get_thread_num() + + ! Profile a simple print statement + call profiler_start(prof_print, "print statement") + !$OMP CRITICAL + write(*,'(A,I2,A,I2)') 'MPI rank ', crank, ', OMP thread ', cthread + !$OMP END CRITICAL + call profiler_stop(prof_print) + + !$OMP END PARALLEL + + ! Finish + call MPI_Finalize(ierr) + call profiler_write() + +end program main