This is similar to the existing coverage and perf-counter APIs in OVS. However, rather than keeping counters, this is aimed at timing how long operations take to perform. "Operations" in this case can be anything from a loop iteration, to a function, to something more complex.
The library will keep track of how long it takes to perform the particular operations and will maintain statistics of those running times. Statistics for a particular operation can be queried from the command line by using ovs-appctl -t <target> performance/show <operation name>. The API is designed to be pretty small. The expected steps are as follows: 1) Create a performance measurement, providing a unique name, using performance_create() 2) Add calls to start_sample() and end_sample() to mark the start and stop of the operation you wish to measure. 3) Periodically call performance_run() in order to compile statistics. 4) Upon completion (likely program shutdown), call performance_destroy() to clean up. Signed-off-by: Mark Michelson <[email protected]> --- lib/automake.mk | 2 + lib/performance.c | 335 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/performance.h | 41 +++++++ 3 files changed, 378 insertions(+) create mode 100644 lib/performance.c create mode 100644 lib/performance.h diff --git a/lib/automake.mk b/lib/automake.mk index effe5b5c2..a3ac7bc1d 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -205,6 +205,8 @@ lib_libopenvswitch_la_SOURCES = \ lib/pcap-file.h \ lib/perf-counter.h \ lib/perf-counter.c \ + lib/performance.h \ + lib/performance.c \ lib/poll-loop.c \ lib/process.c \ lib/process.h \ diff --git a/lib/performance.c b/lib/performance.c new file mode 100644 index 000000000..0d0de3645 --- /dev/null +++ b/lib/performance.c @@ -0,0 +1,335 @@ +/* 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 "performance.h" +#include "timer.h" +#include "timeval.h" +#include "openvswitch/shash.h" +#include "openvswitch/vlog.h" +#include "unixctl.h" +#include "openvswitch/dynamic-string.h" +#include "openvswitch/poll-loop.h" + +VLOG_DEFINE_THIS_MODULE(performance); + +struct sample { + /* Time when we started this sample */ + long long int start_time; + /* Time when we ended this sample */ + long long int end_time; + /* Amount of time elapsed during timing. + * Equal to end_time - start_time + */ + long long int elapsed; +}; + +struct sample_vec { + struct sample *samples; + size_t n_samples; + size_t capacity; +}; + +static void +add_sample(struct sample_vec *vec, struct sample *new_sample) +{ + if (vec->capacity == vec->n_samples) { + vec->samples = x2nrealloc(vec->samples, &vec->capacity, + sizeof *vec->samples); + } + + vec->samples[vec->n_samples++] = *new_sample; +} + +static int +find_earliest(const struct sample_vec *vec, long long int age_ms) +{ + long long int cutoff = time_msec() - age_ms; + + for (size_t i = 0; i < vec->n_samples; i++) { + if (vec->samples[i].end_time >= cutoff) { + return i; + } + } + + /* Either the vector is empty or all times are + * older than the cutoff. + */ + return -1; +} + +static long long int +average(const struct sample *samples, size_t num_samples) +{ + /* Avoid division by zero */ + if (num_samples == 0) { + return 0; + } + + long long int sum = 0; + for (size_t i = 0; i < num_samples; i++) { + sum += samples[i].elapsed; + } + + return sum / num_samples; +} + +static long long int +percentile(const struct sample *samples, size_t num_samples, int percentile) +{ + if (num_samples == 0) { + return 0; + } + + size_t pctl = num_samples * percentile / 100; + return samples[pctl].elapsed; +} + +static void +cull_old_times(struct sample_vec *vec, long long int age_ms) +{ + int i = find_earliest(vec, age_ms); + + if (i <= 0) { + return; + } + + size_t new_size = vec->n_samples - i; + memmove(vec->samples, &vec->samples[i], new_size * sizeof *vec->samples); + vec->n_samples = new_size; +} + +struct stats { + long long int min; + long long int max; + long long int average; + long long int percentile; + long long int num_samples; +}; + +struct performance { + struct sample_vec vec; + long long int sample_rate; + struct timer timer; + struct sample cur_sample; + struct stats one_min; + struct stats five_min; + struct stats ten_min; + struct ovsdb_idl *idl; +}; + +struct shash performances = SHASH_INITIALIZER(&performances); + +static void +format_stats(struct ds *s, const char *prefix, struct stats *stats) +{ + if (stats->num_samples) { + ds_put_format(s, "\t%s samples: %lld\n", prefix, stats->num_samples); + ds_put_format(s, "\t%s minimum: %lld\n", prefix, stats->min); + ds_put_format(s, "\t%s maximum: %lld\n", prefix, stats->max); + ds_put_format(s, "\t%s average: %lld\n", prefix, stats->average); + ds_put_format(s, "\t%s 95th percentile: %lld\n\n", prefix, + stats->percentile); + } else { + ds_put_format(s, "\t%s samples: 0\n", prefix); + ds_put_format(s, "\t%s minimum: N/A\n", prefix); + ds_put_format(s, "\t%s maximum: N/A\n", prefix); + ds_put_format(s, "\t%s average: N/A\n", prefix); + ds_put_format(s, "\t%s 95th percentile: N/A\n\n", prefix); + } +} + +static void +performance_show(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[], void *ignore OVS_UNUSED) +{ + struct performance *perf = shash_find_data(&performances, argv[1]); + + if (!perf) { + unixctl_command_reply_error(conn, "No such performance"); + return; + } + + struct ds s = DS_EMPTY_INITIALIZER; + ds_put_format(&s, "Statistics for '%s'\n", argv[1]); + + format_stats(&s, "1 minute", &perf->one_min); + format_stats(&s, "5 minute", &perf->five_min); + format_stats(&s, "10 minute", &perf->ten_min); + + unixctl_command_reply(conn, ds_cstr(&s)); + ds_destroy(&s); +} + +static void +do_init_performance(void) +{ + unixctl_command_register("performance/show", "", 1, 1, + performance_show, NULL); +} + +static void +performance_init(void) +{ + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, do_init_performance); +} + +void +performance_create(const char *name, long long int sample_rate) +{ + performance_init(); + + struct performance *perf = xcalloc(1, sizeof *perf); + perf->sample_rate = sample_rate; + timer_set_duration(&perf->timer, perf->sample_rate); + shash_add(&performances, name, perf); +} + +void +performance_destroy(const char *name) +{ + struct performance *perf = shash_find_and_delete(&performances, name); + if (perf) { + free(perf->vec.samples); + } + + free(perf); +} + +void +performance_set_sample_rate(const char *name, long long int sample_rate) +{ + struct performance *perf = shash_find_data(&performances, name); + if (!perf || perf->sample_rate == sample_rate) { + return; + } + perf->sample_rate = sample_rate; + /* If we're shortening the sample rate, then make the timer + * expire more quickly. Otherwise, leave it alone. + */ + if (timer_msecs_until_expired(&perf->timer) > sample_rate) { + timer_set_duration(&perf->timer, sample_rate); + } +} + +bool +start_sample(const char *name) +{ + struct performance *perf = shash_find_data(&performances, name); + if (!perf) { + return false; + } + + /* We already started sampling. Need an end before + * we start another sample + */ + if (perf->cur_sample.start_time) { + return false; + } + + perf->cur_sample.start_time = time_msec(); + return true; +} + +bool +end_sample(const char *name) +{ + struct performance *perf = shash_find_data(&performances, name); + if (!perf) { + return false; + } + + /* We can't end a sample if we haven't started one */ + if (!perf->cur_sample.start_time) { + return false; + } + + perf->cur_sample.end_time = time_msec(); + perf->cur_sample.elapsed = perf->cur_sample.end_time + - perf->cur_sample.start_time; + + add_sample(&perf->vec, &perf->cur_sample); + + memset(&perf->cur_sample, 0, sizeof perf->cur_sample); + return true; +} + +static int +cmp_times(const void *left_, const void *right_) +{ + const struct sample *left = left_; + const struct sample *right = right_; + + return left->elapsed == right->elapsed ? 0 + : left->elapsed > right->elapsed; +} + +static struct sample * +sort_times(const struct sample *by_time, size_t vec_size) +{ + struct sample *copy = xmalloc(vec_size * sizeof *copy); + memcpy(copy, by_time, vec_size * sizeof *copy); + qsort(copy, vec_size, sizeof *copy, cmp_times); + + return copy; +} + +static int +get_stats(const struct sample_vec *vec, long long int age_ms, + struct stats *stats) +{ + int start_idx = find_earliest(vec, age_ms); + if (start_idx < 0) { + memset(stats, 0, sizeof *stats); + return -1; + } + size_t vec_size = vec->n_samples - start_idx; + + struct sample *by_time = &vec->samples[start_idx]; + struct sample *by_elapsed = sort_times(by_time, vec_size); + + stats->min = by_elapsed[0].elapsed; + stats->max = by_elapsed[vec_size - 1].elapsed; + stats->average = average(by_time, vec_size); + stats->percentile = percentile(by_elapsed, vec_size, 95); + stats->num_samples = vec_size; + + free(by_elapsed); + + return 0; +} + +void +performance_run(const char *name) +{ + struct performance *perf = shash_find_data(&performances, name); + if (!perf) { + return; + } + if (!timer_expired(&perf->timer)) { + timer_wait(&perf->timer); + return; + } + + get_stats(&perf->vec, 60000, &perf->one_min); + get_stats(&perf->vec, 300000, &perf->five_min); + get_stats(&perf->vec, 600000, &perf->ten_min); + + cull_old_times(&perf->vec, 600000); + timer_set_duration(&perf->timer, perf->sample_rate); + timer_wait(&perf->timer); +} diff --git a/lib/performance.h b/lib/performance.h new file mode 100644 index 000000000..64d460708 --- /dev/null +++ b/lib/performance.h @@ -0,0 +1,41 @@ +/* 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. + */ + +#ifndef PERFORMANCE_H +#define PERFORMANCE_H 1 + +#include <stdbool.h> + +/* This file provides a method for timing operations in OVS. + * + * The expected operation is as follows: + * 1) Call performance_create(), supplying a unique name for the + * operation that is being measured. + * 2) Place calls to start_sample() and end_sample() at the beginning + * and end of the operation you wish to measure. + * 3) Periodically call performance_run() so that statistics can be + * gathered based on the recorded times. If you are running a loop, + * calling this once per loop is a good practice. + * 4) When complete, call performance_destroy() to clean up. + */ + +void performance_create(const char *name, long long int sample_rate); +void performance_destroy(const char *name); +void performance_set_sample_rate(const char * name, long long int sample_rate); +bool start_sample(const char *name); +bool end_sample(const char *name); +void performance_run(const char *name); + +#endif /* performance.h */ -- 2.13.5 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
