diff options
author | Andy Zhou <azhou@nicira.com> | 2015-03-21 00:00:48 -0700 |
---|---|---|
committer | Andy Zhou <azhou@nicira.com> | 2015-04-13 17:23:09 -0700 |
commit | 619c3a42dc1e3fb4e5d0afb3c926ac3d1e7ea7a2 (patch) | |
tree | cdc2cbd6a575b1137724da3b0421735566043d54 /lib/perf-counter.c | |
parent | a75776e6091b8597d966bc1a1cabb150a2de08f0 (diff) | |
download | openvswitch-619c3a42dc1e3fb4e5d0afb3c926ac3d1e7ea7a2.tar.gz |
lib: add a hardware performance counter access library
First cut of adding a performance library that provides access to
hardware counters. Please see comments in perf-counter.h for
more details.
Signed-off-by: Andy Zhou <azhou@nicira.com>
Acked-by: Ethan Jackson <ethan@nicira.com>
Diffstat (limited to 'lib/perf-counter.c')
-rw-r--r-- | lib/perf-counter.c | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/lib/perf-counter.c b/lib/perf-counter.c new file mode 100644 index 000000000..e2eaefc02 --- /dev/null +++ b/lib/perf-counter.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2015 Nicira, 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. + */ + +/* This implementation only applies to the Linux platform. */ +#ifdef __linux__ + +#include <stddef.h> +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <linux/perf_event.h> +#include <asm/unistd.h> +#include <config.h> +#include "dynamic-string.h" +#include "openvswitch/vlog.h" +#include "perf-counter.h" +#include "shash.h" +#include "util.h" + +VLOG_DEFINE_THIS_MODULE(perf_counter); + +static struct shash perf_counters; +static int fd__ = 0; + +uint64_t +perf_counter_read(uint64_t *counter) +{ + if (fd__ > 0) { + read(fd__, counter, sizeof(*counter)); + } else { + *counter = 0; + } + + return *counter; +} + +static long +perf_event_open(struct perf_event_attr *hw_event, pid_t pid, + int cpu, int group_fd, unsigned long flags) +{ + int ret; + + ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, + group_fd, flags); + return ret; +} + +/* Set up perf event counters to read user space instruction counters + * only for this process, on all cpus. */ +static void +perf_event_setup(void) +{ + struct perf_event_attr pe; + + memset(&pe, 0, sizeof(struct perf_event_attr)); + pe.type = PERF_TYPE_HARDWARE; + pe.size = sizeof(struct perf_event_attr); + pe.config = PERF_COUNT_HW_INSTRUCTIONS; + pe.disabled = 1; + pe.exclude_kernel = 1; + pe.exclude_hv = 1; + + fd__ = perf_event_open(&pe, 0, -1, -1, 0); + if (fd__ == -1) { + VLOG_ERR("Peformance counter is not available on this platform.\n"); + } else { + ioctl(fd__, PERF_EVENT_IOC_RESET, 0); + ioctl(fd__, PERF_EVENT_IOC_ENABLE, 0); + } +} + +static void +perf_counter_init(struct perf_counter *counter) +{ + counter->once = true; + shash_add_assert(&perf_counters, counter->name, counter); +} + +void +perf_counter_accumulate(struct perf_counter *counter, uint64_t start_count) +{ + uint64_t end_count; + + if (!counter->once) { + perf_counter_init(counter); + } + + counter->n_events++; + perf_counter_read(&end_count); + counter->total_count += end_count - start_count; +} + +static void +perf_counter_to_ds(struct ds *ds, struct perf_counter *pfc) +{ + double ratio; + + if (pfc->n_events) { + ratio = (double)pfc->total_count / (double)pfc->n_events; + } else { + ratio = 0.0; + } + + ds_put_format(ds, "%-40s%12lu%12lu%12.1f\n", pfc->name, pfc->n_events, + pfc->total_count, ratio); +} + +static void +perf_counters_to_ds(struct ds *ds) +{ + const char *err_str; + const struct shash_node **sorted; + int i; + + err_str = NULL; + if (fd__ == -1) { + err_str = "performance counter is not supported on this platfrom"; + } else if (!shash_count(&perf_counters)) { + err_str = "performance counter has never been hit"; + } + + if (err_str) { + ds_put_format(ds, "%s\n", err_str); + return; + } + + /* Display counters in alphabetical order. */ + sorted = shash_sort(&perf_counters); + for (i = 0; i < shash_count(&perf_counters); i++) { + perf_counter_to_ds(ds, sorted[i]->data); + } + free(sorted); +} + +/* + * Caller is responsible for free memory. + */ +char * +perf_counters_to_string() +{ + struct ds ds; + + ds_init(&ds); + perf_counters_to_ds(&ds); + return ds_steal_cstr(&ds); +} + +void +perf_counters_init(void) +{ + shash_init(&perf_counters); + perf_event_setup(); +} + +void +perf_counters_clear(void) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &perf_counters) { + struct perf_counter *perf = node->data; + + perf->n_events = 0; + perf->total_count = 0; + } +} + +void +perf_counters_destroy() +{ + struct shash_node *node, *next; + + if (fd__ != -1) { + ioctl(fd__, PERF_EVENT_IOC_DISABLE, 0); + close(fd__); + } + + SHASH_FOR_EACH_SAFE (node, next, &perf_counters) { + shash_delete(&perf_counters, node); + } + + shash_destroy(&perf_counters); +} +#endif |