Line | Branch | Exec | Source |
---|---|---|---|
1 | #pragma once | ||
2 | |||
3 | #include <omp.h> | ||
4 | |||
5 | #include <array> | ||
6 | #include <chrono> | ||
7 | #include <cstdint> | ||
8 | #include <cstdlib> | ||
9 | #include <fstream> | ||
10 | #include <iomanip> | ||
11 | #include <iostream> | ||
12 | #include <memory> | ||
13 | #include <sstream> | ||
14 | #include <stdexcept> | ||
15 | #include <string> | ||
16 | #include <util/include/util.hpp> | ||
17 | #include <utility> | ||
18 | |||
19 | namespace ppc::task { | ||
20 | |||
21 | /// @brief Represents the type of task (parallelization technology). | ||
22 | /// @details Used to select the implementation type in tests and execution logic. | ||
23 | enum TypeOfTask : uint8_t { | ||
24 | /// Use all available implementations | ||
25 | kALL, | ||
26 | /// MPI (Message Passing Interface) | ||
27 | kMPI, | ||
28 | /// OpenMP (Open Multi-Processing) | ||
29 | kOMP, | ||
30 | /// Sequential implementation | ||
31 | kSEQ, | ||
32 | /// Standard Thread Library (STL threads) | ||
33 | kSTL, | ||
34 | /// Intel Threading Building Blocks (TBB) | ||
35 | kTBB, | ||
36 | /// Unknown task type | ||
37 | kUnknown | ||
38 | }; | ||
39 | |||
40 | using TaskMapping = std::pair<TypeOfTask, std::string>; | ||
41 | using TaskMappingArray = std::array<TaskMapping, 6>; | ||
42 | |||
43 | const TaskMappingArray kTaskTypeMappings = {{{TypeOfTask::kALL, "all"}, | ||
44 | {TypeOfTask::kMPI, "mpi"}, | ||
45 | {TypeOfTask::kOMP, "omp"}, | ||
46 | {TypeOfTask::kSEQ, "seq"}, | ||
47 | {TypeOfTask::kSTL, "stl"}, | ||
48 | {TypeOfTask::kTBB, "tbb"}}}; | ||
49 | |||
50 | 768 | inline std::string TypeOfTaskToString(TypeOfTask type) { | |
51 |
2/2✓ Branch 0 taken 2850 times.
✓ Branch 1 taken 40 times.
|
2890 | for (const auto &[key, value] : kTaskTypeMappings) { |
52 |
2/2✓ Branch 0 taken 728 times.
✓ Branch 1 taken 2122 times.
|
2850 | if (key == type) { |
53 | return value; | ||
54 | } | ||
55 | } | ||
56 | 40 | return "unknown"; | |
57 | } | ||
58 | |||
59 | /// @brief Indicates whether a task is enabled or disabled. | ||
60 | enum StatusOfTask : uint8_t { | ||
61 | /// Task is enabled and should be executed | ||
62 | kEnabled, | ||
63 | /// Task is disabled and will be skipped | ||
64 | kDisabled | ||
65 | }; | ||
66 | |||
67 | /// @brief Returns a string representation of the task status. | ||
68 | /// @param status_of_task Task status (enabled or disabled). | ||
69 | /// @return "enabled" if the task is enabled, otherwise "disabled". | ||
70 | inline std::string GetStringTaskStatus(StatusOfTask status_of_task) { | ||
71 | if (status_of_task == kDisabled) { | ||
72 |
1/2✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | return "disabled"; |
73 | } | ||
74 |
1/2✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | return "enabled"; |
75 | } | ||
76 | |||
77 | /// @brief Returns a string representation of the task type based on the JSON settings file. | ||
78 | /// @param type_of_task Type of the task. | ||
79 | /// @param settings_file_path Path to the JSON file containing task type strings. | ||
80 | /// @return Formatted string combining the task type and its corresponding value from the file. | ||
81 | /// @throws std::runtime_error If the file cannot be opened. | ||
82 | 828 | inline std::string GetStringTaskType(TypeOfTask type_of_task, const std::string &settings_file_path) { | |
83 | 828 | std::ifstream file(settings_file_path); | |
84 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 788 times.
|
828 | if (!file.is_open()) { |
85 |
2/4✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 40 times.
✗ Branch 6 not taken.
|
80 | throw std::runtime_error("Failed to open " + settings_file_path); |
86 | } | ||
87 | |||
88 | auto list_settings = ppc::util::InitJSONPtr(); | ||
89 |
2/2✓ Branch 1 taken 768 times.
✓ Branch 2 taken 20 times.
|
788 | file >> *list_settings; |
90 | |||
91 |
1/2✓ Branch 1 taken 768 times.
✗ Branch 2 not taken.
|
768 | std::string type_str = TypeOfTaskToString(type_of_task); |
92 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 728 times.
|
768 | if (type_str == "unknown") { |
93 | 40 | return type_str; | |
94 | } | ||
95 | |||
96 |
4/8✓ Branch 1 taken 728 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 728 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 708 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 708 times.
✗ Branch 11 not taken.
|
3600 | return type_str + "_" + std::string((*list_settings)["tasks"][type_str]); |
97 | 828 | } | |
98 | |||
99 | enum StateOfTesting : uint8_t { kFunc, kPerf }; | ||
100 | |||
101 | template <typename InType, typename OutType> | ||
102 | /// @brief Base abstract class representing a generic task with a defined pipeline. | ||
103 | /// @tparam InType Input data type. | ||
104 | /// @tparam OutType Output data type. | ||
105 |
6/12✓ Branch 1 taken 55 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 60 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 10 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 10 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 10 times.
✗ Branch 17 not taken.
|
511 | class Task { |
106 | public: | ||
107 | /// @brief Validates input data and task attributes before execution. | ||
108 | /// @return True if validation is successful. | ||
109 | 1006 | virtual bool Validation() final { | |
110 |
2/2✓ Branch 0 taken 661 times.
✓ Branch 1 taken 10 times.
|
1006 | if (stage_ == PipelineStage::kNone || stage_ == PipelineStage::kDone) { |
111 | 986 | stage_ = PipelineStage::kValidation; | |
112 | } else { | ||
113 | 20 | stage_ = PipelineStage::kException; | |
114 |
1/2✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
20 | throw std::runtime_error("Validation should be called before preprocessing"); |
115 | } | ||
116 | 986 | return ValidationImpl(); | |
117 | } | ||
118 | |||
119 | /// @brief Performs preprocessing on the input data. | ||
120 | /// @return True if preprocessing is successful. | ||
121 | 966 | virtual bool PreProcessing() final { | |
122 |
2/2✓ Branch 0 taken 631 times.
✓ Branch 1 taken 20 times.
|
966 | if (stage_ == PipelineStage::kValidation) { |
123 | 926 | stage_ = PipelineStage::kPreProcessing; | |
124 | } else { | ||
125 | 40 | stage_ = PipelineStage::kException; | |
126 |
1/2✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
40 | throw std::runtime_error("Preprocessing should be called after validation"); |
127 | } | ||
128 |
2/2✓ Branch 0 taken 431 times.
✓ Branch 1 taken 200 times.
|
926 | if (state_of_testing_ == StateOfTesting::kFunc) { |
129 | 526 | InternalTimeTest(); | |
130 | } | ||
131 | 926 | return PreProcessingImpl(); | |
132 | } | ||
133 | |||
134 | /// @brief Executes the main logic of the task. | ||
135 | /// @return True if execution is successful. | ||
136 | 1116 | virtual bool Run() final { | |
137 |
2/2✓ Branch 0 taken 706 times.
✓ Branch 1 taken 20 times.
|
1116 | if (stage_ == PipelineStage::kPreProcessing || stage_ == PipelineStage::kRun) { |
138 | 1076 | stage_ = PipelineStage::kRun; | |
139 | } else { | ||
140 | 40 | stage_ = PipelineStage::kException; | |
141 |
1/2✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
40 | throw std::runtime_error("Run should be called after preprocessing"); |
142 | } | ||
143 | 1076 | return RunImpl(); | |
144 | } | ||
145 | |||
146 | /// @brief Performs postprocessing on the output data. | ||
147 | /// @return True if postprocessing is successful. | ||
148 | 966 | virtual bool PostProcessing() final { | |
149 |
2/2✓ Branch 0 taken 616 times.
✓ Branch 1 taken 35 times.
|
966 | if (stage_ == PipelineStage::kRun) { |
150 | 896 | stage_ = PipelineStage::kDone; | |
151 | } else { | ||
152 | 70 | stage_ = PipelineStage::kException; | |
153 |
1/2✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
|
70 | throw std::runtime_error("Postprocessing should be called after run"); |
154 | } | ||
155 |
2/2✓ Branch 0 taken 416 times.
✓ Branch 1 taken 200 times.
|
896 | if (state_of_testing_ == StateOfTesting::kFunc) { |
156 | 496 | InternalTimeTest(); | |
157 | } | ||
158 | 856 | return PostProcessingImpl(); | |
159 | } | ||
160 | |||
161 | /// @brief Returns the current testing mode. | ||
162 | /// @return Reference to the current StateOfTesting. | ||
163 | StateOfTesting &GetStateOfTesting() { | ||
164 | return state_of_testing_; | ||
165 | } | ||
166 | |||
167 | /// @brief Sets the dynamic task type. | ||
168 | /// @param type_of_task Task type to set. | ||
169 | void SetTypeOfTask(TypeOfTask type_of_task) { | ||
170 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
346 | type_of_task_ = type_of_task; |
171 | } | ||
172 | |||
173 | /// @brief Returns the dynamic task type. | ||
174 | /// @return Current dynamic task type. | ||
175 | [[nodiscard]] TypeOfTask GetDynamicTypeOfTask() const { | ||
176 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | return type_of_task_; |
177 | } | ||
178 | |||
179 | /// @brief Returns the current task status. | ||
180 | /// @return Task status (enabled or disabled). | ||
181 | [[nodiscard]] StatusOfTask GetStatusOfTask() const { | ||
182 | return status_of_task_; | ||
183 | } | ||
184 | |||
185 | /// @brief Returns the static task type. | ||
186 | /// @return Static task type (default: kUnknown). | ||
187 | static constexpr TypeOfTask GetStaticTypeOfTask() { | ||
188 | return TypeOfTask::kUnknown; | ||
189 | } | ||
190 | |||
191 | /// @brief Returns a reference to the input data. | ||
192 | /// @return Reference to the task's input data. | ||
193 | InType &GetInput() { | ||
194 |
6/12✓ Branch 1 taken 55 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 60 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 10 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 10 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 10 times.
✗ Branch 17 not taken.
|
175 | return input_; |
195 | } | ||
196 | |||
197 | /// @brief Returns a reference to the output data. | ||
198 | /// @return Reference to the task's output data. | ||
199 | OutType &GetOutput() { | ||
200 |
3/6✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 10 times.
✗ Branch 8 not taken.
|
366 | return output_; |
201 | } | ||
202 | |||
203 | /// @brief Destructor. Verifies that the pipeline was executed in the correct order. | ||
204 | /// @note Terminates the program if the pipeline order is incorrect or incomplete. | ||
205 | 350 | virtual ~Task() { | |
206 | 380 | if (stage_ != PipelineStage::kDone && stage_ != PipelineStage::kException) { | |
207 | ppc::util::DestructorFailureFlag::Set(); | ||
208 | } | ||
209 | #if _OPENMP >= 201811 | ||
210 | omp_pause_resource_all(omp_pause_soft); | ||
211 | #endif | ||
212 |
4/12✓ Branch 0 taken 30 times.
✓ Branch 1 taken 155 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 10 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
380 | } |
213 | |||
214 | protected: | ||
215 | /// @brief Measures execution time between preprocessing and postprocessing steps. | ||
216 | /// @throws std::runtime_error If execution exceeds the allowed time limit. | ||
217 | 1022 | virtual void InternalTimeTest() final { | |
218 |
2/2✓ Branch 0 taken 431 times.
✓ Branch 1 taken 416 times.
|
1022 | if (stage_ == PipelineStage::kPreProcessing) { |
219 | 526 | tmp_time_point_ = std::chrono::high_resolution_clock::now(); | |
220 | } | ||
221 | |||
222 |
2/2✓ Branch 0 taken 416 times.
✓ Branch 1 taken 431 times.
|
1022 | if (stage_ == PipelineStage::kDone) { |
223 | 496 | auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - | |
224 | tmp_time_point_) | ||
225 | .count(); | ||
226 | 496 | auto diff = static_cast<double>(duration) * 1e-9; | |
227 | |||
228 | 496 | const auto max_time = ppc::util::GetTaskMaxTime(); | |
229 | 496 | std::stringstream err_msg; | |
230 |
2/2✓ Branch 0 taken 396 times.
✓ Branch 1 taken 20 times.
|
496 | if (diff < max_time) { |
231 |
1/2✓ Branch 1 taken 396 times.
✗ Branch 2 not taken.
|
456 | err_msg << "Test time:" << std::fixed << std::setprecision(10) << diff << '\n'; |
232 | } else { | ||
233 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
40 | err_msg << "\nTask execute time need to be: "; |
234 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
40 | err_msg << "time < " << max_time << " secs.\n"; |
235 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
40 | err_msg << "Original time in secs: " << diff << '\n'; |
236 |
1/2✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
120 | throw std::runtime_error(err_msg.str().c_str()); |
237 | } | ||
238 | 496 | } | |
239 | 982 | } | |
240 | |||
241 | /// @brief User-defined validation logic. | ||
242 | /// @return True if validation is successful. | ||
243 | virtual bool ValidationImpl() = 0; | ||
244 | |||
245 | /// @brief User-defined preprocessing logic. | ||
246 | /// @return True if preprocessing is successful. | ||
247 | virtual bool PreProcessingImpl() = 0; | ||
248 | |||
249 | /// @brief User-defined task execution logic. | ||
250 | /// @return True if a run is successful. | ||
251 | virtual bool RunImpl() = 0; | ||
252 | |||
253 | /// @brief User-defined postprocessing logic. | ||
254 | /// @return True if postprocessing is successful. | ||
255 | virtual bool PostProcessingImpl() = 0; | ||
256 | |||
257 | private: | ||
258 | InType input_{}; | ||
259 | OutType output_{}; | ||
260 | StateOfTesting state_of_testing_ = kFunc; | ||
261 | TypeOfTask type_of_task_ = kUnknown; | ||
262 | StatusOfTask status_of_task_ = kEnabled; | ||
263 | std::chrono::high_resolution_clock::time_point tmp_time_point_; | ||
264 | enum class PipelineStage : uint8_t { | ||
265 | kNone, | ||
266 | kValidation, | ||
267 | kPreProcessing, | ||
268 | kRun, | ||
269 | kDone, | ||
270 | kException | ||
271 | } stage_ = PipelineStage::kNone; | ||
272 | }; | ||
273 | |||
274 | /// @brief Smart pointer alias for Task. | ||
275 | /// @tparam InType Input data type. | ||
276 | /// @tparam OutType Output data type. | ||
277 | template <typename InType, typename OutType> | ||
278 | using TaskPtr = std::shared_ptr<Task<InType, OutType>>; | ||
279 | |||
280 | /// @brief Constructs and returns a shared pointer to a task with the given input. | ||
281 | /// @tparam TaskType Type of the task to create. | ||
282 | /// @tparam InType Type of the input. | ||
283 | /// @param in Input to pass to the task constructor. | ||
284 | /// @return Shared a pointer to the newly created task. | ||
285 | template <typename TaskType, typename InType> | ||
286 | 336 | std::shared_ptr<TaskType> TaskGetter(InType in) { | |
287 | 336 | return std::make_shared<TaskType>(in); | |
288 | } | ||
289 | |||
290 | } // namespace ppc::task | ||
291 |