LCOV - code coverage report
Current view: top level - src/utils - PerfGraphLivePrint.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: fef103 Lines: 152 161 94.4 %
Date: 2025-09-03 20:01:23 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          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 = &section_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 != &section_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 == &section_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 = &section_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 != &section_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 = &section_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             : }

Generated by: LCOV version 1.14