2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 09:58:01 +00:00
ovs/lib/stopwatch.c
Adrian Moreno 9e56549c2b hmap: use short version of safe loops if possible.
Using SHORT version of the *_SAFE loops makes the code cleaner and less
error prone. So, use the SHORT version and remove the extra variable
when possible for hmap and all its derived types.

In order to be able to use both long and short versions without changing
the name of the macro for all the clients, overload the existing name
and select the appropriate version depending on the number of arguments.

Acked-by: Dumitru Ceara <dceara@redhat.com>
Acked-by: Eelco Chaudron <echaudro@redhat.com>
Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2022-03-30 16:59:02 +02:00

553 lines
15 KiB
C

/* Copyright (c) 2017 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <config.h>
#include "stopwatch.h"
#include "openvswitch/shash.h"
#include "openvswitch/vlog.h"
#include "unixctl.h"
#include "openvswitch/dynamic-string.h"
#include "openvswitch/poll-loop.h"
#include "ovs-thread.h"
#include <unistd.h>
#include "socket-util.h"
#include "util.h"
#include "latch.h"
#include "guarded-list.h"
VLOG_DEFINE_THIS_MODULE(stopwatch);
struct average {
double average; /* Moving average */
double alpha; /* Weight given to new samples */
};
#define MARKERS 5
/* Number of samples to collect before reporting P-square calculated
* percentile
*/
#define P_SQUARE_MIN 50
/* The naming of these fields is based on the naming used in the
* P-square algorithm paper.
*/
struct percentile {
int n[MARKERS];
double n_prime[MARKERS];
double q[MARKERS];
double dn[MARKERS];
unsigned long long samples[P_SQUARE_MIN];
double percentile;
};
struct stopwatch {
enum stopwatch_units units;
unsigned long long n_samples;
unsigned long long max;
unsigned long long min;
struct percentile pctl;
struct average short_term;
struct average long_term;
unsigned long long sample_start;
bool sample_in_progress;
};
enum stopwatch_op {
OP_START_SAMPLE,
OP_END_SAMPLE,
OP_SYNC,
OP_RESET,
OP_SHUTDOWN,
};
struct stopwatch_packet {
struct ovs_list list_node;
enum stopwatch_op op;
char name[32];
unsigned long long time;
};
static struct shash stopwatches = SHASH_INITIALIZER(&stopwatches);
static struct ovs_mutex stopwatches_lock = OVS_MUTEX_INITIALIZER;
static pthread_cond_t stopwatches_sync = PTHREAD_COND_INITIALIZER;
static struct latch stopwatch_latch;
static struct guarded_list stopwatch_commands;
static pthread_t stopwatch_thread_id;
static const char *unit_name[] = {
[SW_MS] = "msec",
[SW_US] = "usec",
[SW_NS] = "nsec",
};
/* Percentile value we are calculating */
#define P 0.95
static int
comp_samples(const void *left, const void *right)
{
const double *left_d = left;
const double *right_d = right;
return *right_d > *left_d ? -1 : *right_d < *left_d;
}
/* Calculate the percentile using the P-square algorithm. For more
* information, see https://www1.cse.wustl.edu/~jain/papers/ftp/psqr.pdf
*/
static void
calc_percentile(unsigned long long n_samples, struct percentile *pctl,
unsigned long long new_sample)
{
if (n_samples < P_SQUARE_MIN) {
pctl->samples[n_samples - 1] = new_sample;
}
/* For the first MARKERS samples, we calculate the percentile
* in the traditional way in the pct->q array.
*/
if (n_samples <= MARKERS) {
pctl->q[n_samples - 1] = new_sample;
qsort(pctl->q, n_samples, sizeof *pctl->q, comp_samples);
if (n_samples == MARKERS) {
pctl->n[0] = 0;
pctl->n[1] = 1;
pctl->n[2] = 2;
pctl->n[3] = 3;
pctl->n[4] = 4;
pctl->n_prime[0] = 0;
pctl->n_prime[1] = 2 * P;
pctl->n_prime[2] = 4 * P;
pctl->n_prime[3] = 2 + 2 * P;
pctl->n_prime[4] = 4;
pctl->dn[0] = 0;
pctl->dn[1] = P / 2;
pctl->dn[2] = P;
pctl->dn[3] = (1 + P) / 2;
pctl->dn[4] = 1;
}
pctl->percentile = pctl->q[(int) P * n_samples];
return;
}
/* From here on, update the markers using quadratic spline calculations */
int k;
if (new_sample < pctl->q[0]) {
k = 0;
pctl->q[0] = new_sample;
} else if (new_sample < pctl->q[1]) {
k = 0;
} else if (new_sample < pctl->q[2]) {
k = 1;
} else if (new_sample < pctl->q[3]) {
k = 2;
} else if (new_sample <= pctl->q[4]) {
k = 3;
} else {
k = 3;
pctl->q[4] = new_sample;
}
for (int i = k + 1; i < MARKERS; i++) {
pctl->n[i]++;
}
for (int i = 0; i < MARKERS; i++) {
pctl->n_prime[i] += pctl->dn[i];
}
for (int i = 1; i < MARKERS - 1; i++) {
double d = pctl->n_prime[i] - pctl->n[i];
if ((d >= 1 && pctl->n[i + 1] - pctl->n[i] > 1) ||
(d <= -1 && pctl->n[i - 1] - pctl->n[i] < -1)) {
d = d >= 0 ? 1 : -1;
double a = d / (pctl->n[i + 1] - pctl->n[i - 1]);
double b = (pctl->n[i] - pctl->n[i - 1] + d) *
(pctl->q[i + 1] - pctl->q[i]) / (pctl->n[i + 1] - pctl->n[i]);
double c = (pctl->n[i + 1] - pctl->n[i] - d) *
(pctl->q[i] - pctl->q[i - 1]) / (pctl->n[i] - pctl->n[i - 1]);
double candidate = pctl->q[i] + a * (b + c);
if (pctl->q[i - 1] < candidate && candidate < pctl->q[i + 1]) {
pctl->q[i] = candidate;
} else {
pctl->q[i] = pctl->q[i] +
(d * (pctl->q[i + (int)d] - pctl->q[i]) /
(pctl->n[i +(int)d] - pctl->n[i]));
}
pctl->n[i] += d;
}
}
/* Without enough samples, P-square is not very accurate. Until we reach
* P_SQUARE_MIN, use a traditional calculation for the percentile.
*/
if (n_samples < P_SQUARE_MIN) {
qsort(pctl->samples, n_samples, sizeof *pctl->samples, comp_samples);
pctl->percentile = pctl->samples[(int) (P * n_samples)];
} else {
pctl->percentile = pctl->q[2];
}
}
static void
calc_average(struct average *avg, double new_sample)
{
avg->average = new_sample * avg->alpha + (1 - avg->alpha) * avg->average;
}
static void
add_sample(struct stopwatch *sw, unsigned long long new_sample)
{
if (new_sample > sw->max) {
sw->max = new_sample;
}
if (new_sample < sw->min || sw->n_samples == 0) {
sw->min = new_sample;
}
if (sw->n_samples++ == 0) {
sw->short_term.average = sw->long_term.average = new_sample;
return;
}
calc_percentile(sw->n_samples, &sw->pctl, new_sample);
calc_average(&sw->short_term, new_sample);
calc_average(&sw->long_term, new_sample);
}
static bool
stopwatch_get_stats_protected(const char *name,
struct stopwatch_stats *stats)
{
struct stopwatch *perf;
perf = shash_find_data(&stopwatches, name);
if (!perf) {
return false;
}
stats->count = perf->n_samples;
stats->unit = perf->units;
stats->max = perf->max;
stats->min = perf->min;
stats->pctl_95 = perf->pctl.percentile;
stats->ewma_50 = perf->short_term.average;
stats->ewma_1 = perf->long_term.average;
return true;
}
bool
stopwatch_get_stats(const char *name, struct stopwatch_stats *stats)
{
bool found = false;
ovs_mutex_lock(&stopwatches_lock);
found = stopwatch_get_stats_protected(name, stats);
ovs_mutex_unlock(&stopwatches_lock);
return found;
}
static void
stopwatch_print(struct stopwatch *sw, const char *name,
struct ds *s)
{
ds_put_format(s, "Statistics for '%s'\n", name);
const char *units = unit_name[sw->units];
ds_put_format(s, " Total samples: %llu\n", sw->n_samples);
ds_put_format(s, " Maximum: %llu %s\n", sw->max, units);
ds_put_format(s, " Minimum: %llu %s\n", sw->min, units);
ds_put_format(s, " 95th percentile: %f %s\n",
sw->pctl.percentile, units);
ds_put_format(s, " Short term average: %f %s\n",
sw->short_term.average, units);
ds_put_format(s, " Long term average: %f %s\n",
sw->long_term.average, units);
}
static bool
stopwatch_show_protected(int argc, const char *argv[], struct ds *s)
{
struct stopwatch *sw;
if (argc > 1) {
sw = shash_find_data(&stopwatches, argv[1]);
if (!sw) {
ds_put_cstr(s, "No such stopwatch");
return false;
}
stopwatch_print(sw, argv[1], s);
} else {
struct shash_node *node;
SHASH_FOR_EACH (node, &stopwatches) {
sw = node->data;
stopwatch_print(sw, node->name, s);
}
}
return true;
}
static void
stopwatch_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
const char *argv[], void *aux OVS_UNUSED)
{
struct ds s = DS_EMPTY_INITIALIZER;
bool success;
ovs_mutex_lock(&stopwatches_lock);
success = stopwatch_show_protected(argc, argv, &s);
ovs_mutex_unlock(&stopwatches_lock);
if (success) {
unixctl_command_reply(conn, ds_cstr(&s));
} else {
unixctl_command_reply_error(conn, ds_cstr(&s));
}
ds_destroy(&s);
}
static struct stopwatch_packet *
stopwatch_packet_create(enum stopwatch_op op)
{
struct stopwatch_packet *pkt;
pkt = xzalloc(sizeof *pkt);
pkt->op = op;
return pkt;
}
static void
stopwatch_packet_write(struct stopwatch_packet *pkt)
{
guarded_list_push_back(&stopwatch_commands, &pkt->list_node, SIZE_MAX);
latch_set(&stopwatch_latch);
}
static void
stopwatch_reset(struct unixctl_conn *conn, int argc OVS_UNUSED,
const char *argv[], void *aux OVS_UNUSED)
{
struct stopwatch_packet *pkt = stopwatch_packet_create(OP_RESET);
if (argc > 1) {
ovs_strlcpy(pkt->name, argv[1], sizeof pkt->name);
}
stopwatch_packet_write(pkt);
unixctl_command_reply(conn, "");
}
static void
stopwatch_start_sample_protected(const struct stopwatch_packet *pkt)
{
struct stopwatch *sw = shash_find_data(&stopwatches, pkt->name);
if (!sw || sw->sample_in_progress) {
return;
}
sw->sample_start = pkt->time;
sw->sample_in_progress = true;
}
static void
stopwatch_end_sample_protected(const struct stopwatch_packet *pkt)
{
struct stopwatch *sw = shash_find_data(&stopwatches, pkt->name);
if (!sw || !sw->sample_in_progress) {
return;
}
add_sample(sw, pkt->time - sw->sample_start);
sw->sample_in_progress = false;
}
static void reset_stopwatch(struct stopwatch *sw)
{
sw->short_term.average = 0;
sw->long_term.average = 0;
sw->pctl.percentile = 0;
sw->n_samples = 0;
sw->max = 0;
sw->min = 0;
/* Don't reset sw->sample_start or sw->sample_in_progress.
* This way, if a sample was currently in progress, it can be
* concluded properly after the reset.
*/
}
static void
stopwatch_reset_protected(const struct stopwatch_packet *pkt)
{
if (pkt->name[0]) {
struct stopwatch *sw = shash_find_data(&stopwatches, pkt->name);
if (!sw) {
return;
}
reset_stopwatch(sw);
return;
}
struct shash_node *node;
SHASH_FOR_EACH (node, &stopwatches) {
struct stopwatch *sw = node->data;
reset_stopwatch(sw);
}
}
static void *
stopwatch_thread(void *ign OVS_UNUSED)
{
bool should_exit = false;
while (!should_exit) {
struct ovs_list command_list;
struct stopwatch_packet *pkt;
latch_poll(&stopwatch_latch);
guarded_list_pop_all(&stopwatch_commands, &command_list);
ovs_mutex_lock(&stopwatches_lock);
LIST_FOR_EACH_POP (pkt, list_node, &command_list) {
switch (pkt->op) {
case OP_START_SAMPLE:
stopwatch_start_sample_protected(pkt);
break;
case OP_END_SAMPLE:
stopwatch_end_sample_protected(pkt);
break;
case OP_SYNC:
xpthread_cond_signal(&stopwatches_sync);
break;
case OP_RESET:
stopwatch_reset_protected(pkt);
break;
case OP_SHUTDOWN:
should_exit = true;
break;
}
free(pkt);
}
ovs_mutex_unlock(&stopwatches_lock);
if (!should_exit) {
latch_wait(&stopwatch_latch);
poll_block();
}
}
return NULL;
}
static void
stopwatch_exit(void)
{
struct shash_node *node;
struct stopwatch_packet *pkt = stopwatch_packet_create(OP_SHUTDOWN);
stopwatch_packet_write(pkt);
xpthread_join(stopwatch_thread_id, NULL);
/* Process is exiting and we have joined the only
* other competing thread. We are now the sole owners
* of all data in the file.
*/
SHASH_FOR_EACH_SAFE (node, &stopwatches) {
struct stopwatch *sw = node->data;
shash_delete(&stopwatches, node);
free(sw);
}
shash_destroy(&stopwatches);
ovs_mutex_destroy(&stopwatches_lock);
guarded_list_destroy(&stopwatch_commands);
latch_destroy(&stopwatch_latch);
}
static void
do_init_stopwatch(void)
{
unixctl_command_register("stopwatch/show", "[NAME]", 0, 1,
stopwatch_show, NULL);
unixctl_command_register("stopwatch/reset", "[NAME]", 0, 1,
stopwatch_reset, NULL);
guarded_list_init(&stopwatch_commands);
latch_init(&stopwatch_latch);
stopwatch_thread_id = ovs_thread_create(
"stopwatch", stopwatch_thread, NULL);
atexit(stopwatch_exit);
}
static void
stopwatch_init(void)
{
static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
if (ovsthread_once_start(&once)) {
do_init_stopwatch();
ovsthread_once_done(&once);
}
}
void
stopwatch_create(const char *name, enum stopwatch_units units)
{
stopwatch_init();
struct stopwatch *sw = xzalloc(sizeof *sw);
sw->units = units;
sw->short_term.alpha = 0.50;
sw->long_term.alpha = 0.01;
ovs_mutex_lock(&stopwatches_lock);
shash_add(&stopwatches, name, sw);
ovs_mutex_unlock(&stopwatches_lock);
}
void
stopwatch_start(const char *name, unsigned long long ts)
{
struct stopwatch_packet *pkt = stopwatch_packet_create(OP_START_SAMPLE);
ovs_strlcpy(pkt->name, name, sizeof pkt->name);
pkt->time = ts;
stopwatch_packet_write(pkt);
}
void
stopwatch_stop(const char *name, unsigned long long ts)
{
struct stopwatch_packet *pkt = stopwatch_packet_create(OP_END_SAMPLE);
ovs_strlcpy(pkt->name, name, sizeof pkt->name);
pkt->time = ts;
stopwatch_packet_write(pkt);
}
void
stopwatch_sync(void)
{
struct stopwatch_packet *pkt = stopwatch_packet_create(OP_SYNC);
ovs_mutex_lock(&stopwatches_lock);
stopwatch_packet_write(pkt);
ovs_mutex_cond_wait(&stopwatches_sync, &stopwatches_lock);
ovs_mutex_unlock(&stopwatches_lock);
}