diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ebee3cf..f8cb6e747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to ### For users #### Added +* A timer plugin is added to reflect the time consumed by pdi and its plugins. * Improved messages for specification tree errors, with file & line numbers and support for file names from Paraconf 1.1, [#657](https://github.com/pdidev/pdi/issues/657) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b077743a..ded60147b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,10 +76,9 @@ option(BUILD_SHARED_LIBS "Build shared libraries rather than static ones" option(BUILD_TRACE_PLUGIN "Build Trace plugin" ON) option(BUILD_USER_CODE_PLUGIN "Build User-code plugin" ON) option(BUILD_JSON_PLUGIN "Build JSON plugin" OFF) +option(BUILD_TIMER_PLUGIN "Build Timer plugin" ON) option(ENABLE_BENCHMARKING "Activate benchmarks in the test suite" OFF) - - ### Default build type if(NOT DEFINED CMAKE_BUILD_TYPE) @@ -295,9 +294,9 @@ endif() ## HDF5 if("${BUILD_DECL_HDF5_PLUGIN}" OR "${BUILD_DECL_NETCDF_PLUGIN}") - + set(HDF5_CMAKE_CACHE_ARGS) - + if("${BUILD_HDF5_PARALLEL}") set(HDF5_PREFER_PARALLEL ON) list(APPEND HDF5_CMAKE_CACHE_ARGS "-DHDF5_ENABLE_PARALLEL:BOOL=ON") @@ -473,7 +472,7 @@ sbuild_add_module(JSON_PLUGIN ENABLE_BUILD_FLAG BUILD_JSON_PLUGIN SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/plugins/json" DEPENDS PDI JSON - SUBSTEPS indent test + SUBSTEPS test ) @@ -536,3 +535,10 @@ sbuild_add_module(PDI_API_TESTS INSTALL_COMMAND "" SUBSTEPS test ) + +sbuild_add_module(TIMER_PLUGIN + ENABLE_BUILD_FLAG BUILD_TIMER_PLUGIN + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/plugins/timer" + DEPENDS PDI + SUBSTEPS test +) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 30a736faa..2020f0d53 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -40,6 +40,7 @@ option(BUILD_DECL_NETCDF_PLUGIN "Build Decl'NetCDF plug-in" OFF) option(BUILD_FORTRAN "Build with Fortran support" OFF) option(BUILD_PYCALL_PLUGIN "Build Pycall plug-in" OFF) option(BUILD_PYTHON "Build with Python support" OFF) +option(BUILD_TIMER_PLUGIN "Build Timer plugin" OFF) option(BUILD_JSON_PLUGIN "Build JSON plug-in" OFF) option(DISABLE_PDI "Disable the use of both PDI and Paraconf in the project" OFF) @@ -143,7 +144,6 @@ add_test(NAME PDI_example_json_F COMMAND "${RUNTEST_DIR}" "${MPIEXEC}" "${MPIEXE set_property(TEST PDI_example_json_F PROPERTY TIMEOUT 30) set_property(TEST PDI_example_json_F PROPERTY PROCESSORS 6) endif("${BUILD_FORTRAN}") - endif("${BUILD_JSON_PLUGIN}") add_test(NAME PDI_example_trace_C COMMAND "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" 3 ${MPIEXEC_PREFLAGS} "$" ${MPIEXEC_POSTFLAGS} "${CMAKE_CURRENT_SOURCE_DIR}/trace.yml") @@ -161,3 +161,11 @@ add_test(NAME PDI_example_trace_P COMMAND "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" set_property(TEST PDI_example_trace_P PROPERTY TIMEOUT 15) set_property(TEST PDI_example_trace_P PROPERTY PROCESSORS 3) endif("${BUILD_PYTHON}") + +if("${BUILD_TIMER_PLUGIN}") +if("${BUILD_DECL_NETCDF_PLUGIN}" AND "${BUILD_DECL_HDF5_PLUGIN}" AND "${BUILD_NETCDF_PARALLEL}") +add_test(NAME PDI_example_timer COMMAND "${RUNTEST_DIR}" "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" 3 ${MPIEXEC_PREFLAGS} "$" ${MPIEXEC_POSTFLAGS} "${CMAKE_CURRENT_SOURCE_DIR}/timer.yml") +set_property(TEST PDI_example_timer PROPERTY TIMEOUT 15) +set_property(TEST PDI_example_timer PROPERTY PROCESSORS 3) +endif("${BUILD_DECL_NETCDF_PLUGIN}" AND "${BUILD_DECL_HDF5_PLUGIN}" AND "${BUILD_NETCDF_PARALLEL}") +endif("${BUILD_TIMER_PLUGIN}") \ No newline at end of file diff --git a/example/timer.yml b/example/timer.yml new file mode 100644 index 000000000..86199b0d8 --- /dev/null +++ b/example/timer.yml @@ -0,0 +1,61 @@ +# duration in seconds +duration: 0.75 +# global [height, width] (excluding boundary conditions or ghosts) +datasize: [60, 12] +# degree of parallelism +parallelism: { height: 3, width: 1 } + +# only the following config is passed to PDI +pdi: + metadata: # type of small values for which PDI keeps a copy + iter: int # current iteration id + dsize: { size: 2, type: array, subtype: int } # local data size including ghosts/boundary + psize: { size: 2, type: array, subtype: int } # number of processes in each dimension + pcoord: { size: 2, type: array, subtype: int } # coordinate of the process + data: # type of values for which PDI does not keep a copy + main_field: { size: [ '$dsize[0]', '$dsize[1]' ], type: array, subtype: double } + plugins: + trace: debug + timer: + - timer_h5: "decl_hdf5" + - timer_nc: + start: "decl_netcdf_start_timer" + stop: "decl_netcdf_stop_timer" + - timer_user: [pycall, user_code] + - timer_json: {start: "json_start_timer", stop: "json_stop_timer"} + - timer_pdi: "pdi" + mpi: + decl_hdf5: + file: data.h5 + communicator: $MPI_COMM_WORLD # the MPI communicator used for HDF5 parallel synchronized write + datasets: # type of the datasets to create in file + data: + type: array + subtype: double + size: [10, '$psize[0]*($dsize[0]-2)', '$psize[1]*($dsize[1]-2)'] + write: + main_field: # the name of the data to write + dataset: data + mpio: COLLECTIVE # or INDEPENDENT + when: '$iter<10' # do only write the first 10 iterations (0...9) + memory_selection: # exclude ghosts from the data in memory + size: ['$dsize[0]-2', '$dsize[1]-2'] + start: [1, 1] + dataset_selection: # only write into a single slice in time + size: [1, '$dsize[0]-2', '$dsize[1]-2'] + start: [$iter, '($dsize[0]-2)*$pcoord[0]', '($dsize[1]-2)*$pcoord[1]'] + decl_netcdf: + file: data.nc + communicator: $MPI_COMM_WORLD # the MPI communicator used for HDF5 parallel synchronized write + variables: # type of the datasets to create in file + v_data: + type: array + subtype: double + size: [10, '$psize[0]*$dsize[0]', '$psize[1]*$dsize[1]'] + write: + main_field: # the name of the data to write + variable: v_data + when: '$iter<10' # do only write the first 10 iterations (0...9) + variable_selection: # only write into a single slice in time + subsize: [1, '$dsize[0]', '$dsize[1]'] + start: [$iter, '$dsize[0]*$pcoord[0]', '$dsize[1]*$pcoord[1]'] diff --git a/pdi/docs/CMakeLists.txt b/pdi/docs/CMakeLists.txt index 739a7b581..d29542ada 100644 --- a/pdi/docs/CMakeLists.txt +++ b/pdi/docs/CMakeLists.txt @@ -52,6 +52,7 @@ set(DOXYGEN_INPUT "${PDI_SOURCE_DIR}/../plugins/decl_hdf5/README.md" "${PDI_SOURCE_DIR}/../plugins/decl_netcdf/README.md" "${PDI_SOURCE_DIR}/../plugins/mpi/README.md" + "${PDI_SOURCE_DIR}/../plugins/timer/README.md" "${PDI_SOURCE_DIR}/../plugins/trace/README.md" "${PDI_SOURCE_DIR}/../plugins/user_code/README.md" "${PDI_SOURCE_DIR}/../plugins/pycall/README.md" diff --git a/pdi/docs/Plugins.md b/pdi/docs/Plugins.md index 4d3fa3268..bcc7f662d 100644 --- a/pdi/docs/Plugins.md +++ b/pdi/docs/Plugins.md @@ -9,6 +9,7 @@ |\subpage JSON_plugin "JSON plugin" |Export data in JSON format. | |\subpage serialize_plugin "Serialize plugin" |Serializes and deserializes shared data. | |\subpage set_value_plugin "Set Value plugin" |Set values to data and metadata from yaml file. | +|\subpage timer_plugin "Timer plugin" |Measure the time spent by PDI and its plugins. | |\subpage trace_plugin "Trace plugin" |Generate a trace of what happens in %PDI data store. | |\subpage user_code_plugin "user-code plugin" |Call your function on event or when data becomes available. | diff --git a/pdi/src/pdi.cxx b/pdi/src/pdi.cxx index f7609752d..de41b7005 100644 --- a/pdi/src/pdi.cxx +++ b/pdi/src/pdi.cxx @@ -191,6 +191,7 @@ try { g_transaction.clear(); g_transaction_data.clear(); Global_context::init(conf); + Global_context::context().event("pdi_start_timer"); return PDI_OK; } catch (const Error& e) { return g_error_context.return_err(e); @@ -205,6 +206,7 @@ try { Paraconf_wrapper fw; g_transaction.clear(); g_transaction_data.clear(); + Global_context::context().event("pdi_stop_timer"); Global_context::finalize(); return PDI_OK; } catch (const Error& e) { diff --git a/plugins/decl_hdf5/file_op.cxx b/plugins/decl_hdf5/file_op.cxx index 1d0b61586..982934f3a 100644 --- a/plugins/decl_hdf5/file_op.cxx +++ b/plugins/decl_hdf5/file_op.cxx @@ -261,6 +261,8 @@ File_op::File_op(Expression&& file, Collision_policy collision_policy) void File_op::execute(Context& ctx) { + ctx.event("decl_hdf5_start_timer"); + // first gather the ops we actually want to do vector dset_reads; vector dset_writes; @@ -434,6 +436,7 @@ void File_op::execute(Context& ctx) ctx.logger().trace("Getting size of `{}' dataset finished", dataset_name); } + ctx.event("decl_hdf5_stop_timer"); ctx.logger().trace("All operations done in `{}'. Closing the file.", filename); } diff --git a/plugins/decl_netcdf/dnc_file_context.cxx b/plugins/decl_netcdf/dnc_file_context.cxx index fb8efb0b8..3a3068b23 100644 --- a/plugins/decl_netcdf/dnc_file_context.cxx +++ b/plugins/decl_netcdf/dnc_file_context.cxx @@ -212,6 +212,7 @@ Dnc_variable* Dnc_file_context::variable(const std::string& desc_name, const std void Dnc_file_context::execute(const std::string& desc_name, PDI::Ref ref) { + m_ctx.event("decl_netcdf_start_timer"); if (m_when.to_long(m_ctx)) { std::list variables_holder; // memory for Variables created from descriptor @@ -276,10 +277,12 @@ void Dnc_file_context::execute(const std::string& desc_name, PDI::Ref ref) nc_file.get_sizeof_variable(size_it->first, dataset_name, ref); } } + m_ctx.event("decl_netcdf_stop_timer"); } void Dnc_file_context::execute() { + m_ctx.event("decl_netcdf_start_timer"); if (m_when.to_long(m_ctx)) { std::list variables_holder; std::vector variables_to_get; @@ -354,6 +357,7 @@ void Dnc_file_context::execute() i++; } } + m_ctx.event("decl_netcdf_stop_timer"); } } // namespace decl_netcdf diff --git a/plugins/decl_netcdf/dnc_file_context.h b/plugins/decl_netcdf/dnc_file_context.h index 60cbb9ac1..f57aee4fd 100644 --- a/plugins/decl_netcdf/dnc_file_context.h +++ b/plugins/decl_netcdf/dnc_file_context.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2024-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2020 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -109,6 +109,8 @@ class Dnc_file_context * \param other Dnc_file_context to move */ Dnc_file_context(Dnc_file_context&& other) noexcept; + + static std::string pretty_name() { return "Decl'NetCDF"; } }; } // namespace decl_netcdf diff --git a/plugins/json/json.cxx b/plugins/json/json.cxx index e4c9ab9f9..308f718b9 100644 --- a/plugins/json/json.cxx +++ b/plugins/json/json.cxx @@ -69,6 +69,7 @@ class json_plugin: public PDI::Plugin ~json_plugin() { context().logger().info("Closing plugin"); } + static std::string pretty_name() { return "JSON"; } private: /** Read the configuration file @@ -328,6 +329,7 @@ class json_plugin: public PDI::Plugin */ void write_data(const std::string& data_name, Ref_r&& reference) { + context().event("json_start_timer"); Logger& logger = context().logger(); for (const auto& [condition, fpath]: m_data_to_path_map[data_name]) { @@ -374,6 +376,7 @@ class json_plugin: public PDI::Plugin } logger.debug("Done ! {} ", data_name); } + context().event("json_stop_timer"); } }; diff --git a/plugins/pycall/pycall.cxx b/plugins/pycall/pycall.cxx index 84a91eb7e..a87abbb58 100644 --- a/plugins/pycall/pycall.cxx +++ b/plugins/pycall/pycall.cxx @@ -153,7 +153,9 @@ class Trigger alias.expose(ctx, pyscope); } try { + ctx.event("pycall_start_timer"); pybind11::exec(m_code, pyscope); + ctx.event("pycall_stop_timer"); } catch (const std::exception& e) { ctx.logger().error("while calling python, caught exception: {}", e.what()); } catch (...) { diff --git a/plugins/serialize/serialize.cxx b/plugins/serialize/serialize.cxx index 7cfd575fb..1e3cab347 100644 --- a/plugins/serialize/serialize.cxx +++ b/plugins/serialize/serialize.cxx @@ -272,6 +272,7 @@ struct serialize_plugin: PDI::Plugin { */ void share_serialized(const std::string& desc_name, PDI::Ref ref) { + context().event("serialize_start_timer"); std::string serialized_name = m_desc_to_serialize[desc_name]; context().logger().debug("Serializing `{}` as `{}`", desc_name, serialized_name); PDI::Datatype_sptr serialized_type = serialize_type(ref.type()); @@ -333,6 +334,7 @@ struct serialize_plugin: PDI::Plugin { ); m_serialized_remove_callback.emplace_back(serialized_name, remove_callback, PDI_IN); } + context().event("serialize_stop_timer"); } void release_serialized(const std::string& desc_name, PDI::Ref ref) diff --git a/plugins/timer/CMakeLists.txt b/plugins/timer/CMakeLists.txt new file mode 100644 index 000000000..6b0313150 --- /dev/null +++ b/plugins/timer/CMakeLists.txt @@ -0,0 +1,48 @@ +#============================================================================= +# Copyright (C) 2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of CEA nor the names of its contributors may be used to +# endorse or promote products derived from this software without specific +# prior written permission. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +#============================================================================= + +cmake_minimum_required(VERSION 3.22...4.2) +project(pdi_timer_plugin LANGUAGES C CXX) + +# Includes +include(CTest) +include(GNUInstallDirs) + +# PDI +find_package(PDI REQUIRED COMPONENTS plugins) + +# The plugin +add_library(pdi_timer_plugin MODULE timer.cxx) +target_link_libraries(pdi_timer_plugin PUBLIC PDI::PDI_plugins) + +# Installation +set(INSTALL_PDIPLUGINDIR "${PDI_DEFAULT_PLUGINDIR}" CACHE PATH "PDI plugins (${PDI_DEFAULT_PLUGINDIR})") +install(TARGETS pdi_timer_plugin + LIBRARY DESTINATION "${INSTALL_PDIPLUGINDIR}" +) + +# Tests +if("${BUILD_TESTING}") + add_subdirectory(tests/) +endif() diff --git a/plugins/timer/README.md b/plugins/timer/README.md new file mode 100644 index 000000000..3d33f61c1 --- /dev/null +++ b/plugins/timer/README.md @@ -0,0 +1,30 @@ +# Timer plugin {#timer_plugin} + +The timer plugin allows measuring time spent by PDI and its plugins. + +## Configuration {#timer_configuration} + +Simple plugin build: +```yaml +plugins: + timer: + - timer_A: {start: "decl_hdf5_start_timer", stop: "decl_hdf5_stop_timer"} + - timer_B: "decl_hdf5" + - timer_C: ["toto", "titi"] + - timer_D: + start: "begin_timing" + stop: "end_timing" + - timer_E: ["pdi"] +``` + +The timer plugin configuration contains a list of timer names (e.g. `- timer_A`, `- timer_B`, etc). Each timer will record the time spent between the `start` and `stop` events. + +`timer_A` uses a map-styled definition where both keys `start` and `stop` are mandatory. + +`timer_B` uses a scalar-styled definition where only the prefix of the timer events is provided. The prefix can be either a user-defined name, or the name of the PDI plugins, such as `decl_hdf5`, `decl_netcdf`, `pycall`, etc. If the prefix is the PDI plugin, internal events named with `prefix_start_timer` and `prefix_stop_timer` will be emitted inside the plugin (except `set_value`). In the above example, `timer_B` is equivalent to `timer_A`. + +`timer_C` uses a list-styled definition where a list of prefix is provided. If the name is not one of the PDI plugins' name, then it is the user's responsibility to emit the `prefix_start_timer` and `prefix_stop_timer` events. In this example, `timer_C` will record the time spent between `toto_start_timer`, `toto_stop_timer`, and between `titi_start_timer`, `titi_stop_timer`. + +It is also possible to have different names for start and stop events such as `timer_D`. + +To measure the time used by PDI between `PDI_init` and `PDI_finalize`, one can define a timer as shown by `timer_E` with timer prefix "pdi". diff --git a/plugins/timer/cmake/runtest-dir b/plugins/timer/cmake/runtest-dir new file mode 100755 index 000000000..e617dbc92 --- /dev/null +++ b/plugins/timer/cmake/runtest-dir @@ -0,0 +1,18 @@ +#!/bin/bash + +WKDIR="$(mktemp -p "${PWD}" -d run-XXXXXXXXXX)" +function finish { + rm -rf "${WKDIR}" +} +trap finish EXIT + +while [ '--runtest-dir-copy-file' = "$1" ] +do + shift + cp "$1" "${WKDIR}/" + shift +done + +cd "${WKDIR}" + +"$@" diff --git a/plugins/timer/tests/CMakeLists.txt b/plugins/timer/tests/CMakeLists.txt new file mode 100644 index 000000000..eda075d3f --- /dev/null +++ b/plugins/timer/tests/CMakeLists.txt @@ -0,0 +1,55 @@ +#============================================================================= +# Copyright (C) 2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of CEA nor the names of its contributors may be used to +# endorse or promote products derived from this software without specific +# prior written permission. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLEq FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +#============================================================================= + +cmake_minimum_required(VERSION 3.22...4.2) + +if(NOT TARGET GTest::gtest) + option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" OFF) + add_subdirectory("../../../vendor/googletest-56efe39/" "googletest" EXCLUDE_FROM_ALL) +endif() +include(GoogleTest) + +set(RUNTEST_DIR "${CMAKE_CURRENT_LIST_DIR}/../cmake/runtest-dir") + +# Add the plugin path to PDI_PLUGIN_PATH +set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY TEST_INCLUDE_FILE "${CMAKE_CURRENT_BINARY_DIR}/TestPath.cmake") +file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TestPath.cmake" + CONTENT " +set(PDI_PLUGIN_PATH \"\$ENV{PDI_PLUGIN_PATH}\")\n +if(\"x\${PDI_PLUGIN_PATH}x\" STREQUAL xx)\n +set(ENV{PDI_PLUGIN_PATH} \"\$\")\n +else()\n +set(ENV{PDI_PLUGIN_PATH} \"\$:\${PDI_PLUGIN_PATH}\")\n +endif() +" +) +find_package(MPI REQUIRED) + +add_executable(timer_test timer_test.cxx ) +target_link_libraries(timer_test PDI::PDI_C MPI::MPI_CXX) +target_compile_features(timer_test PRIVATE cxx_std_17) + +add_test(NAME timer_test COMMAND "${RUNTEST_DIR}" "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" 1 ${MPIEXEC_PREFLAGS} "$" ${MPIEXEC_POSTFLAGS}) +set_property(TEST timer_test PROPERTY TIMEOUT 15) +set_property(TEST timer_test PROPERTY PROCESSORS 1) \ No newline at end of file diff --git a/plugins/timer/tests/timer_test.cxx b/plugins/timer/tests/timer_test.cxx new file mode 100644 index 000000000..db4063e38 --- /dev/null +++ b/plugins/timer/tests/timer_test.cxx @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (C) 2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * forcumentation and/or other materials provided with the distribution. + * * Neither the name of CEA nor the names of its contributors may be used to + * enforrse or promote products derived from this software without specific + * prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include +#include + +#define IMX 50 +#define JMX 40 +#define NI_GHOST 1 +#define NJ_GHOST 2 +#define DIM 2 + +constexpr char CONFIG_YAML[] = R"( +logging: debug +metadata: + input: int + ni: int + nj: int + nig: int + njg: int + nit: int + njt: int + istart: int + jstart: int +data: + reals: + type: array + subtype: double + size: [$nj + 2*$njg, $ni + 2*$nig] + subsize: [$nj, $ni] + start: [$njg, $nig] + values: + type: array + subtype: int + size: [$nj + 2*$njg, $ni + 2*$nig] + subsize: [$nj, $ni] + start: [$njg, $nig] +plugins: + timer: + - timer_pdi: "pdi" +)"; + +int main(int argc, char* argv[]) +{ + const int icst = -1; /// constants values in the ghost nodes + const double rcst = -1.01; + + int nig = NI_GHOST, njg = NJ_GHOST; + int ni = IMX, nj = JMX; + int values[JMX + 2 * NJ_GHOST][IMX + NI_GHOST * 2] = {{0}}, cp_values[JMX + 2 * NJ_GHOST][IMX + NI_GHOST * 2] = {{0}}; + double reals[JMX + 2 * NJ_GHOST][IMX + NI_GHOST * 2] = {{0}}, cp_reals[JMX + 2 * NJ_GHOST][IMX + NI_GHOST * 2] = {{0}}; + int i, j, input; + int nit, njt; + + /// MPI and parallel data or info + int dims[DIM], coord[DIM], periodic[DIM]; + int istart, jstart; + MPI_Comm comm2D; + periodic[0] = 0; + periodic[1] = 0; + dims[0] = 1; + dims[1] = 1; + + int provided; + MPI_Init(&argc, &argv); + + PC_tree_t conf = PC_parse_string(CONFIG_YAML); + MPI_Comm world = MPI_COMM_WORLD; + + PDI_init(conf); + + int rank; + MPI_Comm_rank(world, &rank); + int size; + MPI_Comm_size(world, &size); + + MPI_Cart_create(world, DIM, dims, periodic, 0, &comm2D); + MPI_Cart_coords(comm2D, rank, DIM, coord); + + istart = coord[1] * ni; + jstart = coord[0] * nj; + + nit = 2 * ni; + njt = 2 * nj; + + PDI_expose("nig", &nig, PDI_OUT); /// Ghost cells + PDI_expose("njg", &njg, PDI_OUT); + + PDI_expose("ni", &ni, PDI_OUT); /// Size of the portion of the array for a given MPI task + PDI_expose("nj", &nj, PDI_OUT); + + PDI_expose("nit", &nit, PDI_OUT); /// size of the distributed array + PDI_expose("njt", &njt, PDI_OUT); + + PDI_expose("istart", &istart, PDI_OUT); /// offset + PDI_expose("jstart", &jstart, PDI_OUT); + + // Fill arrays + for (j = 0; j < nj + 2 * njg; ++j) { + for (i = 0; i < ni + 2 * nig; ++i) { + cp_values[j][i] = icst; + cp_reals[j][i] = rcst; /// array initialized with const values + } + } + /// Values and reals == 0 in the ghost. + double cst = -rcst; + for (j = njg; j < nj + njg; ++j) { + for (i = nig; i < ni + nig; ++i) { + values[j][i] = (i + coord[1] * ni - nig) + (j + coord[0] * nj - njg) * 10; + reals[j][i] = (i + coord[1] * ni - nig) * cst + (j + coord[0] * nj - njg) * 10 * cst; + } + } + + PDI_expose("rank", &rank, PDI_OUT); + PDI_expose("input", &input, PDI_OUT); + + /// Test that export/exchange works + PDI_multi_expose("", "reals", &reals, PDI_OUT, "values", &values, PDI_INOUT, NULL); + + PDI_finalize(); + PC_tree_destroy(&conf); + MPI_Finalize(); +} diff --git a/plugins/timer/timer.cxx b/plugins/timer/timer.cxx new file mode 100644 index 000000000..02bc4597b --- /dev/null +++ b/plugins/timer/timer.cxx @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (C) 2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of CEA nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific + * prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include +#include + +#include +#include +#include + +namespace { + +using namespace PDI; + +/** The timer plugin +*/ +class timer_plugin: public PDI::Plugin +{ + // Map of timer's name and timer's starting point + std::unordered_map start_times; + + // Map of timer's name and timer's duration + std::unordered_map accumulated_times; + + // Map of start event, and different timers to be started + std::unordered_map > start_events; + + // Map of start event, and different timers to be stopped + std::unordered_map > stop_events; + +public: + timer_plugin(Context& ctx, PC_tree_t spec_tree) + : Plugin{ctx} + { + read_config_tree(ctx, spec_tree); + + ctx.callbacks().add_event_callback([this](const std::string& name) { + if (start_events.find(name) != start_events.end()) { + for (const auto& event_name: start_events[name]) { + startTimer(event_name); + } + } + + if (stop_events.find(name) != stop_events.end()) { + for (const auto& event_name: stop_events[name]) { + stopTimer(event_name); + } + } + }); + + ctx.logger().info("Plugin loaded successfully"); + } + + ~timer_plugin() + { + for (const auto& [name, duration]: accumulated_times) { + context().logger().info("Total time spent for {} : {} seconds", name, duration); + } + context().logger().info("Closing plugin"); + } + + static std::string pretty_name() { return "Timer"; } + +private: + /** Read the configuration file + * + * \param logger PDI's logger instance + * \param spec_tree the yaml tree + */ + void read_config_tree(Context& ctx, PC_tree_t spec_tree) + { + if (PC_status(spec_tree)) { + ctx.logger().error("Error in read_config_tree"); + return; + } + + for (int i = 0; i < len(spec_tree, 0); i++) { + PC_tree_t timer_item = PC_get(spec_tree, "[%d]", i); + std::string timer_name = to_string(PC_get(timer_item, "{0}")); + + PC_tree_t val = PC_get(timer_item, ".%s", timer_name.c_str()); + if (is_map(val)) { + ctx.logger().debug("Defined timer (map-styled): {}", timer_name); + auto start_ev = to_string(PC_get(val, ".start")); + auto stop_ev = to_string(PC_get(val, ".stop")); + register_timer(start_ev, stop_ev, timer_name); + } else { + ctx.logger().debug("Defined timer (scalar/list-styled): {}", timer_name); + opt_each(val, [&](PC_tree_t sub_elem) { + auto start_ev = to_string(sub_elem) + "_start_timer"; + auto stop_ev = to_string(sub_elem) + "_stop_timer"; + register_timer(start_ev, stop_ev, timer_name); + }); + } + } + print_timer_property(); + } + + void register_timer(std::string& start_event, std::string& stop_event, std::string& timer_name) + { + start_events[start_event].push_back(timer_name); + stop_events[stop_event].push_back(timer_name); + } + + void print_timer_property() + { + context().logger().debug("All registered timers: "); + for (const auto& [key, value]: start_events) { + context().logger().debug("event [{}] starts timer ", key); + for (auto n: value) { + context().logger().debug(" \t\t {} ", n); + } + } + for (const auto& [key, value]: stop_events) { + context().logger().debug("event [{}] stops timer ", key); + for (auto n: value) { + context().logger().debug(" \t\t {} ", n); + } + } + } + + void startTimer(const std::string& name) + { + if (start_times.find(name) != start_times.end()) { + context().logger().error("Timer for {} is already running. Ignoring the start", name); + return; + } + start_times[name] = std::chrono::high_resolution_clock::now(); + } + + // Stop a timer and accumulate the duration + void stopTimer(const std::string& name) + { + auto it = start_times.find(name); + if (it == start_times.end()) { + context().logger().error("Cannot end timer for {} because it was never started.", name); + return; + } + + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end_time - start_times[name]; + + accumulated_times[name] += elapsed.count(); + start_times.erase(it); + } +}; + +} // namespace +PDI_PLUGIN(timer) diff --git a/plugins/user_code/user_code.cxx b/plugins/user_code/user_code.cxx index 7b25626bb..bbd19096a 100644 --- a/plugins/user_code/user_code.cxx +++ b/plugins/user_code/user_code.cxx @@ -161,6 +161,7 @@ class Trigger /// call the function that has been registered void call(Context& ctx) { + ctx.event("user_code_start_timer"); // all exposed aliases that will be unexposed on destroy vector exposed_aliases; for (auto&& alias: m_aliases) { @@ -174,6 +175,7 @@ class Trigger } catch (...) { ctx.logger().error("While calling user code, caught exception"); } + ctx.event("user_code_stop_timer"); } }; // class Trigger