Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members

timing.C

Go to the documentation of this file.
00001 // Copyright (C) 2004, Kenneth C. Schalk
00002 // 
00003 // This file is part of Vesta.
00004 // 
00005 // Vesta is free software; you can redistribute it and/or
00006 // modify it under the terms of the GNU Lesser General Public
00007 // License as published by the Free Software Foundation; either
00008 // version 2.1 of the License, or (at your option) any later version.
00009 // 
00010 // Vesta is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013 // Lesser General Public License for more details.
00014 // 
00015 // You should have received a copy of the GNU Lesser General Public
00016 // License along with Vesta; if not, write to the Free Software
00017 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018 
00019 // timing.C - implementation of fine-grained NFS call timing
00020 // recording, one of the performance debugging features
00021 
00022 // Last modified on Tue Dec 13 16:23:39 EST 2005 by ken@xorian.net
00023 
00024 #include <BufStream.H>
00025 #include <iomanip>
00026 
00027 #include <Basics.H>
00028 
00029 #include "VestaSource.H"
00030 #include <VestaConfig.H>
00031 
00032 #include "logging.H"
00033 #include "timing.H"
00034 
00035 #include <assert.h>
00036 #include <pthread.h>
00037 #include <unistd.h>
00038 #include <string.h>
00039 #include <stdlib.h>
00040 
00041 using std::setw;
00042 using std::setfill;
00043 using Basics::OBufStream;
00044 
00045 static pthread_key_t timing_key;
00046 static pthread_once_t timing_once = PTHREAD_ONCE_INIT;
00047 
00048 // Should we be recording right now?  (Note: *not* protected by a
00049 // mutex, which makes for a race condition.  We don't really care
00050 // though.  A timing recorder should see its value on the next NFS
00051 // call anyway.)
00052 static bool recording_timing = false;
00053 
00054 Basics::mutex popen_mu;
00055 
00056 static unsigned int timing_file_minutes = 60;
00057 
00058 extern "C"
00059 {
00060   static void timing_init()
00061   {
00062     int err = pthread_key_create(&timing_key, NULL);
00063     assert(err == 0);
00064 
00065     try
00066       {
00067         Text unused;
00068         if(VestaConfig::get("Repository",
00069                             "timing_file_minutes", unused))
00070           timing_file_minutes = VestaConfig::get_int("Repository",
00071                                                      "timing_file_minutes");
00072       }
00073     catch (VestaConfig::failure f)
00074       {
00075         Repos::dprintf(DBG_ALWAYS,
00076                        "VestaConfig::failure in VestaSourceServerExport: %s\n",
00077                        f.msg.cchars());
00078         abort();
00079       }
00080   
00081   }
00082 }
00083 
00084 void timing_start_call(unsigned int proc)
00085 {
00086   pthread_once(&timing_once, timing_init);
00087   Timing_Recorder *recorder =
00088     (Timing_Recorder *) pthread_getspecific(timing_key);
00089 
00090   if(recorder != 0)
00091     {
00092       recorder->start(proc);
00093     }
00094 }
00095 
00096 void timing_dupe_status(int new_call)
00097 {
00098   pthread_once(&timing_once, timing_init);
00099   Timing_Recorder *recorder =
00100     (Timing_Recorder *) pthread_getspecific(timing_key);
00101 
00102   if(recorder != 0)
00103     {
00104       recorder->dupe_status(new_call != 0);
00105     }
00106 }
00107 
00108 void timing_record_time_point(const char *file, unsigned int line)
00109 {
00110   pthread_once(&timing_once, timing_init);
00111   Timing_Recorder *recorder =
00112     (Timing_Recorder *) pthread_getspecific(timing_key);
00113 
00114   if(recorder != 0)
00115     {
00116       recorder->time_point(file, line);
00117     }
00118 }
00119 
00120 void timing_end_call(/*OUT*/ struct timeval *delta_time)
00121 {
00122   pthread_once(&timing_once, timing_init);
00123   Timing_Recorder *recorder =
00124     (Timing_Recorder *) pthread_getspecific(timing_key);
00125 
00126   if(recorder != 0)
00127     {
00128       struct timeval delta = recorder->end();
00129       if(delta_time != 0)
00130         {
00131           *delta_time = delta;
00132         }
00133     }
00134   else if(delta_time != 0)
00135     {
00136       delta_time->tv_sec = 0;
00137       delta_time->tv_usec = 0;
00138     }
00139 }
00140 
00141 int timing_in_call()
00142 {
00143   pthread_once(&timing_once, timing_init);
00144   Timing_Recorder *recorder =
00145     (Timing_Recorder *) pthread_getspecific(timing_key);
00146 
00147   if(recorder != 0)
00148     {
00149       return (recorder->in_call() ? 1 : 0);
00150     }
00151   return 0;
00152 }
00153 
00154 void timing_control(bool enable)
00155 {
00156   recording_timing = enable;
00157 }
00158 
00159 void timing_record_LongId(const LongId &lid)
00160 {
00161   pthread_once(&timing_once, timing_init);
00162   Timing_Recorder *recorder =
00163     (Timing_Recorder *) pthread_getspecific(timing_key);
00164 
00165   if(recorder != 0)
00166     {
00167       recorder->record_LongId(lid);
00168     }
00169 }
00170 
00171 
00172 static unsigned long thread_index()
00173 {
00174 #if defined(__linux__)
00175   return pthread_self()/1024;
00176 #elif defined(__osf__)
00177   return pthread_getsequence_np(pthread_self());
00178 #else
00179 #error Need way to get thread index for this platform
00180 #endif
00181 }
00182 
00183 #define USECS_PER_SEC 1000000
00184 
00185 static struct timeval delta_time(const struct timeval &time_a,
00186                                  const struct timeval &time_b)
00187 {
00188   struct timeval result;
00189 
00190   if(time_a.tv_sec == time_b.tv_sec)
00191     {
00192       result.tv_sec = 0;
00193       // Need to handle time moving backwards thanks to ntp
00194       result.tv_usec = ((time_b.tv_usec >= time_a.tv_usec)
00195                         ? time_b.tv_usec - time_a.tv_usec
00196                         : 0);
00197     }
00198   else if(time_b.tv_sec > time_a.tv_sec)
00199     {
00200       result.tv_sec = (time_b.tv_sec - time_a.tv_sec) - 1;
00201       result.tv_usec = (time_b.tv_usec + USECS_PER_SEC) - time_a.tv_usec;
00202       if(result.tv_usec > USECS_PER_SEC)
00203         {
00204           result.tv_sec += result.tv_usec/USECS_PER_SEC;
00205           result.tv_usec %= USECS_PER_SEC;
00206         }
00207     }
00208   else
00209     {
00210       // Need to handle time moving backwards thanks to ntp
00211       result.tv_sec = 0;
00212       result.tv_usec = 0;
00213     }
00214 
00215   return result;
00216 }
00217 
00218 static pthread_once_t timing_cleanup_once = PTHREAD_ONCE_INIT;
00219 static Basics::mutex recorder_list_mu;
00220 Timing_Recorder *Timing_Recorder::first = 0;
00221 
00222 extern "C"
00223 {
00224   void Timing_Recorder_exit_cleanup() throw()
00225   {
00226     recorder_list_mu.lock();
00227     Timing_Recorder *cur = Timing_Recorder::first;
00228     while(cur)
00229       {
00230         Timing_Recorder *next = cur->next;
00231         delete cur;
00232         cur = next;
00233       }
00234     Timing_Recorder::first = 0;
00235     recorder_list_mu.unlock();
00236   }
00237 
00238   void Timing_Recorder_exit_cleanup_setup() throw()
00239   {
00240     atexit(Timing_Recorder_exit_cleanup);
00241   }
00242 }
00243 
00244 Timing_Recorder::Timing_Recorder()
00245   : current_byte_count(0), timing_record_stream(0), file_end_time(0)
00246 {
00247   pthread_once(&timing_once, timing_init);
00248 
00249   // Only one timing recorder per thread
00250   assert(pthread_getspecific(timing_key) == 0);
00251 
00252   // This is the timing recorder for this thread.
00253   int err = pthread_setspecific(timing_key, (void *) this);
00254   assert(err == 0);
00255 
00256   // Make sure everything gets cleaned up at exit time.
00257   pthread_once(&timing_cleanup_once, Timing_Recorder_exit_cleanup_setup);
00258 
00259   // Insert this recorder into the list
00260   recorder_list_mu.lock();
00261   this->next = Timing_Recorder::first;
00262   Timing_Recorder::first = this;
00263   recorder_list_mu.unlock();
00264 }
00265 
00266 void Timing_Recorder::start_new_file(struct timeval *current_time)
00267 {
00268   // Close existing stream, if any.
00269   if(timing_record_stream != 0)
00270     {
00271       fflush(timing_record_stream);
00272       popen_mu.lock();
00273       pclose(timing_record_stream);
00274       popen_mu.unlock();
00275     }
00276 
00277   // Get the year/month/day/hour/minute for use in the timing filename
00278 
00279   // If this is the first file we're opening, use the current time
00280   // rounded down to the specified number of minutes.
00281   struct tm time_detail;
00282   if(file_end_time == 0)
00283     {
00284       // Get the current time if the caller didn't provide it.
00285       struct timeval temp_time;
00286       if(current_time == 0)
00287         {
00288           struct timezone unused_tz;
00289 
00290           gettimeofday(&temp_time, &unused_tz);
00291 
00292           current_time = &temp_time;
00293         }
00294 
00295       localtime_r(&(current_time->tv_sec), &time_detail);
00296       time_detail.tm_sec = 0;
00297       time_detail.tm_min -= (time_detail.tm_min%timing_file_minutes);
00298     }
00299   // Use the end time for the last file to label the next file
00300   else
00301     {
00302       time_t file_label_time = file_end_time;
00303       localtime_r(&file_label_time, &time_detail);
00304     }
00305 
00306   // Generate a name for a directory to put the timings into, and
00307   // create it if it doesn't exist.
00308   OBufStream l_dirname;
00309   l_dirname << "timings."
00310             << (time_detail.tm_year+1900) << "-"
00311             << setw(2) << setfill('0') << (time_detail.tm_mon + 1) << "-"
00312             << setw(2) << setfill('0') << time_detail.tm_mday;
00313   if(!FS::Exists(l_dirname.str()))
00314     {
00315       mkdir(l_dirname.str(), 0755);
00316     }
00317 
00318   // Open the stream we'll record our results into.  We start a gzip
00319   // process to compress them as we write them.
00320   OBufStream l_cmd;
00321   l_cmd << "exec gzip - > " << l_dirname.str()
00322         << "/nfs_calls.pid_" << getpid()
00323         << ".thread_" << thread_index()
00324         << "."
00325         << setw(2) << setfill('0') << time_detail.tm_hour << "_"
00326         << setw(2) << setfill('0') << time_detail.tm_min;
00327   popen_mu.lock();
00328   timing_record_stream = popen(l_cmd.str(), "w");
00329   popen_mu.unlock();
00330   assert(timing_record_stream != 0);
00331 
00332   // Compute the time at which we should start a new file (the
00333   // beginning of the next interval).
00334   file_end_time = mktime(&time_detail) + 60*timing_file_minutes;
00335 }
00336 
00337 Timing_Recorder::~Timing_Recorder()
00338 {
00339   if(timing_record_stream != 0)
00340     {
00341       fflush(timing_record_stream);
00342       popen_mu.lock();
00343       pclose(timing_record_stream);
00344       popen_mu.unlock();
00345     }
00346 }
00347 
00348 void Timing_Recorder::start(unsigned int proc)
00349 {
00350   // Can't start a new call until we finish the last one.
00351   assert(current_byte_count == 0);
00352 
00353   // Get the start time
00354   struct timezone unused_tz;
00355   int err = gettimeofday(&(this->start_time), &unused_tz);
00356   assert(err == 0);
00357 
00358   // If we're not supposed to be recording timing, do nothing else.
00359   if(!recording_timing)
00360     return;
00361 
00362   // Start a new timing file if we should
00363   if((timing_record_stream == 0) || (this->start_time.tv_sec >= file_end_time))
00364     {
00365       start_new_file(&(this->start_time));
00366 
00367       // Get the time again to avoid counting the change of file for
00368       // this call.
00369       err = gettimeofday(&(this->start_time), &unused_tz);
00370       assert(err == 0);
00371     }
00372 
00373   // ------------------------------------------------------------
00374   // Place the start record into the buffer
00375   // ------------------------------------------------------------
00376 
00377   // Start marker byte
00378   write_buffer[0] = 1;
00379 
00380   // NFS procedure
00381   write_buffer[1] = proc;
00382   assert(write_buffer[1] == proc);
00383 
00384   // Start time seconds
00385   unsigned int start_sec = this->start_time.tv_sec;
00386   memcpy(&(write_buffer[2]), (void *) &start_sec, sizeof(unsigned int));
00387 
00388   // Start time microseconds
00389   unsigned int start_usec = this->start_time.tv_usec;
00390   memcpy(&(write_buffer[6]), (void *) &start_usec, sizeof(unsigned int));
00391 
00392   // Set initial byte count
00393   current_byte_count = 2+(sizeof(unsigned int)*2);
00394   assert(current_byte_count == 10);
00395 }
00396 
00397 struct timeval Timing_Recorder::record_delta()
00398 {
00399   // Get the current time
00400   struct timeval current_time;
00401   struct timezone unused_tz;
00402   int err = gettimeofday(&current_time, &unused_tz);
00403   assert(err == 0);
00404 
00405   // Find the time delta
00406   struct timeval delta = delta_time(this->start_time, current_time);
00407 
00408   // Seconds delta from start
00409   unsigned short sec_delta = delta.tv_sec;
00410   assert(sec_delta == delta.tv_sec);
00411   memcpy(&(write_buffer[current_byte_count]),
00412          (void *) &sec_delta, sizeof(unsigned short));
00413   assert(sizeof(unsigned short) == 2);
00414   current_byte_count += sizeof(unsigned short);
00415 
00416   // Microseconds delta from start
00417   unsigned int usec_delta = delta.tv_usec;
00418   memcpy(&(write_buffer[current_byte_count]),
00419          (void *) &usec_delta, sizeof(unsigned int));
00420   assert(sizeof(unsigned int) == 4);
00421   current_byte_count += sizeof(unsigned int);
00422 
00423   return delta;
00424 }
00425 
00426 void Timing_Recorder::dupe_status(bool new_call)
00427 {
00428   // If we're not in a call, do nothing.
00429   if(!in_call())
00430     return;
00431 
00432   // ------------------------------------------------------------
00433   // Place the duplicate record into the buffer
00434   // ------------------------------------------------------------
00435 
00436   // Duplicate status marker byte
00437   write_buffer[current_byte_count++] = (new_call ? 4 : 5);
00438 
00439   // Time delta from start
00440   (void) record_delta();
00441 
00442   // Make sure we haven't run out of buffer space.
00443   assert(current_byte_count < TIMING_RECORDER_BUF_SIZE);
00444 }
00445 
00446 void Timing_Recorder::time_point(const char *file, unsigned short line)
00447 {
00448   // If we're not in a call, do nothing.
00449   if(!in_call())
00450     return;
00451 
00452   // ------------------------------------------------------------
00453   // Place the time point record into the buffer
00454   // ------------------------------------------------------------
00455 
00456   // Time point marker byte
00457   write_buffer[current_byte_count++] = 2;
00458 
00459   // Time delta from start
00460   (void) record_delta();
00461 
00462   // Source file
00463   strcpy(&(write_buffer[current_byte_count]), file);
00464   current_byte_count += strlen(file)+1;
00465 
00466   // Source line number
00467   memcpy(&(write_buffer[current_byte_count]),
00468          (void *) &line, sizeof(unsigned short));
00469   current_byte_count += sizeof(unsigned short);
00470 
00471   // Make sure we haven't run out of buffer space.
00472   assert(current_byte_count < TIMING_RECORDER_BUF_SIZE);
00473 }
00474 
00475 struct timeval Timing_Recorder::end()
00476 {
00477   // If we're not in a call, just compute and return the call delta.
00478   if(!in_call())
00479     {
00480       // Get the current time
00481       struct timeval current_time;
00482       struct timezone unused_tz;
00483       int err = gettimeofday(&current_time, &unused_tz);
00484       assert(err == 0);
00485 
00486       // Find the time delta
00487       return delta_time(this->start_time, current_time);
00488     }
00489 
00490   // ------------------------------------------------------------
00491   // Place the end record into the buffer
00492   // ------------------------------------------------------------
00493 
00494   // End record marker byte
00495   write_buffer[current_byte_count++] = 3;
00496 
00497   // Time delta from start
00498   struct timeval delta = record_delta();
00499 
00500   // Make sure we haven't run out of buffer space.
00501   assert(current_byte_count < TIMING_RECORDER_BUF_SIZE);
00502 
00503   // Write the entire record for this call
00504   size_t written = fwrite(write_buffer, current_byte_count, 1,
00505                           timing_record_stream);
00506   assert(written == 1);
00507   fflush(timing_record_stream);
00508 
00509   // Reset the byte count
00510   current_byte_count = 0;
00511 
00512   // Return the delta since the start of call.
00513   return delta;
00514 }
00515 
00516 void Timing_Recorder::record_LongId(const LongId &lid)
00517 {
00518   // If we're not in a call, do nothing.
00519   if(!in_call())
00520     return;
00521 
00522   // ------------------------------------------------------------
00523   // Place the LongId record into the buffer
00524   // ------------------------------------------------------------
00525 
00526   // LongId marker byte
00527   write_buffer[current_byte_count++] = 6;
00528 
00529   // Store the LongId
00530   memcpy(&(write_buffer[current_byte_count]), &(lid.value.byte[0]),
00531          sizeof(lid.value));
00532   current_byte_count += sizeof(lid.value);
00533 
00534   // Make sure we haven't run out of buffer space.
00535   assert(current_byte_count < TIMING_RECORDER_BUF_SIZE);
00536 }

Generated on Mon May 8 00:48:47 2006 for Vesta by  doxygen 1.4.2