| /* |
| * 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. |
| * |
| * Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net> |
| * |
| * 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; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #elif defined(_MSC_VER) |
| #include "config-msvc.h" |
| #endif |
| |
| #include "syshead.h" |
| |
| #include "perf.h" |
| |
| #ifdef ENABLE_PERFORMANCE_METRICS |
| |
| #include "error.h" |
| #include "otime.h" |
| |
| #include "memdbg.h" |
| |
| 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(¤t, NULL)); |
| p->sofar += (double) tv_subtract(¤t, &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 /* ifdef ENABLE_PERFORMANCE_METRICS */ |
| #ifdef _MSC_VER /* Dummy function needed to avoid empty file compiler warning in Microsoft VC */ |
| static void |
| dummy(void) |
| { |
| } |
| #endif |
| #endif /* ifdef ENABLE_PERFORMANCE_METRICS */ |