1 // $Id: runreport.cpp,v 1.59 2011/03/07 06:08:51 bobgian Exp $
2 
3 /*
4   Copyright 2002  Peter Beerli, Mary Kuhner, Jon Yamato and Joseph Felsenstein
5 
6   This software is distributed free of charge for non-commercial use
7   and is copyrighted.  Of course, we do not guarantee that the software
8   works, and are not responsible for any damage you may cause or have.
9 */
10 
11 #include <cassert>
12 
13 #include "runreport.h"
14 #include "chainout.h"
15 #include "chainpack.h"
16 #include "force.h"
17 #include "forcesummary.h"
18 #include "stringx.h"
19 #include "types.h"
20 #include "registry.h"  // for GetDataPack() in MakeReport()
21 
22 #ifdef DMALLOC_FUNC_CHECK
23 #include "/usr/local/include/dmalloc.h"
24 #endif
25 
26 using namespace std;
27 
28 //------------------------------------------------------------------------------------
29 
30 string UNKNOWN_STRING = "<unknown>";
31 
32 //------------------------------------------------------------------------------------
33 
34 /****************************************************************
35    This function predicts the end of the chain process, given
36    the number of steps so far and the total required number of
37    steps.  It is meant to be called by the chain manager.
38 *****************************************************************/
RunReport(const ForceSummary & fs,verbosity_type progress)39 RunReport::RunReport(const ForceSummary& fs, verbosity_type progress)
40     : m_level(progress),
41       m_filelevel(registry.GetUserParameters().GetVerbosity()),
42       m_forcesum(fs)
43 {
44     m_profiletotal = static_cast<time_t>(0.0);
45 };
46 
PrognoseRegion(const ChainPack & chpack,long int region,long int steps,long int total)47 void RunReport::PrognoseRegion(const ChainPack& chpack, long int region,
48                                long int steps, long int total)
49 {
50 
51     if (m_level == CONCISE || m_level == NONE) return;
52     if (steps == total) return; //Let's not predict the current time.
53 
54     string timestr;
55 
56 #ifdef NOTIME_FUNC
57     // system does not provide a clock so no prognosis can be made
58     timestr = "unknown";
59 #else
60     time_t tnow = GetTime();
61     time_t tstart = chpack.GetStartTime(region);   // start of the whole thing
62     if (steps == 0) timestr = "unknown";
63     else
64     {
65         double proportion = static_cast<double>(total) / static_cast<double>(steps);
66         time_t predict = static_cast<time_t>(((tnow-tstart) * proportion) + tstart);
67         timestr = PrintTime(predict,string("%c"));
68     }
69 #endif
70 
71     string msg = "Predicted end of chains for this region:  ";
72     msg += timestr + "\n";
73     ReportNormal(msg);
74 
75 } // PrognoseRegion
76 
77 //------------------------------------------------------------------------------------
78 
79 // This functions are used to predict the end of the profile
80 // process and print a report on progress so far.  When first
81 // beginning a profile, call RecordProfileStart() to set the time;
82 // then call PrognoseProfiles to report on progress.
83 
RecordProfileStart()84 void RunReport::RecordProfileStart()
85 {
86     // Don't even try if there is no system clock.
87 #ifndef NOTIME_FUNC
88     m_profilestart = GetTime();
89 #endif
90     ReportUrgent("Beginning profiling, please be patient");
91 } // RecordProfileStart
92 
93 //------------------------------------------------------------------------------------
94 
PrognoseProfiles(long int thisprof,long int totprofs)95 void RunReport::PrognoseProfiles(long int thisprof, long int totprofs)
96 {
97     if (m_level == NONE || m_level == CONCISE) return;
98     long int profile = thisprof + 1;   // elsewhere they are counted starting from 0
99     if (profile==totprofs)
100     {
101         m_profiletotal += (GetTime() - m_profilestart);
102         string msg = "Finished profile "+ ToString(profile)
103             + " of " + ToString(totprofs) + ".\n";
104         ReportNormal(msg);
105         return;
106     }
107     string timestr;
108 
109 #ifdef NOTIME_FUNC
110     timestr = "unknown";
111 #else
112     time_t tnow = GetTime();
113     double proportion = static_cast<double>(totprofs) / static_cast<double>(profile);
114     time_t predict = static_cast<time_t>(((tnow - m_profilestart) * proportion) + m_profilestart);
115     timestr = PrintTime(predict, string("%c"));
116 #endif
117     string msg = "Finished profile " + ToString(profile) + " of " + ToString(totprofs)
118         + ".  Predicted end of this set of profiles:  " + timestr + "\n";
119     ReportNormal(msg);
120 
121 } // PrognoseProfiles
122 
PrognoseAll(const ChainPack & chpack,long int thisregion,long int totalregions)123 void RunReport::PrognoseAll(const ChainPack& chpack, long int thisregion, long int totalregions)
124 {
125     if (m_level == CONCISE || m_level == NONE) return;
126     thisregion++;
127     if ((thisregion == totalregions) && (totalregions == 1)) return;
128     //The program is already done, since there's no 'overall' region to profile.
129 
130     string timestr;
131 
132 #ifdef NOTIME_FUNC
133     // system does not provide a clock so no prognosis can be made
134     timestr = "unknown";
135 #else
136     time_t tnow = GetTime();
137     time_t tstart = chpack.GetStartTime();   // start of the whole thing
138     double proportion = static_cast<double>(totalregions) / static_cast<double>(thisregion);
139     time_t predict = static_cast<time_t>(((tnow-tstart) * proportion) + tstart);
140     if (totalregions > 1)
141     {
142         //We will probably spend as much time profiling over all regions as
143         // we spend profiling all the regions individually.
144         predict += static_cast<time_t>(m_profiletotal * proportion);
145     }
146     timestr = PrintTime(predict,string("%c"));
147 #endif
148 
149     string msg = "Predicted end of this LAMARC run: ";
150     msg += timestr;
151     ReportNormal(msg);
152 
153 } // PrognoseAll
154 
155 /**************************************************************
156    This function prints the "moving bar" which shows progress
157    of the run.  It is meant to be called by the chain.
158    You must call SetBarParameters before calling this.  Also,
159    for PrintBar to work properly it must be called for the
160    first time with steps = 0 and for the last time with steps = m_totalsteps.
161 **************************************************************/
162 
PrintBar(long int steps)163 void RunReport::PrintBar(long int steps)
164 {
165 
166 #ifdef NOPROGRESSBAR
167     if (steps == 0)
168     {
169         string msg = m_chaintype + " Chain " + ToString(m_chainno+1) + " of "
170             + ToString(registry.GetChainParameters().GetNChains(m_chaintype_num)) + ":";
171         ReportNormal(msg, false);
172     }
173     return;
174 #else
175     if (m_level == CONCISE || m_level == NONE) return;
176 
177     long int i;
178     static long int previous = 0;
179     long int width = 12;
180 
181     if (steps == 0)
182     { // Initial display of bar
183         long int chaintot = registry.GetChainParameters().GetNChains(m_chaintype_num);
184         string msg = m_chaintype + " Chain " + ToString(m_chainno+1) + " of "
185             + ToString(chaintot) + ":";
186         SaveOutput(msg, false);
187         cout << endl << PrintTime(GetTime()) << "  ";
188         cout << MakeJustified(m_chaintype + string(" chain "),-14)
189              << Pretty(m_chainno + 1L, 3)
190              << " of " << Pretty(chaintot, 3) << ":  ";
191         cout << "[|                   ] ";
192         cout << Pretty(steps + 1L, width);
193         cout.flush();
194         previous = 0;
195         return;
196     }
197 
198     long int counterDisplay = steps - m_burnin;
199 
200     if(counterDisplay % m_counter_display_increment == 0)
201     {
202         long int percent = static_cast<long int>(100.0 * static_cast<double>(steps)
203                                                  / static_cast<double>(m_totalsteps));
204 
205         if (percent > previous && ((percent/5) * 5) - percent < EPSILON)
206         {
207             for (i = 0; i < width+22; i++) cout << "\b";   // back up
208             for (i = 0; i < percent/5; i++)
209             {
210                 cout << "=";
211             }
212             if (percent != 100) cout << "|";
213             for (i = 0; i < 19-(percent/5); i++) cout << " ";
214             cout << "] ";
215         }
216         else
217         {
218             for (i = 0; i < width; ++i) cout << "\b";
219         }
220         cout << Pretty(counterDisplay, width);
221         if (steps == m_totalsteps) cout << " steps " << endl;
222         cout.flush();
223         previous = percent;
224     }
225 #endif
226 } // PrintBar
227 
228 //------------------------------------------------------------------------------------
229 
AddForceToTable(const Force * force,StringVec1d & table,const ChainOut & chout) const230 void RunReport::AddForceToTable(const Force* force, StringVec1d& table,
231                                 const ChainOut& chout) const
232 {
233     unsigned long int i;
234 
235     StringVec1d temptable = force->MakeChainParamReport(chout);
236 
237     // add the header
238     table[0] += MakeCentered(force->GetShortparamname(), temptable[0].size());
239 
240     // Code added to deal with cases where the tables are not the same
241     // size, as with divergence
242 
243     if (temptable.size() + 1 > table.size())
244     {
245         // put in blank entries
246         assert(table.size() > 0);  // no entries at all??
247         string blankentry(' ',table[1].size());
248         for (i = table.size(); i < temptable.size() + 1; ++i)
249         {
250             table.push_back(blankentry);
251         }
252     }
253     // add the parameter values
254     for (i = 1; i < table.size() && i < temptable.size() + 1; ++i)
255     {
256         table[i] += "  " + temptable[i-1];
257     }
258 
259 } // AddForceToTable
260 
261 //------------------------------------------------------------------------------------
262 
MakeReport(const ChainOut & chout) const263 void RunReport::MakeReport(const ChainOut& chout) const
264 {
265     // Prepare header lines
266     unsigned long int i;
267 
268     // Prepare tables of chain MLEs
269     m_scalars.erase();
270     m_tables.clear();
271 
272     const vector<Force *>& forces = m_forcesum.GetAllForces();
273 
274     // initialize the cross partition tables with naming info
275     // the partition tables will be handled in the loop below
276     const DataPack& datapack = registry.GetDataPack();
277     StringVec1d xpartnames = datapack.GetAllCrossPartitionNames();
278 
279     StringVec1d tables2d;
280     StringVec1d::iterator name;
281     if (xpartnames.size() > 1)
282     {
283         // all cross-partitions will use the Class name "Class".
284         tables2d.push_back(MakeJustified("Class",-22));
285         for (name = xpartnames.begin(); name != xpartnames.end(); ++name)
286             tables2d.push_back(MakeJustified(*name, -20));
287     }
288 
289     StringVec2d tables3d;
290 
291     for (i = 0; i < forces.size(); ++i) // fill up all tables
292     {
293         if (forces[i]->ReportDimensionality() == 1) // scalar
294         {
295             m_scalars += MakeJustified(forces[i]->GetShortparamname(), -10) + "  ";
296             m_scalars += forces[i]->MakeChainParamReport(chout)[0] + "  ";
297         }
298         else
299         {                               // tabular
300             // crosswise tables
301             if (forces[i]->ReportDimensionality() == 2)
302             {
303                 AddForceToTable(forces[i],tables2d,chout);
304             }
305             else
306             {
307                 // partition tables
308                 assert (forces[i]->ReportDimensionality() == 3);
309                 StringVec1d table3d;
310                 StringVec1d partnames = datapack.
311                     GetAllPartitionNames(forces[i]->GetTag());
312                 table3d.push_back(MakeJustified(forces[i]->GetClassName(),-23));
313                 for (name = partnames.begin(); name != partnames.end(); ++name)
314                     table3d.push_back(MakeJustified(*name, -22));
315                 AddForceToTable(forces[i],table3d,chout);
316                 tables3d.push_back(table3d);
317             }
318         }
319     }
320 
321     // put all the tables together into the big table
322     m_tables.push_back(string("")); // add an empty line between m_scalars
323     // and tables
324     m_tables.assign(tables2d.begin(), tables2d.end());
325     m_tables.push_back(string("")); // add an empty line between tables
326     StringVec2d::iterator tab3d;
327     for(tab3d = tables3d.begin(); tab3d != tables3d.end(); ++tab3d)
328     {
329         m_tables.insert(m_tables.end(), tab3d->begin(), tab3d->end());
330         m_tables.push_back(string("")); // add an empty line between tables
331     }
332 
333     // if heating, make swapping report
334     long int numtemps = chout.GetNumtemps();
335     if (numtemps > 1)
336     {
337         DoubleVec1d swaprates = chout.GetSwaprates();
338         DoubleVec1d temperatures = chout.GetTemperatures();
339         assert(swaprates.size() == static_cast<unsigned long int>(numtemps));
340         assert(temperatures.size() == static_cast<unsigned long int>(numtemps));
341         string tableline = "Temperature: ";
342         long int temp;
343         for (temp = 0; temp < numtemps; ++temp)
344         {
345             tableline += Pretty(temperatures[temp],7) + "  ";
346         }
347         m_tables.push_back(tableline);
348         tableline = "  Swap rate: ";
349         for (temp = 0; temp < numtemps-1; ++temp)
350         {
351             if (swaprates[temp] >= 0.0)
352             {
353                 tableline += Pretty(100.0 * swaprates[temp], 7) + "  ";
354             }
355             else
356             {
357                 tableline += "-------  ";
358             }
359         }
360         tableline += "-------";
361         m_tables.push_back(tableline);
362     }
363 } // MakeReport
364 
365 //------------------------------------------------------------------------------------
366 
FormatReport(const ChainOut & chout,bool current,long int linelength) const367 vector<string> RunReport::FormatReport(const ChainOut& chout, bool current,
368                                        long int linelength) const
369 {
370     bool bayesanalysis = registry.GetChainParameters().IsBayesian();
371     string skiptimestamp;
372     if (current) skiptimestamp = "  ";
373     else skiptimestamp = "";
374 
375     vector<string> report;
376     vector<string> temptable;
377 
378     // header
379     //If this is a summary over regions or replicates, we do not want the
380     // header information at all.  Everything is set 'FLAGLONG' or 'FLAGDOUBLE'
381     // so we check that.  It's sort of a hack, but enh.
382     if (!(chout.GetAccrate() == FLAGDOUBLE &&
383           chout.GetLlikedata() == FLAGDOUBLE &&
384           chout.GetNumBadTrees() == FLAGDOUBLE &&
385           chout.GetTinyPopTrees() == FLAGDOUBLE &&
386           chout.GetZeroDLTrees() == FLAGDOUBLE &&
387           chout.GetStretchedTrees() == FLAGDOUBLE ))
388     {
389         string tempstring = "";
390         if (current) tempstring = PrintTime(GetTime()) + "  ";
391         tempstring += "Accepted ";
392         if (chout.GetAccrate() == FLAGDOUBLE)
393         {
394             tempstring += UNKNOWN_STRING;
395         }
396         else
397         {
398             tempstring += MakeJustified(ToString(100.0 * chout.GetAccrate()), 5) + "%";
399         }
400         if (bayesanalysis)
401             tempstring += " | Point Likelihood " + Pretty(chout.GetLlikemle(), 10);
402         else
403             tempstring += " | Posterior lnL " + Pretty(chout.GetLlikemle(), 10);
404         tempstring += " | Data lnL ";
405         if (chout.GetLlikedata() == NEG_MAX)
406         {
407             tempstring += UNKNOWN_STRING;
408         }
409         else
410         {
411             tempstring += Pretty(chout.GetLlikedata(), 10);
412         }
413         report.push_back(tempstring);
414         long int badtrees = chout.GetNumBadTrees();
415         long int tinytrees = chout.GetTinyPopTrees();
416         long int zerodltrees = chout.GetZeroDLTrees();
417         long int stretchedtrees = chout.GetStretchedTrees();
418         if (badtrees == 0 && tinytrees == 0 &&
419             (zerodltrees == 0 || zerodltrees == FLAGLONG) &&
420             (stretchedtrees == 0 || stretchedtrees == FLAGLONG))
421         {
422             tempstring = "No trees discarded due to limit violations.";
423             report.push_back(tempstring);
424         }
425         else
426         {
427             tempstring = "Trees discarded due to too many events: ";
428             if (badtrees == FLAGLONG)
429             {
430                 tempstring += UNKNOWN_STRING;
431             }
432             else
433             {
434                 tempstring += Pretty(badtrees);
435             }
436             report.push_back(tempstring);
437 
438             tempstring = "Trees discarded due to too small population sizes: ";
439             if (tinytrees == FLAGLONG)
440             {
441                 tempstring += UNKNOWN_STRING;
442             }
443             else
444             {
445                 tempstring += Pretty(tinytrees);
446             }
447             report.push_back(tempstring);
448 
449             tempstring = "Trees discarded due to an infinitesimal data likelihood: ";
450             if (zerodltrees == FLAGLONG)
451             {
452                 tempstring += UNKNOWN_STRING;
453             }
454             else
455             {
456                 tempstring += Pretty(zerodltrees);
457             }
458             report.push_back(tempstring);
459 
460             tempstring = "Trees discarded due to extremely long branch lengths: ";
461             if (stretchedtrees == FLAGLONG)
462             {
463                 tempstring += UNKNOWN_STRING;
464             }
465             else
466             {
467                 tempstring += Pretty(stretchedtrees);
468             }
469             report.push_back(tempstring);
470         }
471         // If multiple Arrangers, print an Arranger report
472         ratemap arrates = chout.GetAllAccrates();
473         ratemap::const_iterator rate;
474         if (arrates.size() > 1)
475         {
476             for (rate = arrates.begin(); rate != arrates.end(); ++rate)
477             {
478                 report.push_back(MakeJustified(rate->first + " accepted ", -28) + " " +
479                                  MakeJustified(ToString(rate->second.first), 8) + "/" +
480                                  ToString(rate->second.second) + " proposals");
481             }
482         }
483     }
484 
485     LongVec1d bayesunique = chout.GetBayesUnique();
486     if (bayesunique.size()>0)
487     {
488         //We have bayesian stuff.
489         report.push_back("");
490         report.push_back("Number of unique sampled values for each parameter:");
491         const ParamVector paramvec(true);
492         assert(bayesunique.size() == paramvec.size());
493         bool toofew = false;
494         for (unsigned long int pnum = 0; pnum < paramvec.size(); pnum++)
495         {
496             ParamStatus mystatus = paramvec[pnum].GetStatus();
497             if (mystatus.Varies())
498             {
499                 string numbayes = ToString(bayesunique[pnum]);
500                 if (bayesunique[pnum] < 50)
501                 {
502                     toofew = true;
503                     numbayes = numbayes + "*";
504                 }
505                 report.push_back(MakeJustified(numbayes, std::max(static_cast<int>(numbayes.size()), 6))
506                                  + ": " + MakeJustified(paramvec[pnum].GetName(),-70));
507             }
508         }
509         if (toofew)
510         {
511             report.push_back("* Note!  This parameter has too few sampled values to provide a reasonable estimate.  "
512                              "Consider collecting more samples or narrowing your prior for this value.");
513         }
514         report.push_back("");
515     }
516 
517     // Actually print the estimates
518     if (m_scalars.size())
519     {
520         report.push_back(skiptimestamp + m_scalars);
521     }
522     temptable.insert(temptable.end(),m_tables.begin(),m_tables.end());
523 
524     temptable = Linewrap(temptable, linelength);
525 
526     transform(temptable.begin(),temptable.end(),temptable.begin(),
527               bind1st(plus<string>(),skiptimestamp));
528     report.insert(report.end(),temptable.begin(),temptable.end());
529 
530     return(report);
531 } // FormatReport
532 
533 //------------------------------------------------------------------------------------
534 
DisplayReport(const ChainOut & chout)535 void RunReport::DisplayReport(const ChainOut& chout)
536 {
537     MakeReport(chout);
538     vector<string> rpt = FormatReport(chout,true,80);
539     for (unsigned long int it = 0; it < rpt.size(); ++it)
540     {
541         ReportNormal(rpt[it], false, 80);
542     }
543 } // DisplayReport
544 
545 //------------------------------------------------------------------------------------
546 
SetBarParameters(long int totsteps,long int burn,long int chno,long int chtype)547 void RunReport::SetBarParameters(long int totsteps, long int burn, long int chno, long int chtype)
548 {
549     m_totalsteps = totsteps;
550     m_chainno = chno;
551     m_burnin = burn;
552     m_counter_display_increment = 10;
553     if (m_totalsteps % 10L != 0)
554     {
555         m_counter_display_increment = registry.GetChainParameters().GetInterval(chtype);
556     }
557     m_burnpercent = 100.0 * static_cast<double>(burn)/static_cast<double>(totsteps);
558     m_chaintype_num = chtype;
559     switch (chtype)
560     {
561         case 0:
562             m_chaintype = "Initial";
563             break;
564         case 1:
565             m_chaintype = "Final";
566             break;
567         default:
568             assert(false);
569             m_chaintype = "Unknown";
570             break;
571     }
572 } // SetBarParameters
573 
574 //------------------------------------------------------------------------------------
575 
ReportUrgent(const string & msg,bool printtime,long int linelength)576 void RunReport::ReportUrgent(const string& msg, bool printtime, long int linelength)
577 {
578     if (m_level != NONE)
579     {
580         PrettyPrint(msg, printtime, linelength);
581     }
582     if (m_filelevel != NONE)
583     {
584         SaveOutput(msg, printtime);
585     }
586 }
587 
ReportNormal(const string & msg,bool printtime,long int linelength)588 void RunReport::ReportNormal(const string& msg, bool printtime, long int linelength)
589 {
590     if (m_level == NORMAL || m_level==VERBOSE)
591     {
592         PrettyPrint(msg, printtime, linelength);
593     }
594     if (m_filelevel == NORMAL || m_filelevel==VERBOSE)
595     {
596         SaveOutput(msg, printtime);
597     }
598 }
599 
ReportNormal(const StringVec1d & msgs,bool printtime,long int linelength)600 void RunReport::ReportNormal(const StringVec1d& msgs, bool printtime, long int linelength)
601 {
602     if (msgs.size() > 0)
603     {
604         ReportNormal(msgs[0], printtime, linelength);
605     }
606     for (unsigned long int line = 1; line<msgs.size(); line++)
607     {
608         ReportNormal("          " + msgs[line], false, linelength);
609     }
610 }
611 
ReportChat(const string & msg,bool printtime,long int linelength)612 void RunReport::ReportChat(const string& msg, bool printtime, long int linelength)
613 {
614     if (m_level==VERBOSE)
615     {
616         PrettyPrint(msg, printtime, linelength);
617     }
618     if (m_filelevel==VERBOSE)
619     {
620         SaveOutput(msg, printtime);
621     }
622 }
623 
ReportDebug(const string & msg,bool printtime,long int linelength)624 void RunReport::ReportDebug(const string& msg, bool printtime, long int linelength)
625 {
626 #ifdef NDEBUG
627     return;
628 #else
629     string debugmsg = "Debug message: " + msg;
630     PrettyPrint(debugmsg, printtime, linelength);
631     SaveOutput(debugmsg, printtime);
632 #endif
633 }
634 
ReportOnce(const string & msg,OnceKey key,bool doNormal)635 void RunReport::ReportOnce(const string& msg, OnceKey key, bool doNormal)
636 {
637     if (m_keyfrequencies.find(key) == m_keyfrequencies.end())
638     {
639         //This is the first time we've seen this message.  Add it, and report
640         // it to the user, either with ReportNormal or ReportDebug.
641         m_keyfrequencies.insert(make_pair(key, 1L));
642         if (doNormal)
643         {
644             ReportNormal(msg);
645             m_keyindices.insert(make_pair(static_cast<long int>(m_messages.size())-1, key));
646         }
647         else
648         {
649             ReportDebug(msg);
650 #ifndef NDEBUG
651             m_keyindices.insert(make_pair(static_cast<long int>(m_messages.size())-1, key));
652 #endif
653         }
654         //Whether we actually reported it or not, we stick it in the index.  This
655         // forces each OnceKey to come from exactly one call (or that all such
656         // calls be the same version of doNormal); at present, there
657         // are only four such calls, and they're all unique.
658     }
659     else
660     {
661         //We've seen this message before. Increment its count, but don't report.
662         KeyToLongMapiter oldkey = m_keyfrequencies.find(key);
663         long int count = oldkey->second;
664         count++;
665         m_keyfrequencies.erase(oldkey);
666         m_keyfrequencies.insert(make_pair(key, count));
667     }
668 }
669 
670 //Private--use one of the above routines instead.
PrettyPrint(const string & msg,bool printtime,long int linelength)671 void RunReport::PrettyPrint(const string& msg, bool printtime, long int linelength)
672 {
673     vector<string> msgline;
674     msgline.push_back(msg);
675     long int full_line = linelength - 10;
676 
677     string timestr, skiptime;
678 #ifdef NOTIME_FUNC
679     // system does not provide a clock
680     timestr = "";
681     skiptime = "";
682     full_line = linelength;
683 #else
684     if (printtime)
685     {
686         time_t tnow = GetTime();
687         timestr = PrintTime(tnow) + "  ";
688         skiptime = "          ";
689     }
690     else
691     {
692         timestr = "";
693         skiptime = "";
694         full_line = linelength;
695     }
696 #endif
697 
698     vector<string> msglines = Linewrap(msgline, full_line);
699 
700     vector<string>::iterator oneline = msglines.begin();
701     cout << timestr << (*oneline) << endl;
702     for(oneline++; oneline != msglines.end(); ++oneline)
703         cout << skiptime << (*oneline) << endl;
704 }
705 
SaveOutput(const string & msg,bool printtime)706 void RunReport::SaveOutput(const string& msg, bool printtime)
707 {
708     string timestr;
709 #ifdef NOTIME_FUNC
710     // system does not provide a clock
711     timestr = "";
712 #else
713     if (printtime)
714     {
715         time_t tnow = GetTime();
716         timestr = PrintTime(tnow) + "  ";
717     }
718     else
719     {
720         timestr = "";
721     }
722 #endif
723     m_messages.push_back(timestr + msg);
724 }
725 
726 //____________________________________________________________________________________
727