From 67b92d3365b4df3c3463d6693a29608a9c2f85aa Mon Sep 17 00:00:00 2001 From: Vicente Adolfo Bolea Sanchez Date: Tue, 14 Dec 2021 16:42:25 -0500 Subject: [PATCH] ci: Add OLCF GitLab-CI Co-authored-by: Vicente Bolea Co-authored-by: Chuck Atkins 15585 bytes 8 files changed, 263 insertions(+), 34 deletions(-) create mode 100644 .gitlab/ci/ascent.yml create mode 100644 .gitlab/ci/ctest_test_submit.cmake create mode 100644 docs/batch_lsf.png diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dd24cba71..e34c31d0f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -132,6 +132,12 @@ when: on_success - when: never +.run_ecp_ci: &run_ecp_ci + rules: + - if: '$CI_PROJECT_PATH == "ecpcitest/vtk-m"' + when: on_success + - when: never + # General Longer Term Tasks: # - Setup clang tidy as sub-pipeline # - Setup a machine to replicate the issue in https://gitlab.kitware.com/vtk/vtk-m/-/issues/447 @@ -170,6 +176,41 @@ stages: script: - "ctest -VV -S .gitlab/ci/ctest_build.cmake" - sccache --show-stats + extends: + - .cmake_build_artifacts + +.cmake_test_linux: &cmake_test_linux + stage: test + timeout: 50 minutes + interruptible: true + before_script: + - *install_cmake + script: + - "ctest $CTEST_TIMEOUT -VV -S .gitlab/ci/ctest_test.cmake" + extends: + - .cmake_test_artifacts + +.cmake_memcheck_linux: &cmake_memcheck_linux + stage: test + timeout: 2 hours + interruptible: true + before_script: + - *install_cmake + script: + - "ctest -VV -S .gitlab/ci/ctest_memcheck.cmake" + artifacts: + expire_in: 24 hours + when: always + paths: + # The generated regression testing images + - build/*.png + - build/*.pnm + - build/*.pmm + reports: + junit: + - build/junit.xml + +.cmake_build_artifacts: &cmake_build_artifacts artifacts: expire_in: 24 hours when: always @@ -198,14 +239,7 @@ stages: # CDash files. - build/DartConfiguration.tcl -.cmake_test_linux: &cmake_test_linux - stage: test - timeout: 50 minutes - interruptible: true - before_script: - - *install_cmake - script: - - "ctest $CTEST_TIMEOUT -VV -S .gitlab/ci/ctest_test.cmake" +.cmake_test_artifacts: &cmake_test_artifacts artifacts: expire_in: 24 hours when: always @@ -227,25 +261,6 @@ stages: junit: - build/junit.xml -.cmake_memcheck_linux: &cmake_memcheck_linux - stage: test - timeout: 2 hours - interruptible: true - before_script: - - *install_cmake - script: - - "ctest -VV -S .gitlab/ci/ctest_memcheck.cmake" - artifacts: - expire_in: 24 hours - when: always - paths: - # The generated regression testing images - - build/*.png - - build/*.pnm - - build/*.pmm - reports: - junit: - - build/junit.xml include: - local: '/.gitlab/ci/centos7.yml' @@ -256,3 +271,4 @@ include: - local: '/.gitlab/ci/ubuntu1804.yml' - local: '/.gitlab/ci/ubuntu2004.yml' - local: '/.gitlab/ci/windows10.yml' + - local: '/.gitlab/ci/ascent.yml' diff --git a/.gitlab/ci/ascent.yml b/.gitlab/ci/ascent.yml new file mode 100644 index 000000000..66bf0a675 --- /dev/null +++ b/.gitlab/ci/ascent.yml @@ -0,0 +1,92 @@ +# Ad-hoc build that runs in the ECP Hardware, concretely in OLCF Ascent. + +build:ascent_gcc_cuda: + tags: [olcf, ascent, nobatch] + extends: + - .ascent_gcc_cuda + - .ascent_build + - .run_ecp_ci + - .cmake_build_artifacts + +test:ascent_gcc_cuda: + tags: [olcf, ascent, batch] + extends: + - .ascent_gcc_cuda + - .ascent_test + - .run_ecp_ci + - .cmake_test_artifacts + +.ascent_gcc_cuda: + variables: + CCACHE_BASEDIR: /gpfs/wolf/ + CCACHE_DIR: "/gpfs/wolf/proj-shared/csc331/vtk-m/ci/ccache/" + + # -isystem= is not affected by CCACHE_BASEDIR, thus we must ignore it + CCACHE_IGNOREOPTIONS: "-isystem=*" + CCACHE_NOHASHDIR: "true" + + CMAKE_BUILD_TYPE: RelWithDebInfo + CMAKE_GENERATOR: Unix Makefiles + CUSTOM_CI_BUILDS_DIR: "/gpfs/wolf/proj-shared/csc331/vtk-m/ci/runtime" + FF_ENABLE_JOB_CLEANUP: "true" + + CC: gcc + CXX: g++ + CUDAHOSTCXX: g++ + JOB_MODULES: gcc/8.1.1 spectrum-mpi lsf-tools cuda/11.2.0 + VTKM_SETTINGS: cuda+ascent+ccache + +.ascent_build: + stage: build + variables: + CTEST_MAX_PARALLELISM: 4 + before_script: + # Prep the environment + - module purge + - echo ${JOB_MODULES} + - module load git git-lfs cmake zstd ${JOB_MODULES} + - export PATH="/gpfs/wolf/proj-shared/csc331/vtk-m/ci/utils:$PATH" + - ccache -p + - ccache -z + + - git remote add lfs https://gitlab.kitware.com/vtk/vtk-m.git + - git fetch lfs + - git-lfs install + - git-lfs pull lfs + + # Start running the builds scripts + - cmake --version + - "cmake -V -P .gitlab/ci/config/gitlab_ci_setup.cmake" + - "ctest -VV -S .gitlab/ci/ctest_configure.cmake" + + script: + - "ctest -VV -S .gitlab/ci/ctest_build.cmake" + - ccache -s + +.ascent_test: + stage: test + variables: + GITLAB_CI_EMULATION: "true" + SCHEDULER_PARAMETERS: -P CSC331 -W 1:00 -nnodes 1 -alloc_flags gpudefault + CTEST_MAX_PARALLELISM: 8 + # Tests errors to address due to different env/arch in Ascent + # Refer to issue: https://gitlab.kitware.com/vtk/vtk-m/-/issues/652 + CTEST_EXCLUSIONS: >- + UnitTestMathSERIAL + UnitTestMathCUDA + UnitTestSerialDeviceAdapter + UnitTestAverageByKeySERIAL + UnitTestKeysSERIAL + UnitTestWorkletReduceByKeySERIAL + RegressionTestAmrArraysSERIAL + RegressionTestAmrArraysCUDA + + before_script: + # Prep the environment + - module purge + - module load git cmake ${JOB_MODULES} + + script: + - "jsrun -n1 -r1 -a1 -g1 -c7 ctest -VV -S .gitlab/ci/ctest_test.cmake || test_output=$?" + - ctest -VV -S .gitlab/ci/ctest_test_submit.cmake + - $(exit $test_output) diff --git a/.gitlab/ci/config/initial_config.cmake b/.gitlab/ci/config/initial_config.cmake index f20eac906..8b6dc70a4 100644 --- a/.gitlab/ci/config/initial_config.cmake +++ b/.gitlab/ci/config/initial_config.cmake @@ -57,7 +57,7 @@ foreach(option IN LISTS options) set(VTKm_NO_DEPRECATED_VIRTUAL "OFF" CACHE STRING "") elseif(no_testing STREQUAL option) - set(VTKm_ENABLE_TESTING OFF CACHE BOOL "") + set(VTKm_ENABLE_TESTING "OFF" CACHE STRING "") elseif(examples STREQUAL option) set(VTKm_ENABLE_EXAMPLES "ON" CACHE STRING "") @@ -108,6 +108,37 @@ foreach(option IN LISTS options) set(CMAKE_CXX_COMPILER "/opt/rocm/llvm/bin/clang++" CACHE FILEPATH "") set(VTKm_ENABLE_KOKKOS_HIP ON CACHE STRING "") set(CMAKE_HIP_ARCHITECTURES "gfx900" CACHE STRING "") + + elseif(ascent STREQUAL option) + set(CMAKE_C_FLAGS "-mcpu=power9" CACHE STRING "") + set(CMAKE_CXX_FLAGS "-mcpu=power9" CACHE STRING "") + + elseif(ccache STREQUAL option) + find_program(CCACHE_COMMAND NAMES ccache REQUIRED) + + set(CCACHE_VERSION "NotFound") + execute_process( + COMMAND ${CCACHE_COMMAND} "--version" + OUTPUT_VARIABLE CCACHE_VERSION + ECHO_ERROR_VARIABLE + ) + + string(REGEX REPLACE "\n" " " CCACHE_VERSION ${CCACHE_VERSION}) + string(REGEX REPLACE "^.*ccache version ([.0-9]*).*$" "\\1" + CCACHE_VERSION ${CCACHE_VERSION}) + + # We need a recent version of ccache in order to ignore -isystem while + # hashing keys for the building cache. + if(${CCACHE_VERSION} VERSION_GREATER_EQUAL 4) + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_COMMAND}" CACHE STRING "") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_COMMAND}" CACHE STRING "") + + if(VTKm_ENABLE_CUDA) + set(CMAKE_CUDA_COMPILER_LAUNCHER "${CCACHE_COMMAND}" CACHE STRING "") + endif() + else() + message(FATAL_ERROR "CCACHE version [${CCACHE_VERSION}] is <= 4") + endif() endif() endforeach() diff --git a/.gitlab/ci/ctest_test.cmake b/.gitlab/ci/ctest_test.cmake index c539e778f..647e278c1 100644 --- a/.gitlab/ci/ctest_test.cmake +++ b/.gitlab/ci/ctest_test.cmake @@ -11,7 +11,7 @@ ##============================================================================= # We need this CMake versions for tests -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.18) # Read the files from the build directory that contain # host information ( name, parallel level, etc ) @@ -26,17 +26,27 @@ set(test_exclusions $ENV{CTEST_EXCLUSIONS} ) +string(REPLACE " " ";" test_exclusions "${test_exclusions}") string(REPLACE ";" "|" test_exclusions "${test_exclusions}") if (test_exclusions) set(test_exclusions "(${test_exclusions})") endif () +if (CMAKE_VERSION VERSION_GREATER 3.21.0) + set(junit_args OUTPUT_JUNIT "${CTEST_BINARY_DIRECTORY}/junit.xml") +endif() + +set(PARALLEL_LEVEL "10") +if (DEFINED ENV{CTEST_MAX_PARALLELISM}) + set(PARALLEL_LEVEL $ENV{CTEST_MAX_PARALLELISM}) +endif() + ctest_test(APPEND - PARALLEL_LEVEL "10" + PARALLEL_LEVEL ${PARALLEL_LEVEL} RETURN_VALUE test_result EXCLUDE "${test_exclusions}" REPEAT "UNTIL_PASS:3" - OUTPUT_JUNIT "${CTEST_BINARY_DIRECTORY}/junit.xml" + ${junit_args} ) message(STATUS "ctest_test RETURN_VALUE: ${test_result}") diff --git a/.gitlab/ci/ctest_test_submit.cmake b/.gitlab/ci/ctest_test_submit.cmake new file mode 100644 index 000000000..ec4fd328a --- /dev/null +++ b/.gitlab/ci/ctest_test_submit.cmake @@ -0,0 +1,23 @@ +##============================================================================= +## +## Copyright (c) Kitware, Inc. +## All rights reserved. +## See LICENSE.txt for details. +## +## This software is distributed WITHOUT ANY WARRANTY; without even +## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +## PURPOSE. See the above copyright notice for more information. +## +##============================================================================= + +# We need this CMake versions for tests +cmake_minimum_required(VERSION 3.18) + +# Read the files from the build directory that contain +# host information ( name, parallel level, etc ) +include("$ENV{CI_PROJECT_DIR}/build/CIState.cmake") +ctest_read_custom_files("${CTEST_BINARY_DIRECTORY}") + +ctest_start(APPEND) +ctest_submit(PARTS Test BUILD_ID build_id) +message(STATUS "Test submission build_id: ${build_id}") diff --git a/CTestCustom.cmake.in b/CTestCustom.cmake.in index b63189bcc..1c4a65a76 100644 --- a/CTestCustom.cmake.in +++ b/CTestCustom.cmake.in @@ -41,6 +41,12 @@ list(APPEND CTEST_CUSTOM_WARNING_EXCEPTION # I am seeing these for the Kokkos builds, and I don't want to fight the # compiler flags there, so I'm just going to suppress those. ".*nvlink warning.*SM Arch.*not found in.*" + + # Disable warnings about third party libraries. + # Normally compilers do not generate warnings for includes using -isystem, + # however, that is not always the case, specially in exotic systems such as + # OLCF Ascent/Summit + ".*vtkm/thirdparty.*" ) list(APPEND CTEST_CUSTOM_WARNING_MATCH diff --git a/docs/CI-README.md b/docs/CI-README.md index 6b1baadb5..1028ed82e 100644 --- a/docs/CI-README.md +++ b/docs/CI-README.md @@ -1,4 +1,3 @@ - Gitlab CI =============== @@ -17,8 +16,8 @@ Gitlab CI - How to add a new tester - How to update an existing docker image -4. ECP OSTI CI - - Issues +4. ECP Continuous Integration + - OLCF Ascent testing machine # Kitware Gitlab CI @@ -258,3 +257,55 @@ sudo docker login --username= cd .gitlab/ci/docker sudo ./update_all.sh 20201230 ``` + +# ECP Continuous Integration + +## OLCF Ascent testing machine + +VTK-m provides CI builds that run at the OLCF Ascent testing cluster. OLCF +Ascent is a scaled down version of OLCF Summit which replicates the same +provisions of software and architecture found at OLCF Summit, this is very +useful for us since we are allowed to periodically and automatically branches of +VTK-m. This is a significant leap compared to our previous workflow in which we +would have someone to manually test at OLCF Summit every few months. + +The ECP Gitlab continuous integration infrastructure differs from the Kitware +Gitlab CI infrastructure at the following points: + +- Kitare Gitlab CI uses the `docker` executer as the _backend_ for its + `Gitlab-Runner` daemon whereas ECP Gitlab CI uses the Jacamar CI executer as + the _backend_ for the `Gitlab-Runner` daemon. + +- ECP Gitlab VTK-m project is a mirror Gitlab project of the main Kitware Gitlab + VTK-m repository. + +- The runners provided by the ECP Gitlab CI reside inside the OLCF Ascent + cluster. + +Jacamar CI allows us to implicitly launch jobs using the HPC job scheduler LSF. +Jacamar-CI also connects the LSF job with the GitLab project which allows us to +control its state, monitor its output, and access its artifacts. Below is a brief +diagram describing the relations between the GitLab CI instance and the job. + +![Jacamar CI with LSF](./batch_lsf.png) + +Our Ascent Pipeline is composed of two stages: + +1. The build stage, which builds VTK-m and runs in the batch nodes +2. The test stage, which runs VTK-m unit tests and runs at the compute nodes. + +Due to the isolated environment in which LFS jobs run at Ascent, we are not able +to access to our `sccache` file server as we do in our other CI builds, thus, +for this very site we provide a local installation of `ccache`. This it turns +out to provided similar hit ratios as `sscache`, since we do not have any other +CI site that runs a _Power9_ architecture. + +Lastly, builds and tests status are reported to our VTK-m CDashboard and are +displayed in the same groups as Kitware Gitlab's builds. + +As for the flavor being currently tested at ECP Ascent is VTK-m with CUDA and +GCC8. + +For a view of only ascent jobs refer to the following [link][cdash-ascent]. + +[cdash-ascent]: https://open.cdash.org/index.php?project=VTKM&filtercount=1&showfilters=1&field1=site&compare1=63&value1=ascent diff --git a/docs/batch_lsf.png b/docs/batch_lsf.png new file mode 100644 index 0000000000000000000000000000000000000000..282b54800c9d4e436cce28c2b0a9e9f12234983d GIT binary patch literal 15585 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 gxWE73J&5ug*%TutrSo8OA@@zjhUVunXRqA*9}