perf.c
6fbf66fa
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single TCP/UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
564a2109
  *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
6fbf66fa
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program (see the file COPYING included with this
  *  distribution); if not, write to the Free Software Foundation, Inc.,
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "syshead.h"
 
 #include "perf.h"
 
 #ifdef ENABLE_PERFORMANCE_METRICS
 
 #include "error.h"
 #include "otime.h"
 
 #include "memdbg.h"
 
 #ifdef USE_PTHREAD
 #error ENABLE_PERFORMANCE_METRICS is incompatible with USE_PTHREAD
 #endif
 
 static const char *metric_names[] = {
   "PERF_BIO_READ_PLAINTEXT",
   "PERF_BIO_WRITE_PLAINTEXT",
   "PERF_BIO_READ_CIPHERTEXT",
   "PERF_BIO_WRITE_CIPHERTEXT",
   "PERF_TLS_MULTI_PROCESS",
   "PERF_IO_WAIT",
   "PERF_EVENT_LOOP",
   "PERF_MULTI_CREATE_INSTANCE",
   "PERF_MULTI_CLOSE_INSTANCE",
   "PERF_MULTI_SHOW_STATS",
   "PERF_MULTI_BCAST",
   "PERF_MULTI_MCAST",
   "PERF_SCRIPT",
   "PERF_READ_IN_LINK",
   "PERF_PROC_IN_LINK",
   "PERF_READ_IN_TUN",
   "PERF_PROC_IN_TUN",
   "PERF_PROC_OUT_LINK",
   "PERF_PROC_OUT_TUN",
   "PERF_PROC_OUT_TUN_MTCP"
 };
 
 struct perf
 {
 # define PS_INITIAL            0
 # define PS_METER_RUNNING      1
 # define PS_METER_INTERRUPTED  2
   int state;
 
   struct timeval start;
   double sofar;
   double sum;
   double max;
   double count;
 };
 
 struct perf_set
 {
   int stack_len;
   int stack[STACK_N];
   struct perf perf[PERF_N];
 };
 
 static struct perf_set perf_set;
 
 static void perf_print_state (int lev);
 
 static inline int
 get_stack_index (int sdelta)
 {
   const int sindex = perf_set.stack_len + sdelta;
   if (sindex >= 0 && sindex < STACK_N)
     return sindex;
   else
     return -1;
 }
 
 static int
 get_perf_index (int sdelta)
 {
   const int sindex = get_stack_index (sdelta);
   if (sindex >= 0)
     {
       const int pindex = perf_set.stack[sindex];
       if (pindex >= 0 && pindex < PERF_N)
 	return pindex;
       else
 	return -1;
     }
   else
     return -1;
 }
 
 static struct perf *
 get_perf (int sdelta)
 {
   const int pindex = get_perf_index (sdelta);
   if (pindex >= 0)
     return &perf_set.perf[pindex];
   else
     return NULL;
 }
 
 static void
 push_perf_index (int pindex)
 {
   const int sindex = get_stack_index (0);
   const int newlen = get_stack_index (1);
   if (sindex >= 0 && newlen >= 0
       && pindex >= 0 && pindex < PERF_N)
     {
       int i;
       for (i = 0; i < sindex; ++i)
 	if (perf_set.stack[i] == pindex)
 	  {
 	    perf_print_state (M_INFO);
 	    msg (M_FATAL, "PERF: push_perf_index %s failed",
 		 metric_names [pindex]); 
 	  }
 
       perf_set.stack[sindex] = pindex;
       perf_set.stack_len = newlen;
     }
   else
     msg (M_FATAL, "PERF: push_perf_index: stack push error"); 
 }
 
 static void
 pop_perf_index (void)
 {
   const int newlen = get_stack_index (-1);
   if (newlen >= 0)
     {
       perf_set.stack_len = newlen;
     }
   else
     msg (M_FATAL, "PERF: pop_perf_index: stack pop error"); 
 }
 
 static void
 state_must_be (const struct perf *p, const int wanted)
 {
   if (p->state != wanted)
     msg (M_FATAL, "PERF: bad state actual=%d wanted=%d",
 	 p->state,
 	 wanted);
 }
 
 static void
 update_sofar (struct perf *p)
 {
   struct timeval current;
   ASSERT (!gettimeofday (&current, NULL));
   p->sofar += (double) tv_subtract (&current, &p->start, 600) / 1000000.0;
   tv_clear (&p->start);
 }
 
 static void
 perf_start (struct perf *p)
 {
   state_must_be (p, PS_INITIAL);
   ASSERT (!gettimeofday (&p->start, NULL));
   p->sofar = 0.0;
   p->state = PS_METER_RUNNING;
 }
 
 static void
 perf_stop (struct perf *p)
 {
   state_must_be (p, PS_METER_RUNNING);
   update_sofar (p);
   p->sum += p->sofar;
   if (p->sofar > p->max)
     p->max = p->sofar;
   p->count += 1.0;
   p->sofar = 0.0;
   p->state = PS_INITIAL;
 }
 
 static void
 perf_interrupt (struct perf *p)
 {
   state_must_be (p, PS_METER_RUNNING);
   update_sofar (p);
   p->state = PS_METER_INTERRUPTED;
 }
 
 static void
 perf_resume (struct perf *p)
 {
   state_must_be (p, PS_METER_INTERRUPTED);
   ASSERT (!gettimeofday (&p->start, NULL));
   p->state = PS_METER_RUNNING;
 }
 
 void
 perf_push (int type)
 {
   struct perf *prev;
   struct perf *cur;
 
   ASSERT (SIZE(metric_names) == PERF_N);
   push_perf_index (type);
 
   prev = get_perf (-2);
   cur = get_perf (-1);
 
   ASSERT (cur);
 
   if (prev)
     perf_interrupt (prev);
   perf_start (cur);
 }
 
 void
 perf_pop (void)
 {
   struct perf *prev;
   struct perf *cur;
 
   prev = get_perf (-2);
   cur = get_perf (-1);
 
   ASSERT (cur);
   perf_stop (cur);
 
   if (prev)
     perf_resume (prev);
 
   pop_perf_index ();
 }
 
 void
 perf_output_results (void)
 {
   int i;
   msg (M_INFO, "LATENCY PROFILE (mean and max are in milliseconds)");
   for (i = 0; i < PERF_N; ++i)
     {
       struct perf *p = &perf_set.perf[i];
       if (p->count > 0.0)
 	{
 	  const double mean = p->sum / p->count;
 	  msg (M_INFO, "%s n=%.0f mean=%.3f max=%.3f", metric_names[i], p->count, mean*1000.0, p->max*1000.0);
 	}
     }
 }
 
 static void
 perf_print_state (int lev)
 {
   struct gc_arena gc = gc_new ();
   int i;
   msg (lev, "PERF STATE");
   msg (lev, "Stack:");
   for (i = 0; i < perf_set.stack_len; ++i)
     {
       const int j = perf_set.stack[i];
       const struct perf *p = &perf_set.perf[j];
       msg (lev, "[%d] %s state=%d start=%s sofar=%f sum=%f max=%f count=%f",
 	   i,
 	   metric_names[j],
 	   p->state,
 	   tv_string (&p->start, &gc),
 	   p->sofar,
 	   p->sum,
 	   p->max,
 	   p->count);
     }
   gc_free (&gc);
 }
 
 #else
 static void dummy(void) {}
 #endif