$extrastylesheet
perf_log.C
Go to the documentation of this file.
00001 // The libMesh Finite Element Library.
00002 // Copyright (C) 2002-2014 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
00003 
00004 // This library is free software; you can redistribute it and/or
00005 // modify it under the terms of the GNU Lesser General Public
00006 // License as published by the Free Software Foundation; either
00007 // version 2.1 of the License, or (at your option) any later version.
00008 
00009 // This library is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012 // Lesser General Public License for more details.
00013 
00014 // You should have received a copy of the GNU Lesser General Public
00015 // License along with this library; if not, write to the Free Software
00016 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00017 
00018 
00019 
00020 // C++ includes
00021 #include <iostream>
00022 #include <iomanip>
00023 #include <ctime>
00024 #include <unistd.h>
00025 #include <sys/utsname.h>
00026 #include <sys/types.h>
00027 #include <pwd.h>
00028 #include <vector>
00029 #include <sstream>
00030 
00031 // Local includes
00032 #include "libmesh/perf_log.h"
00033 #include "libmesh/timestamp.h"
00034 
00035 namespace libMesh
00036 {
00037 
00038 
00039 // ------------------------------------------------------------
00040 // PerfLog class member funcions
00041 
00042 bool PerfLog::called = false;
00043 
00044 
00045 PerfLog::PerfLog(const std::string& ln,
00046                  const bool le) :
00047   label_name(ln),
00048   log_events(le),
00049   total_time(0.)
00050 {
00051   gettimeofday (&tstart, NULL);
00052 
00053   if (log_events)
00054     this->clear();
00055 }
00056 
00057 
00058 
00059 PerfLog::~PerfLog()
00060 {
00061   if (log_events)
00062     this->print_log();
00063 }
00064 
00065 
00066 
00067 void PerfLog::clear()
00068 {
00069   if (log_events)
00070     {
00071       //  check that all events are closed
00072       for (std::map<std::pair<std::string,std::string>, PerfData>::iterator
00073              pos = log.begin(); pos != log.end(); ++pos)
00074         if (pos->second.open)
00075           libmesh_error_msg("ERROR clearning performance log for class " \
00076                             << label_name                             \
00077                             << "\nevent "                             \
00078                             << pos->first.second                      \
00079                             << " is still being monitored!");
00080 
00081       gettimeofday (&tstart, NULL);
00082 
00083       log.clear();
00084 
00085       while (!log_stack.empty())
00086         log_stack.pop();
00087     }
00088 }
00089 
00090 
00091 std::string PerfLog::get_info_header() const
00092 {
00093   std::ostringstream oss;
00094 
00095   if (log_events)
00096     {
00097       std::string date = Utility::get_timestamp();
00098 
00099       // Get system information
00100       struct utsname sysInfo;
00101       uname(&sysInfo);
00102 
00103       // Get user information
00104       //
00105       // Some systems, for example Crays, actually have getpwuid on the head-node
00106       // but (if I understand correctly) a dynamically-linked glibc is not available
00107       // on the backend, which is running a reduced operating system like Compute
00108       // Node Linux.  Thus functions like getpwuid cannot be called.  This makes
00109       // automatically testing for the existence of getpwuid on the login node
00110       // difficult.  The configure test would work on the login node but will fail
00111       // on the backend.  Hence we have added a configure flag, --disable-getpwuid,
00112       // to manually turn this off.
00113 #ifdef LIBMESH_HAVE_GETPWUID
00114       struct passwd* p = getpwuid(getuid());
00115 #endif
00116       oss << "\n";
00117 
00118       // Construct string stream objects for each of the outputs
00119       std::ostringstream
00120         pid_stream,
00121         nprocs_stream,
00122         time_stream,
00123         os_stream,
00124         host_stream,
00125         osrel_stream,
00126         osver_stream,
00127         machine_stream,
00128         user_stream;
00129 
00130 
00131       // Put pointers to these streams in a vector
00132       std::vector<std::ostringstream*> v;
00133       v.push_back(&pid_stream);
00134       v.push_back(&nprocs_stream);
00135       v.push_back(&time_stream);
00136       v.push_back(&os_stream);
00137       v.push_back(&host_stream);
00138       v.push_back(&osrel_stream);
00139       v.push_back(&osver_stream);
00140       v.push_back(&machine_stream);
00141       v.push_back(&user_stream);
00142 
00143       // Fill string stream objects
00144       if (libMesh::global_n_processors() > 1)
00145         {
00146           pid_stream     << "| Processor id:   " << libMesh::global_processor_id();
00147           nprocs_stream  << "| Num Processors: " << libMesh::global_n_processors();
00148         }
00149 
00150       time_stream    << "| Time:           " << date                   ;
00151       os_stream      << "| OS:             " << sysInfo.sysname        ;
00152       host_stream    << "| HostName:       " << sysInfo.nodename       ;
00153       osrel_stream   << "| OS Release:     " << sysInfo.release        ;
00154       osver_stream   << "| OS Version:     " << sysInfo.version        ;
00155       machine_stream << "| Machine:        " << sysInfo.machine        ;
00156       user_stream    << "| Username:       ";
00157 #ifdef LIBMESH_HAVE_GETPWUID
00158       if (p && p->pw_name)
00159         user_stream  << p->pw_name;
00160       else
00161 #endif
00162         user_stream  << "Unknown";
00163 
00164       // Parse the LIBMESH_CONFIGURE_INFO string literal before using it in PerfLog output
00165       std::string libmesh_configure_info(LIBMESH_CONFIGURE_INFO);
00166       std::vector<std::string> parsed_libmesh_configure_info;
00167       this->split_on_whitespace(libmesh_configure_info,
00168                                 parsed_libmesh_configure_info);
00169 
00170       // There should always be at at least one entry in
00171       // parsed_libmesh_configure_info, even if the user just ran
00172       // ../configure.
00173       libmesh_assert_greater (parsed_libmesh_configure_info.size(), 0);
00174 
00175       // Find the longest string in all the streams
00176       unsigned int max_length = 0;
00177       for (unsigned int i=0; i<v.size(); ++i)
00178         if (v[i]->str().size() > max_length)
00179           max_length = cast_int<unsigned int>
00180             (v[i]->str().size());
00181 
00182       // Find the longest string in the parsed_libmesh_configure_info
00183       for (unsigned i=0; i<parsed_libmesh_configure_info.size(); ++i)
00184         if (parsed_libmesh_configure_info[i].size() > max_length)
00185           max_length = cast_int<unsigned int>
00186             (parsed_libmesh_configure_info[i].size());
00187 
00188       // Print dashed line for the header
00189       oss << ' '
00190           << std::string(max_length+1, '-')
00191           << '\n';
00192 
00193       // Loop over all the strings and add end formatting
00194       for (unsigned int i=0; i<v.size(); ++i)
00195         {
00196           if (v[i]->str().size())
00197             oss << v[i]->str()
00198                 << std::setw (cast_int<int>
00199                               (max_length + 4 - v[i]->str().size()))
00200                 << std::right
00201                 << "|\n";
00202         }
00203 
00204       // Print out configuration header plus first parsed string.  The
00205       // magic number 18 below accounts for the length of the word
00206       // 'Configuration'.
00207       oss << "| Configuration:  "
00208           << parsed_libmesh_configure_info[0]
00209           << std::setw (cast_int<int>
00210                         (max_length + 4 -
00211                          parsed_libmesh_configure_info[0].size() - 18))
00212           << std::right
00213           << "|\n";
00214 
00215       // Loop over the parsed_libmesh_configure_info and add end formatting.  The magic
00216       // number 3 below accounts for the leading 'pipe' character and indentation
00217       for (unsigned i=1; i<parsed_libmesh_configure_info.size(); ++i)
00218         {
00219           oss << "|  "
00220               << parsed_libmesh_configure_info[i]
00221               << std::setw (cast_int<int>
00222                             (max_length + 4 -
00223                              parsed_libmesh_configure_info[i].size() - 3))
00224               << std::right
00225               << "|\n";
00226         }
00227 
00228 
00229       // Print dashed line
00230       oss << ' '
00231           << std::string(max_length+1, '-')
00232           << '\n';
00233     }
00234 
00235   return oss.str();
00236 }
00237 
00238 
00239 
00240 
00241 std::string PerfLog::get_perf_info() const
00242 {
00243   std::ostringstream oss;
00244 
00245   if (log_events && !log.empty())
00246     {
00247       // Stop timing for this event.
00248       struct timeval tstop;
00249 
00250       gettimeofday (&tstop, NULL);
00251 
00252       const double elapsed_time = (static_cast<double>(tstop.tv_sec  - tstart.tv_sec) +
00253                                    static_cast<double>(tstop.tv_usec - tstart.tv_usec)*1.e-6);
00254 
00255       // Figure out the formatting required based on the event names
00256       // Unsigned ints for each of the column widths
00257       unsigned int event_col_width            = 30;
00258       const unsigned int ncalls_col_width     = 11;
00259       const unsigned int tot_time_col_width   = 12;
00260       const unsigned int avg_time_col_width   = 12;
00261       const unsigned int tot_time_incl_sub_col_width   = 12;
00262       const unsigned int avg_time_incl_sub_col_width   = 12;
00263       const unsigned int pct_active_col_width = 9;
00264       const unsigned int pct_active_incl_sub_col_width = 9;
00265 
00266       // Iterator to be used to loop over the map of timed events
00267       std::map<std::pair<std::string,std::string>, PerfData>::const_iterator pos;
00268 
00269       // Reset the event column width based on the longest event name plus
00270       // a possible 2-character indentation, plus a space.
00271       for (pos = log.begin(); pos != log.end(); ++pos)
00272         if (pos->first.second.size()+3 > event_col_width)
00273           event_col_width = cast_int<unsigned int>
00274             (pos->first.second.size()+3);
00275 
00276       // Set the total width of the column
00277       const unsigned int total_col_width =
00278         event_col_width     +
00279         ncalls_col_width    +
00280         tot_time_col_width  +
00281         avg_time_col_width  +
00282         tot_time_incl_sub_col_width  +
00283         avg_time_incl_sub_col_width  +
00284         pct_active_col_width+
00285         pct_active_incl_sub_col_width+1;
00286 
00287       // Print dashed line
00288       oss << ' '
00289           << std::string(total_col_width, '-')
00290           << '\n';
00291 
00292       {
00293         // Construct temporary message string
00294         std::ostringstream temp;
00295         temp << "| " << label_name << " Performance: Alive time=" << elapsed_time
00296              << ", Active time=" << total_time;
00297 
00298         // Get the size of the temporary string
00299         const unsigned int temp_size = cast_int<unsigned int>
00300           (temp.str().size());
00301 
00302         // Send the temporary message to the output
00303         oss << temp.str();
00304 
00305         // If this string is longer than the previously computed total
00306         // column width, skip the additional formatting... this shouldn't
00307         // happen often, hopefully.  Add two additional characters for a
00308         // space and a "|" character at the end.
00309         if (temp_size < total_col_width+2)
00310           oss << std::setw(total_col_width - temp_size + 2)
00311               << std::right
00312               << "|";
00313 
00314         oss << '\n';
00315       }
00316 
00317       // Print dashed line
00318       oss << ' '
00319           << std::string(total_col_width, '-')
00320           << '\n';
00321 
00322 
00323       // Write out the header for the events listing
00324       oss << "| "
00325           << std::setw(event_col_width)
00326           << std::left
00327           << "Event"
00328           << std::setw(ncalls_col_width)
00329           << std::left
00330           << "nCalls"
00331           << std::setw(tot_time_col_width)
00332           << std::left
00333           << "Total Time"
00334           << std::setw(avg_time_col_width)
00335           << std::left
00336           << "Avg Time"
00337           << std::setw(tot_time_incl_sub_col_width)
00338           << std::left
00339           << "Total Time"
00340           << std::setw(avg_time_incl_sub_col_width)
00341           << std::left
00342           << "Avg Time"
00343           << std::setw(pct_active_col_width+pct_active_incl_sub_col_width)
00344           << std::left
00345           << "% of Active Time"
00346           << "|\n"
00347           << "| "
00348           << std::setw(event_col_width)
00349           << std::left
00350           << ""
00351           << std::setw(ncalls_col_width)
00352           << std::left
00353           << ""
00354           << std::setw(tot_time_col_width)
00355           << std::left
00356           << "w/o Sub"
00357           << std::setw(avg_time_col_width)
00358           << std::left
00359           << "w/o Sub"
00360           << std::setw(tot_time_incl_sub_col_width)
00361           << std::left
00362           << "With Sub"
00363           << std::setw(avg_time_incl_sub_col_width)
00364           << std::left
00365           << "With Sub"
00366           << std::setw(pct_active_col_width)
00367           << std::left
00368           << "w/o S"
00369           << std::setw(pct_active_incl_sub_col_width)
00370           << std::left
00371           << "With S"
00372           << "|\n|"
00373           << std::string(total_col_width, '-')
00374           << "|\n|"
00375           << std::string(total_col_width, ' ')
00376           << "|\n";
00377 
00378       unsigned int summed_function_calls = 0;
00379       double       summed_total_time     = 0;
00380       double       summed_percentage     = 0;
00381 
00382       std::string last_header("");
00383 
00384       for (pos = log.begin(); pos != log.end(); ++pos)
00385         {
00386           const PerfData& perf_data = pos->second;
00387 
00388           // Only print the event if the count is non-zero.
00389           if (perf_data.count != 0)
00390             {
00391               const unsigned int perf_count    = perf_data.count;
00392               const double       perf_time     = perf_data.tot_time;
00393               const double       perf_avg_time = perf_time / static_cast<double>(perf_count);
00394               const double       perf_time_incl_sub     = perf_data.tot_time_incl_sub;
00395               const double       perf_avg_time_incl_sub = perf_time_incl_sub / static_cast<double>(perf_count);
00396               const double       perf_percent  = (total_time != 0.) ? perf_time / total_time * 100. : 0.;
00397               const double       perf_percent_incl_sub  = (total_time != 0.) ? perf_time_incl_sub / total_time * 100. : 0.;
00398 
00399               summed_function_calls += perf_count;
00400               summed_total_time     += perf_time;
00401               summed_percentage     += perf_percent;
00402 
00403               // Print the event name
00404               if (pos->first.first == "")
00405                 oss << "| "
00406                     << std::setw(event_col_width)
00407                     << std::left
00408                     << pos->first.second;
00409 
00410               else
00411                 {
00412                   if (last_header != pos->first.first)
00413                     {
00414                       last_header = pos->first.first;
00415 
00416                       // print blank line followed by header name
00417                       // (account for additional space before the
00418                       // header)
00419                       oss << "|"
00420                           << std::string(total_col_width, ' ')
00421                           << "|\n| "
00422                           << std::setw(total_col_width-1)
00423                           << std::left
00424                           << pos->first.first
00425                           << "|\n";
00426                     }
00427 
00428                   oss << "|   "
00429                       << std::setw(event_col_width-2)
00430                       << std::left
00431                       << pos->first.second;
00432                 }
00433 
00434 
00435               // Print the number of calls to the event.
00436               oss << std::setw(ncalls_col_width)
00437                   << perf_count;
00438 
00439               // Save the original stream flags
00440               std::ios_base::fmtflags out_flags = oss.flags();
00441 
00442               // Print the total time spent in the event
00443               oss << std::fixed
00444                   << std::setprecision(4)
00445                   << std::setw(tot_time_col_width)
00446                   << std::left
00447                   << perf_time;
00448 
00449 
00450               // Print the average time per function call
00451               oss << std::fixed
00452                   << std::setprecision(6)
00453                   << std::setw(avg_time_col_width)
00454                   << std::left
00455                   << perf_avg_time;
00456 
00457               // Print the total time spent in the event incl. sub-events
00458               oss << std::fixed
00459                   << std::setprecision(4)
00460                   << std::setw(tot_time_incl_sub_col_width)
00461                   << std::left
00462                   << perf_time_incl_sub;
00463 
00464               // Print the average time per function call incl. sub-events
00465               oss << std::fixed
00466                   << std::setprecision(6)
00467                   << std::setw(avg_time_incl_sub_col_width)
00468                   << std::left
00469                   << perf_avg_time_incl_sub;
00470 
00471               // Print the percentage of the time spent in the event
00472               oss << std::fixed
00473                   << std::setprecision(2)
00474                   << std::setw(pct_active_col_width)
00475                   << std::left
00476                   << perf_percent;
00477 
00478               // Print the percentage of the time spent in the event incl. sub-events
00479               oss << std::fixed
00480                   << std::setprecision(2)
00481                   << std::setw(pct_active_incl_sub_col_width)
00482                   << std::left
00483                   << perf_percent_incl_sub;
00484 
00485               // Reset the stream flags
00486               oss.flags(out_flags);
00487 
00488               oss << "|\n";
00489             }
00490         }
00491 
00492       oss << ' '
00493           << std::string(total_col_width, '-')
00494           << "\n| "
00495           << std::setw(event_col_width)
00496           << std::left
00497           << "Totals:";
00498 
00499       // Print the total number of logged function calls
00500       // For routines which are called many times, summed_function_calls may
00501       // exceed 7 digits.  If this happens use, scientific notation.
00502       if (summed_function_calls < 9999999)
00503         oss << std::setw(ncalls_col_width)
00504             << summed_function_calls;
00505 
00506       else
00507         {
00508           // Save the original stream flags
00509           std::ios_base::fmtflags out_flags = oss.flags();
00510 
00511           oss << std::scientific
00512               << std::setprecision(3)
00513               << std::setw(ncalls_col_width)
00514               << std::left
00515               << static_cast<Real>(summed_function_calls);
00516 
00517           // Reset the stream flags
00518           oss.flags(out_flags);
00519         }
00520 
00521       // Print the total time spent in logged function calls.  Don't bother saving/restoring
00522       // the flags here since we are almost done with this stream anyway...
00523       oss << std::fixed
00524           << std::setprecision(4)
00525           << std::setw(tot_time_col_width)
00526           << std::left
00527           << summed_total_time;
00528 
00529       // Null, the average time doesn't make sense as a total
00530       oss << std::setw(avg_time_col_width) << "";
00531 
00532       // Same for times that include sub-events
00533       oss << std::setw(tot_time_incl_sub_col_width)
00534           << ""
00535           << std::setw(avg_time_incl_sub_col_width)
00536           << "";
00537 
00538       // Print the total percentage followed by dashed line
00539       oss << std::fixed
00540           << std::setprecision(2)
00541           << std::setw(pct_active_col_width)
00542           << std::left
00543           << summed_percentage
00544           << std::setw(pct_active_incl_sub_col_width)
00545           << ""
00546           << "|\n "
00547           << std::string(total_col_width, '-')
00548           << '\n';
00549     }
00550 
00551   return oss.str();
00552 }
00553 
00554 
00555 
00556 std::string PerfLog::get_log() const
00557 {
00558   std::ostringstream oss;
00559 
00560   if (log_events)
00561     {
00562       // Only print the log
00563       // if it isn't empty
00564       if (!log.empty())
00565         {
00566           // Possibly print machine info,
00567           // but only do this once
00568           if (!called)
00569             {
00570               called = true;
00571               oss << get_info_header();
00572             }
00573           oss << get_perf_info();
00574         }
00575     }
00576 
00577   return oss.str();
00578 }
00579 
00580 
00581 
00582 void PerfLog::print_log() const
00583 {
00584   if (log_events)
00585     {
00586       // Check to see if the log_string is empty, and if so,
00587       // avoid printing an unnecessary newline.
00588       std::string log_string = this->get_log();
00589       if (log_string.size() > 0)
00590         libMesh::out << log_string << std::endl;
00591     }
00592 }
00593 
00594 PerfData PerfLog::get_perf_data(const std::string &label, const std::string &header)
00595 {
00596   return log[std::make_pair(header, label)];
00597 }
00598 
00599 void PerfLog::start_event(const std::string &label,
00600                           const std::string &header)
00601 {
00602   this->push(label,header);
00603 }
00604 
00605 
00606 
00607 void PerfLog::stop_event(const std::string &label,
00608                          const std::string &header)
00609 {
00610   this->pop(label,header);
00611 }
00612 
00613 
00614 
00615 void PerfLog::pause_event(const std::string &,
00616                           const std::string &)
00617 {
00618   // nothing to do.  pushing the next object on the stack will handle it
00619 }
00620 
00621 
00622 
00623 void PerfLog::restart_event(const std::string &,
00624                             const std::string &)
00625 {
00626   // nothing to do.  popping the top off the stack will handle it.
00627 }
00628 
00629 
00630 
00631 void PerfLog::split_on_whitespace(const std::string& input, std::vector<std::string>& output) const
00632 {
00633   // Check for easy return
00634   if (input.size()==0)
00635     return;
00636 
00637   // Here we hard-code the string to split on, since the algorithm below
00638   // is somewhat specific to it...
00639   const std::string split_on("' '");
00640 
00641   size_t current_pos = 0;
00642   while (true)
00643     {
00644       // Find next end location
00645       size_t end_pos = input.find(split_on, current_pos);
00646 
00647       if (end_pos != std::string::npos)
00648         {
00649           // Create substring.  Note: the second argument to substr is
00650           // the *length* of string to create, not the ending position!
00651           output.push_back( input.substr(current_pos, end_pos - current_pos + 1) );
00652 
00653           // Update search starting position, make sure to go past the end of the split_on string, but
00654           // include the previous single quote (hence the -1).
00655           current_pos = end_pos + split_on.size() - 1;
00656         }
00657       else
00658         {
00659           // Push back whatever remains of the string onto the output.
00660           // Note that substr with only 1 argument pushes back
00661           // whatever remains of the string.  This also handles the
00662           // case where the string does not contain any matches.
00663           output.push_back( input.substr(current_pos) );
00664 
00665           // We are done searching the string, so break out of the while loop
00666           break;
00667         }
00668     }
00669 }
00670 
00671 } // namespace libMesh