Line data Source code
1 : //* This file is part of the MOOSE framework 2 : //* https://mooseframework.inl.gov 3 : //* 4 : //* All rights reserved, see COPYRIGHT for full restrictions 5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT 6 : //* 7 : //* Licensed under LGPL 2.1, please see LICENSE for details 8 : //* https://www.gnu.org/licenses/lgpl-2.1.html 9 : 10 : #include "PerfGraphLivePrint.h" 11 : #include "PerfGraphRegistry.h" 12 : #include "MooseApp.h" 13 : 14 68460 : PerfGraphLivePrint::PerfGraphLivePrint(PerfGraph & perf_graph, MooseApp & app) 15 : : ConsoleStreamInterface(app), 16 68460 : _app(app), 17 68460 : _perf_graph(perf_graph), 18 136920 : _perf_graph_registry(moose::internal::getPerfGraphRegistry()), 19 68460 : _execution_list(perf_graph._execution_list), 20 68460 : _currently_destructing(false), 21 68460 : _time_limit(perf_graph._live_print_time_limit), 22 68460 : _mem_limit(perf_graph._live_print_mem_limit), 23 68460 : _stack_level(0), 24 68460 : _current_execution_list_end(0), 25 68460 : _current_execution_list_last(0), 26 68460 : _last_execution_list_end(0), 27 68460 : _last_printed_increment(NULL), 28 68460 : _last_num_printed(0), 29 68460 : _console_num_printed(0), 30 136920 : _stack_top_print_dots(true) 31 : { 32 68460 : } 33 : 34 : void 35 1338 : PerfGraphLivePrint::printLiveMessage(PerfGraph::SectionIncrement & section_increment) 36 : { 37 1338 : auto & section_info = _perf_graph_registry.sectionInfo(section_increment._id); 38 : 39 : // If we're not printing dots - we shouldn't be printing the message at all 40 1338 : if (!section_info._print_dots || !_stack_top_print_dots) 41 : { 42 276 : section_increment._state = PerfGraph::IncrementState::PRINTED; 43 276 : _last_printed_increment = §ion_increment; 44 276 : return; 45 : } 46 : 47 : // If the live_message is empty - just print the name 48 : auto message = 49 1062 : !section_info._live_message.empty() ? section_info._live_message : section_info._name; 50 : 51 : // If this section is just started - but other stuff has printed before we got to print its 52 : // message, we need to print it anyway because it could be lengthy and print unexplained 53 : // dots to the console (until the Finished message). "Currently" conveys the message 54 : // that we did not just start it, but we are doing that right now 55 1062 : if (_last_num_printed <= _console_num_printed && 56 928 : section_increment._state == PerfGraph::IncrementState::STARTED && 57 355 : section_increment._beginning_num_printed != _console_num_printed) 58 : { 59 201 : message = "Currently " + message; 60 : // The 10 is for "Currently " 61 201 : section_increment._num_dots = 10; 62 : } 63 : 64 : // This line is different - need to finish the last line 65 1062 : if (_last_printed_increment && _last_printed_increment != §ion_increment && 66 279 : _last_printed_increment->_state == PerfGraph::IncrementState::PRINTED && 67 156 : section_info._print_dots) 68 156 : _console << '\n'; 69 : 70 : // Do we need to print dots? 71 1062 : if (_last_printed_increment && _last_printed_increment == §ion_increment && 72 559 : section_increment._state == PerfGraph::IncrementState::PRINTED) 73 : { 74 559 : if (section_info._print_dots) 75 : { 76 559 : _console << "."; 77 559 : section_increment._num_dots++; 78 : } 79 : } 80 : // Printed before so print "Still" 81 503 : else if (section_increment._state == PerfGraph::IncrementState::PRINTED) 82 : { 83 28 : _console << std::string(2 * section_increment._print_stack_level, ' ') << "Still " << message; 84 : 85 : // If we're not printing dots - just finish the line 86 14 : if (!section_info._print_dots) 87 0 : _console << '\n'; 88 : 89 : // The 6 is for "Still " 90 14 : section_increment._num_dots = 6; 91 : } 92 : else // Just print the message 93 : { 94 978 : _console << std::string(2 * section_increment._print_stack_level, ' ') << message; 95 : 96 : // If we're not printing dots - just finish the line 97 489 : if (!section_info._print_dots) 98 0 : _console << '\n'; 99 : 100 : // Reset the dots since we're printing, except in the "Currently" case 101 489 : if (section_increment._state != PerfGraph::IncrementState::STARTED || 102 489 : _last_num_printed > _console_num_printed || 103 355 : section_increment._beginning_num_printed == _console_num_printed) 104 288 : section_increment._num_dots = 0; 105 : } 106 : 107 1062 : section_increment._state = PerfGraph::IncrementState::PRINTED; 108 : 109 : // Get the message to the screen 110 1062 : _console << std::flush; 111 : 112 : // Keep track of where we printed in the console 113 1062 : _last_num_printed = section_increment._beginning_num_printed = _console.numPrinted(); 114 : 115 1062 : _last_printed_increment = §ion_increment; 116 1062 : } 117 : 118 : void 119 794 : PerfGraphLivePrint::printStats(PerfGraph::SectionIncrement & section_increment_start, 120 : PerfGraph::SectionIncrement & section_increment_finish) 121 : { 122 794 : if (_stack_level < 1) 123 0 : return; 124 : 125 : mooseAssert(_perf_graph_registry.sectionExists(section_increment_start._id), 126 : "Not found in map: " << section_increment_start._id); 127 : 128 794 : auto & section_info_start = _perf_graph_registry.sectionInfo(section_increment_start._id); 129 : 130 : // If the live_message is empty - just print the name 131 794 : auto message = !section_info_start._live_message.empty() ? section_info_start._live_message 132 794 : : section_info_start._name; 133 : 134 : auto time_increment = 135 794 : std::chrono::duration<double>(section_increment_finish._time - section_increment_start._time) 136 794 : .count(); 137 : 138 794 : auto memory_total = section_increment_finish._memory; 139 : 140 794 : auto num_horizontal_chars = message.size() + (2 * section_increment_start._print_stack_level); 141 : 142 : // Add multiapps prefix size 143 794 : if (!_app.isUltimateMaster()) 144 49 : num_horizontal_chars += _app.name().size() + 2; 145 : 146 : // Do we need to print "Finished"? 147 : // This happens after something else printed in-between when this increment started and finished 148 794 : if (!section_info_start._print_dots || 149 558 : (_last_printed_increment && _last_printed_increment != §ion_increment_start) || 150 306 : (section_increment_start._beginning_num_printed != 151 306 : _console_num_printed)) // This means someone _else_ printed 152 : { 153 : // If we had printed some dots - we need to finish the line 154 1540 : if (_last_printed_increment && 155 669 : _last_printed_increment->_state == PerfGraph::IncrementState::PRINTED && 156 143 : _perf_graph_registry.sectionInfo(_last_printed_increment->_id)._print_dots) 157 115 : _console << '\n'; 158 : 159 1578 : _console << std::string(2 * section_increment_start._print_stack_level, ' ') << "Finished " 160 526 : << message; 161 : 162 : // 9 is for "Finished " 163 526 : num_horizontal_chars += 9; 164 : } 165 : else 166 268 : num_horizontal_chars += section_increment_start._num_dots; 167 : 168 : // Actually do the printing 169 794 : _console << std::setw(WRAP_LENGTH - num_horizontal_chars); 170 : 171 794 : _console << " ["; 172 794 : _console << COLOR_YELLOW; 173 794 : _console << std::setw(6) << std::fixed << std::setprecision(2) << time_increment << " s"; 174 794 : _console << COLOR_DEFAULT; 175 794 : _console << ']' << " ["; 176 794 : _console << COLOR_YELLOW << std::setw(5) << std::fixed; 177 794 : _console << memory_total; 178 794 : _console << " MB"; 179 794 : _console << COLOR_DEFAULT; 180 794 : _console << ']'; 181 : 182 : // If we're not printing dots - just finish the line 183 794 : _console << std::endl; 184 : 185 794 : _last_num_printed = _console.numPrinted(); 186 : 187 794 : _last_printed_increment = §ion_increment_finish; 188 794 : } 189 : 190 : void 191 1747 : PerfGraphLivePrint::printStackUpToLast() 192 : { 193 1747 : if (_stack_level < 1) 194 0 : return; 195 : 196 : // We need to print out everything on the stack before this that hasn't already been printed... 197 4089 : for (unsigned int s = 0; s < _stack_level - 1; s++) 198 : { 199 2342 : auto & section = _print_thread_stack[s]; 200 : 201 : // Hasn't been printed at all and nothing else has been printed since this started 202 2342 : if (section._state == PerfGraph::IncrementState::STARTED) 203 385 : printLiveMessage(section); 204 : 205 2342 : section._state = PerfGraph::IncrementState::PRINTED; 206 : } 207 : } 208 : 209 : void 210 1094 : PerfGraphLivePrint::inSamePlace() 211 : { 212 : // If someone else printed since, then we need to start over, and set everything on the stack to 213 : // printed Everything is set to printed because if something printed and we're still in the same 214 : // place then we need to NOT print out the beginning message 215 1094 : if (_last_num_printed != _console_num_printed) 216 : { 217 0 : _last_printed_increment = nullptr; 218 : 219 0 : for (unsigned int s = 0; s < _stack_level; s++) 220 0 : _print_thread_stack[s]._state = PerfGraph::IncrementState::PRINTED; 221 : 222 0 : return; 223 : } 224 : 225 : // Only print if there is something to print! 226 1094 : if (_stack_level > 0) 227 : { 228 953 : _stack_top_print_dots = 229 953 : _perf_graph_registry.sectionInfo(_print_thread_stack[_stack_level - 1]._id)._print_dots; 230 : 231 953 : printStackUpToLast(); 232 : 233 953 : printLiveMessage(_print_thread_stack[_stack_level - 1]); 234 : 235 : // Reset this each time 236 953 : _stack_top_print_dots = true; 237 : } 238 : } 239 : 240 : void 241 958922 : PerfGraphLivePrint::iterateThroughExecutionList() 242 : { 243 : // Current position in the execution list 244 958922 : auto p = _last_execution_list_end; 245 : 246 2884666 : while (p != _current_execution_list_end) 247 : { 248 : // The ternary here is for wrapping around 249 1925744 : auto next_p = p + 1 < MAX_EXECUTION_LIST_SIZE ? p + 1 : 0; 250 : 251 1925744 : auto & section_increment = _execution_list[p]; 252 : 253 : // New section, add to the stack 254 1925744 : if (section_increment._state == PerfGraph::IncrementState::STARTED) 255 : { 256 962983 : section_increment._print_stack_level = _stack_level; 257 : 258 : // Store this increment in the stack 259 962983 : _print_thread_stack[_stack_level] = section_increment; 260 : 261 962983 : _stack_level++; 262 : } 263 : else // This means it's finished need to see if we need to print it 264 : { 265 : mooseAssert(_stack_level, "Popping beyond the beginning of the stack!"); 266 : 267 : // Get the beginning information for this section... it is the thing currently on the top of 268 : // the stack 269 962761 : auto & section_increment_start = _print_thread_stack[_stack_level - 1]; 270 : 271 : auto time_increment = 272 962761 : std::chrono::duration<double>(section_increment._time - section_increment_start._time) 273 962761 : .count(); 274 962761 : auto memory_increment = section_increment._memory - section_increment_start._memory; 275 : 276 : // If it has already been printed or meets our criteria then print it and finish it 277 2888283 : if (_perf_graph._live_print_all || 278 1924897 : section_increment_start._state == PerfGraph::IncrementState::PRINTED || 279 2887658 : time_increment > _time_limit.load(std::memory_order_relaxed) || 280 1923946 : memory_increment > _mem_limit.load(std::memory_order_relaxed)) 281 : { 282 794 : printStackUpToLast(); 283 : 284 794 : printStats(section_increment_start, section_increment); 285 : } 286 : 287 962761 : _stack_level--; 288 : } 289 : 290 1925744 : p = next_p; 291 : } 292 958922 : } 293 : 294 : void 295 3438 : PerfGraphLivePrint::start() 296 : { 297 : // Keep going until we're signaled to end 298 : // Note that _currently_destructing can only be set to true in this thread 299 : // Which means that by the time we make it back to the top of the loop, either 300 : // there was nothing to process or everything has been processed. 301 962360 : while (!_currently_destructing) 302 : { 303 962270 : std::unique_lock<std::mutex> lock(_perf_graph._destructing_mutex); 304 : 305 : // Wait for five seconds (by default), or until notified that a section is finished 306 : // For a section to have finished the execution list has to have been appended to 307 : // This keeps spurious wakeups from happening 308 : // Note that the `lock` is only protecting _destructing since the execution list uses atomics. 309 : // It must be atomic in order to keep the main thread from having to lock as it 310 : // executes. The only downside to this is that it is possible for this thread to wake, 311 : // check the condition, miss the notification, then wait. In our case this is not detrimental, 312 : // as the only thing that will happen is we will wait 5 more seconds. This is also very 313 : // unlikely. 314 : // One other thing: wait_for() is not guaranteed to wait for 5 seconds. "Spurious" wakeups 315 : // can occur - but the predicate here keeps us from doing anything in that case. 316 : // This will either wait until 5 seconds have passed, the signal is sent, _or_ a spurious 317 : // wakeup happens to find that there is work to do. 318 962270 : _perf_graph._finished_section.wait_for( 319 : lock, 320 962270 : std::chrono::duration<Real>(_time_limit.load(std::memory_order_relaxed)), 321 1914333 : [this] 322 : { 323 : // Get destructing first so that the execution_list will be in sync 324 1914333 : this->_currently_destructing = _perf_graph._destructing; 325 : 326 : // The end will be one past the last 327 : // This "acquire" synchronizes with the "release" in the PerfGraph 328 : // to ensure that all of the writes to the execution list have been 329 : // published to this thread for the "end" we're reading 330 3828666 : this->_current_execution_list_end = 331 1914333 : _perf_graph._execution_list_end.load(std::memory_order_acquire); 332 : 333 : // Save off the number of things currently printed to the console 334 1914333 : this->_console_num_printed = _console.numPrinted(); 335 : 336 : // If we are destructing or there is new work to do... allow moving on 337 3825277 : return this->_currently_destructing || 338 5739610 : this->_last_execution_list_end != this->_current_execution_list_end; 339 : }); 340 : 341 : // If the PerfGraph is destructing and we don't have anything left to print - we need to quit 342 : // Otherwise, if there are still things to print - do it... afterwards, the loop above 343 : // will end because _done_future has been set in PerfGraph. 344 965610 : if (this->_currently_destructing && 345 965610 : this->_last_execution_list_end == this->_current_execution_list_end) 346 3299 : return; 347 : 348 : // The last entry in the current execution list for convenience 349 1917844 : _current_execution_list_last = static_cast<long int>(_current_execution_list_end) - 1 >= 0 350 958922 : ? _current_execution_list_end - 1 351 : : MAX_EXECUTION_LIST_SIZE; 352 : 353 : // Only happens if nothing has been added 354 958922 : if (_current_execution_list_end == 0 && _last_execution_list_end == _current_execution_list_end) 355 0 : continue; 356 : 357 : // Iterate from the last thing printed (begin) to the last thing in the list (end) 358 : // If the time or memory of any section is above the threshold, print everything in between and 359 : // update begin 360 : 361 : // Are we still sitting in the same place as the last iteration? If so, we need to print 362 : // progress and exit 363 958922 : if (_last_execution_list_end == _current_execution_list_end) 364 1094 : inSamePlace(); 365 : 366 : // This means that new stuff has been added to the execution list. We need to iterate through 367 : // it, modifying the stack and printing anything that needs printing 368 958922 : iterateThroughExecutionList(); 369 : 370 958922 : _last_num_printed = _console.numPrinted(); 371 : 372 958922 : _last_execution_list_end = _current_execution_list_end; 373 962221 : } 374 : }