diff options
author | Dave Barach <dave@barachs.net> | 2019-01-24 10:34:24 -0500 |
---|---|---|
committer | Damjan Marion <dmarion@me.com> | 2019-01-24 16:19:04 +0000 |
commit | ec595ef02639005b34334097af76b41ceef3dca5 (patch) | |
tree | b8b752d9c9371b9ea75a9a28bfa1a3a6e7494b18 /src/plugins/perfmon | |
parent | 22f23ae802f6dc654dbef27340c67773eb8be8c3 (diff) |
perfmon plugin: 2-way parallel stat collection
As a FUD reduction measure, this patch implements 2-way parallel
counter collection. Synthetic stat component counter pairs run at the
same time. Running two counters (of any kind) at the same time
naturally reduces the aggregate time required by an approximate
factor-of-2, depending on whether an even or odd number of stats have
been requested.
I don't completely buy the argument that computing synthetic stats
such as instructions-per-clock will be inaccurate if component counter
values are collected sequentially. Given uniform traffic pattern, it
must make no difference.
As the collection interval increases, the difference between serial
and parallel component counter collection will approach zero, see also
the Central Limit theorem.
Change-Id: I36ebdcf125e8882cca8a1929ec58f17fba1ad8f1
Signed-off-by: Dave Barach <dave@barachs.net>
Diffstat (limited to 'src/plugins/perfmon')
-rw-r--r-- | src/plugins/perfmon/perfmon.c | 62 | ||||
-rw-r--r-- | src/plugins/perfmon/perfmon.h | 14 | ||||
-rw-r--r-- | src/plugins/perfmon/perfmon_periodic.c | 295 |
3 files changed, 228 insertions, 143 deletions
diff --git a/src/plugins/perfmon/perfmon.c b/src/plugins/perfmon/perfmon.c index c6a80224e0e..359555705aa 100644 --- a/src/plugins/perfmon/perfmon.c +++ b/src/plugins/perfmon/perfmon.c @@ -157,10 +157,16 @@ perfmon_init (vlib_main_t * vm) pm->log_class = vlib_log_register_class ("perfmon", 0); /* Default data collection interval */ - pm->timeout_interval = 3.0; - vec_validate (pm->pm_fds, vec_len (vlib_mains) - 1); - vec_validate (pm->perf_event_pages, vec_len (vlib_mains) - 1); - vec_validate (pm->rdpmc_indices, vec_len (vlib_mains) - 1); + pm->timeout_interval = 2.0; /* seconds */ + vec_validate (pm->pm_fds, 1); + vec_validate (pm->pm_fds[0], vec_len (vlib_mains) - 1); + vec_validate (pm->pm_fds[1], vec_len (vlib_mains) - 1); + vec_validate (pm->perf_event_pages, 1); + vec_validate (pm->perf_event_pages[0], vec_len (vlib_mains) - 1); + vec_validate (pm->perf_event_pages[1], vec_len (vlib_mains) - 1); + vec_validate (pm->rdpmc_indices, 1); + vec_validate (pm->rdpmc_indices[0], vec_len (vlib_mains) - 1); + vec_validate (pm->rdpmc_indices[1], vec_len (vlib_mains) - 1); pm->page_size = getpagesize (); ht = pm->perfmon_table = 0; @@ -297,10 +303,12 @@ set_pmc_command_fn (vlib_main_t * vm, perfmon_main_t *pm = &perfmon_main; unformat_input_t _line_input, *line_input = &_line_input; perfmon_event_config_t ec; + f64 delay; u32 timeout_seconds; u32 deadman; - vec_reset_length (pm->events_to_collect); + vec_reset_length (pm->single_events_to_collect); + vec_reset_length (pm->paired_events_to_collect); pm->ipc_event_index = ~0; pm->mispredict_event_index = ~0; @@ -316,28 +324,28 @@ set_pmc_command_fn (vlib_main_t * vm, ec.name = "instructions"; ec.pe_type = PERF_TYPE_HARDWARE; ec.pe_config = PERF_COUNT_HW_INSTRUCTIONS; - pm->ipc_event_index = vec_len (pm->events_to_collect); - vec_add1 (pm->events_to_collect, ec); + pm->ipc_event_index = vec_len (pm->paired_events_to_collect); + vec_add1 (pm->paired_events_to_collect, ec); ec.name = "cpu-cycles"; ec.pe_type = PERF_TYPE_HARDWARE; ec.pe_config = PERF_COUNT_HW_CPU_CYCLES; - vec_add1 (pm->events_to_collect, ec); + vec_add1 (pm->paired_events_to_collect, ec); } else if (unformat (line_input, "branch-mispredict-rate")) { ec.name = "branch-misses"; ec.pe_type = PERF_TYPE_HARDWARE; ec.pe_config = PERF_COUNT_HW_BRANCH_MISSES; - pm->mispredict_event_index = vec_len (pm->events_to_collect); - vec_add1 (pm->events_to_collect, ec); + pm->mispredict_event_index = vec_len (pm->paired_events_to_collect); + vec_add1 (pm->paired_events_to_collect, ec); ec.name = "branches"; ec.pe_type = PERF_TYPE_HARDWARE; ec.pe_config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; - vec_add1 (pm->events_to_collect, ec); + vec_add1 (pm->paired_events_to_collect, ec); } else if (unformat (line_input, "%U", unformat_processor_event, pm, &ec)) { - vec_add1 (pm->events_to_collect, ec); + vec_add1 (pm->single_events_to_collect, ec); } #define _(type,event,str) \ else if (unformat (line_input, str)) \ @@ -345,7 +353,7 @@ set_pmc_command_fn (vlib_main_t * vm, ec.name = str; \ ec.pe_type = type; \ ec.pe_config = event; \ - vec_add1 (pm->events_to_collect, ec); \ + vec_add1 (pm->single_events_to_collect, ec); \ } foreach_perfmon_event #undef _ @@ -354,21 +362,33 @@ set_pmc_command_fn (vlib_main_t * vm, format_unformat_error, line_input); } - if (vec_len (pm->events_to_collect) == 0) + /* Stick paired events at the front of the (unified) list */ + if (vec_len (pm->paired_events_to_collect) > 0) + { + perfmon_event_config_t *tmp; + /* first 2n events are pairs... */ + vec_append (pm->paired_events_to_collect, pm->single_events_to_collect); + tmp = pm->single_events_to_collect; + pm->single_events_to_collect = pm->paired_events_to_collect; + pm->paired_events_to_collect = tmp; + } + + if (vec_len (pm->single_events_to_collect) == 0) return clib_error_return (0, "no events specified..."); + /* Figure out how long data collection will take */ + delay = + ((f64) vec_len (pm->single_events_to_collect)) * pm->timeout_interval; + delay /= 2.0; /* collect 2 stats at once */ + vlib_cli_output (vm, "Start collection for %d events, wait %.2f seconds", - vec_len (pm->events_to_collect), - (f64) (vec_len (pm->events_to_collect)) - * pm->timeout_interval); + vec_len (pm->single_events_to_collect), delay); vlib_process_signal_event (pm->vlib_main, perfmon_periodic_node.index, PERFMON_START, 0); /* Coarse-grained wait */ - vlib_process_suspend (vm, - ((f64) (vec_len (pm->events_to_collect) - * pm->timeout_interval))); + vlib_process_suspend (vm, delay); deadman = 0; /* Reasonable to guess that collection may not be quite done... */ @@ -438,7 +458,7 @@ format_capture (u8 * s, va_list * args) if (i == pm->ipc_event_index) { f64 ipc_rate; - ASSERT (i + 1 < vec_len (c->counter_names)); + ASSERT ((i + 1) < vec_len (c->counter_names)); if (c->counter_values[i + 1] > 0) ipc_rate = (f64) c->counter_values[i] diff --git a/src/plugins/perfmon/perfmon.h b/src/plugins/perfmon/perfmon.h index 47ee471d5fc..9663dae36d1 100644 --- a/src/plugins/perfmon/perfmon.h +++ b/src/plugins/perfmon/perfmon.h @@ -97,8 +97,11 @@ typedef struct perfmon_cpuid_and_table_t *perfmon_tables; uword *perfmon_table; - /* vector of events to collect */ - perfmon_event_config_t *events_to_collect; + /* vector of single events to collect */ + perfmon_event_config_t *single_events_to_collect; + + /* vector of paired events to collect */ + perfmon_event_config_t *paired_events_to_collect; /* Base indices of synthetic event tuples */ u32 ipc_event_index; @@ -109,13 +112,14 @@ typedef struct /* Current event (index) being collected */ u32 current_event; - u32 *rdpmc_indices; + int n_active; + u32 **rdpmc_indices; /* mmap base / size of (mapped) struct perf_event_mmap_page */ - u8 **perf_event_pages; + u8 ***perf_event_pages; u32 page_size; /* Current perf_event file descriptors, per thread */ - int *pm_fds; + int **pm_fds; /* Logging */ vlib_log_class_t log_class; diff --git a/src/plugins/perfmon/perfmon_periodic.c b/src/plugins/perfmon/perfmon_periodic.c index 4e7e2378320..ae20ac4c62f 100644 --- a/src/plugins/perfmon/perfmon_periodic.c +++ b/src/plugins/perfmon/perfmon_periodic.c @@ -31,22 +31,34 @@ perf_event_open (struct perf_event_attr *hw_event, pid_t pid, int cpu, return ret; } -static u64 -read_current_perf_counter (vlib_main_t * vm) +static void +read_current_perf_counters (vlib_main_t * vm, u64 * c0, u64 * c1) { - if (vm->perf_counter_id) - return clib_rdpmc (vm->perf_counter_id); - else + int i; + u64 *cc; + perfmon_main_t *pm = &perfmon_main; + uword my_thread_index = vm->thread_index; + + *c0 = *c1 = 0; + + for (i = 0; i < pm->n_active; i++) { - u64 sw_value; - if (read (vm->perf_counter_fd, &sw_value, sizeof (sw_value)) != - sizeof (sw_value)) + cc = (i == 0) ? c0 : c1; + if (pm->rdpmc_indices[i][my_thread_index] != ~0) + *cc = clib_rdpmc ((int) pm->rdpmc_indices[i][my_thread_index]); + else { - clib_unix_warning ("counter read failed, disable collection..."); - vm->vlib_node_runtime_perf_counter_cb = 0; - return 0ULL; + u64 sw_value; + if (read (pm->pm_fds[i][my_thread_index], &sw_value, + sizeof (sw_value)) != sizeof (sw_value)) + { + clib_unix_warning + ("counter read failed, disable collection..."); + vm->vlib_node_runtime_perf_counter_cb = 0; + return; + } + *cc = sw_value; } - return sw_value; } } @@ -80,9 +92,11 @@ clear_counters (perfmon_main_t * pm) for (i = 0; i < vec_len (nm->nodes); i++) { n = nm->nodes[i]; - n->stats_total.perf_counter_ticks = 0; + n->stats_total.perf_counter0_ticks = 0; + n->stats_total.perf_counter1_ticks = 0; n->stats_total.perf_counter_vectors = 0; - n->stats_last_clear.perf_counter_ticks = 0; + n->stats_last_clear.perf_counter0_ticks = 0; + n->stats_last_clear.perf_counter1_ticks = 0; n->stats_last_clear.perf_counter_vectors = 0; } } @@ -90,7 +104,7 @@ clear_counters (perfmon_main_t * pm) } static void -enable_current_event (perfmon_main_t * pm) +enable_current_events (perfmon_main_t * pm) { struct perf_event_attr pe; int fd; @@ -98,91 +112,108 @@ enable_current_event (perfmon_main_t * pm) perfmon_event_config_t *c; vlib_main_t *vm = vlib_get_main (); u32 my_thread_index = vm->thread_index; + u32 index; + int i, limit = 1; - c = vec_elt_at_index (pm->events_to_collect, pm->current_event); - - memset (&pe, 0, sizeof (struct perf_event_attr)); - pe.type = c->pe_type; - pe.size = sizeof (struct perf_event_attr); - pe.config = c->pe_config; - pe.disabled = 1; - pe.pinned = 1; - /* - * Note: excluding the kernel makes the - * (software) context-switch counter read 0... - */ - if (pe.type != PERF_TYPE_SOFTWARE) - { - /* Exclude kernel and hypervisor */ - pe.exclude_kernel = 1; - pe.exclude_hv = 1; - } + if ((pm->current_event + 1) < vec_len (pm->single_events_to_collect)) + limit = 2; - fd = perf_event_open (&pe, 0, -1, -1, 0); - if (fd == -1) + for (i = 0; i < limit; i++) { - clib_unix_warning ("event open: type %d config %d", c->pe_type, - c->pe_config); - return; - } + c = vec_elt_at_index (pm->single_events_to_collect, + pm->current_event + i); + + memset (&pe, 0, sizeof (struct perf_event_attr)); + pe.type = c->pe_type; + pe.size = sizeof (struct perf_event_attr); + pe.config = c->pe_config; + pe.disabled = 1; + pe.pinned = 1; + /* + * Note: excluding the kernel makes the + * (software) context-switch counter read 0... + */ + if (pe.type != PERF_TYPE_SOFTWARE) + { + /* Exclude kernel and hypervisor */ + pe.exclude_kernel = 1; + pe.exclude_hv = 1; + } - if (pe.type != PERF_TYPE_SOFTWARE) - { - p = mmap (0, pm->page_size, PROT_READ, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) + fd = perf_event_open (&pe, 0, -1, -1, 0); + if (fd == -1) { - clib_unix_warning ("mmap"); - close (fd); + clib_unix_warning ("event open: type %d config %d", c->pe_type, + c->pe_config); return; } - } - if (ioctl (fd, PERF_EVENT_IOC_RESET, 0) < 0) - clib_unix_warning ("reset ioctl"); + if (pe.type != PERF_TYPE_SOFTWARE) + { + p = mmap (0, pm->page_size, PROT_READ, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + { + clib_unix_warning ("mmap"); + close (fd); + return; + } + } + else + p = 0; + + /* + * Software event counters - and others not capable of being + * read via the "rdpmc" instruction - will be read + * by system calls. + */ + if (pe.type == PERF_TYPE_SOFTWARE || p->cap_user_rdpmc == 0) + index = ~0; + else + index = p->index - 1; - if (ioctl (fd, PERF_EVENT_IOC_ENABLE, 0) < 0) - clib_unix_warning ("enable ioctl"); + if (ioctl (fd, PERF_EVENT_IOC_RESET, 0) < 0) + clib_unix_warning ("reset ioctl"); - /* - * Software event counters - and others not capable of being - * read via the "rdpmc" instruction - will be read - * by system calls. - */ - if (pe.type == PERF_TYPE_SOFTWARE || p->cap_user_rdpmc == 0) - pm->rdpmc_indices[my_thread_index] = 0; - else /* use rdpmc instrs */ - pm->rdpmc_indices[my_thread_index] = p->index - 1; - pm->perf_event_pages[my_thread_index] = (void *) p; + if (ioctl (fd, PERF_EVENT_IOC_ENABLE, 0) < 0) + clib_unix_warning ("enable ioctl"); - pm->pm_fds[my_thread_index] = fd; + pm->rdpmc_indices[i][my_thread_index] = index; + pm->perf_event_pages[i][my_thread_index] = (void *) p; + pm->pm_fds[i][my_thread_index] = fd; + } + pm->n_active = i; /* Enable the main loop counter snapshot mechanism */ - vm->perf_counter_id = pm->rdpmc_indices[my_thread_index]; - vm->perf_counter_fd = fd; - vm->vlib_node_runtime_perf_counter_cb = read_current_perf_counter; + vm->vlib_node_runtime_perf_counter_cb = read_current_perf_counters; } static void -disable_event (perfmon_main_t * pm) +disable_events (perfmon_main_t * pm) { vlib_main_t *vm = vlib_get_main (); u32 my_thread_index = vm->thread_index; - - if (pm->pm_fds[my_thread_index] == 0) - return; + int i; /* Stop main loop collection */ vm->vlib_node_runtime_perf_counter_cb = 0; - if (ioctl (pm->pm_fds[my_thread_index], PERF_EVENT_IOC_DISABLE, 0) < 0) - clib_unix_warning ("disable ioctl"); + for (i = 0; i < pm->n_active; i++) + { + if (pm->pm_fds[i][my_thread_index] == 0) + continue; + + if (ioctl (pm->pm_fds[i][my_thread_index], PERF_EVENT_IOC_DISABLE, 0) < + 0) + clib_unix_warning ("disable ioctl"); - if (pm->perf_event_pages[my_thread_index]) - if (munmap (pm->perf_event_pages[my_thread_index], pm->page_size) < 0) - clib_unix_warning ("munmap"); + if (pm->perf_event_pages[i][my_thread_index]) + if (munmap (pm->perf_event_pages[i][my_thread_index], + pm->page_size) < 0) + clib_unix_warning ("munmap"); - (void) close (pm->pm_fds[my_thread_index]); - pm->pm_fds[my_thread_index] = 0; + (void) close (pm->pm_fds[i][my_thread_index]); + pm->pm_fds[i][my_thread_index] = 0; + } } static void @@ -190,7 +221,7 @@ worker_thread_start_event (vlib_main_t * vm) { perfmon_main_t *pm = &perfmon_main; - enable_current_event (pm); + enable_current_events (pm); vm->worker_thread_main_loop_callback = 0; } @@ -198,7 +229,7 @@ static void worker_thread_stop_event (vlib_main_t * vm) { perfmon_main_t *pm = &perfmon_main; - disable_event (pm); + disable_events (pm); vm->worker_thread_main_loop_callback = 0; } @@ -207,7 +238,7 @@ start_event (perfmon_main_t * pm, f64 now, uword event_data) { int i; pm->current_event = 0; - if (vec_len (pm->events_to_collect) == 0) + if (vec_len (pm->single_events_to_collect) == 0) { pm->state = PERFMON_STATE_OFF; return; @@ -216,7 +247,7 @@ start_event (perfmon_main_t * pm, f64 now, uword event_data) clear_counters (pm); /* Start collection on this thread */ - enable_current_event (pm); + enable_current_events (pm); /* And also on worker threads */ for (i = 1; i < vec_len (vlib_mains); i++) @@ -231,7 +262,7 @@ start_event (perfmon_main_t * pm, f64 now, uword event_data) void scrape_and_clear_counters (perfmon_main_t * pm) { - int i, j; + int i, j, k; vlib_main_t *vm = pm->vlib_main; vlib_main_t *stat_vm; vlib_node_main_t *nm; @@ -242,7 +273,6 @@ scrape_and_clear_counters (perfmon_main_t * pm) perfmon_event_config_t *current_event; uword *p; u8 *counter_name; - u64 counter_value; u64 vectors_this_counter; /* snapshoot the nodes, including pm counters */ @@ -272,17 +302,17 @@ scrape_and_clear_counters (perfmon_main_t * pm) n = nm->nodes[i]; nodes[i] = clib_mem_alloc (sizeof (*n)); clib_memcpy_fast (nodes[i], n, sizeof (*n)); - n->stats_total.perf_counter_ticks = 0; + n->stats_total.perf_counter0_ticks = 0; + n->stats_total.perf_counter1_ticks = 0; n->stats_total.perf_counter_vectors = 0; - n->stats_last_clear.perf_counter_ticks = 0; + n->stats_last_clear.perf_counter0_ticks = 0; + n->stats_last_clear.perf_counter1_ticks = 0; n->stats_last_clear.perf_counter_vectors = 0; } } vlib_worker_thread_barrier_release (vm); - current_event = pm->events_to_collect + pm->current_event; - for (j = 0; j < vec_len (vlib_mains); j++) { stat_vm = vlib_mains[j]; @@ -296,38 +326,69 @@ scrape_and_clear_counters (perfmon_main_t * pm) u8 *capture_name; n = nodes[i]; - if (n->stats_total.perf_counter_ticks == 0) - { - clib_mem_free (n); - continue; - } - - capture_name = format (0, "t%d-%v%c", j, n->name, 0); - p = hash_get_mem (pm->capture_by_thread_and_node_name, - capture_name); + if (n->stats_total.perf_counter0_ticks == 0 && + n->stats_total.perf_counter1_ticks == 0) + goto skip_this_node; - if (p == 0) + for (k = 0; k < 2; k++) { - pool_get (pm->capture_pool, c); - memset (c, 0, sizeof (*c)); - c->thread_and_node_name = capture_name; - hash_set_mem (pm->capture_by_thread_and_node_name, - capture_name, c - pm->capture_pool); + u64 counter_value, counter_last_clear; + + /* + * We collect 2 counters at once, except for the + * last counter when the user asks for an odd number of + * counters + */ + if ((pm->current_event + k) + >= vec_len (pm->single_events_to_collect)) + break; + + if (k == 0) + { + counter_value = n->stats_total.perf_counter0_ticks; + counter_last_clear = + n->stats_last_clear.perf_counter0_ticks; + } + else + { + counter_value = n->stats_total.perf_counter1_ticks; + counter_last_clear = + n->stats_last_clear.perf_counter1_ticks; + } + + capture_name = format (0, "t%d-%v%c", j, n->name, 0); + + p = hash_get_mem (pm->capture_by_thread_and_node_name, + capture_name); + + if (p == 0) + { + pool_get (pm->capture_pool, c); + memset (c, 0, sizeof (*c)); + c->thread_and_node_name = capture_name; + hash_set_mem (pm->capture_by_thread_and_node_name, + capture_name, c - pm->capture_pool); + } + else + { + c = pool_elt_at_index (pm->capture_pool, p[0]); + vec_free (capture_name); + } + + /* Snapshoot counters, etc. into the capture */ + current_event = pm->single_events_to_collect + + pm->current_event + k; + counter_name = (u8 *) current_event->name; + vectors_this_counter = n->stats_total.perf_counter_vectors - + n->stats_last_clear.perf_counter_vectors; + + vec_add1 (c->counter_names, counter_name); + vec_add1 (c->counter_values, + counter_value - counter_last_clear); + vec_add1 (c->vectors_this_counter, vectors_this_counter); } - else - c = pool_elt_at_index (pm->capture_pool, p[0]); - - /* Snapshoot counters, etc. into the capture */ - counter_name = (u8 *) current_event->name; - counter_value = n->stats_total.perf_counter_ticks - - n->stats_last_clear.perf_counter_ticks; - vectors_this_counter = n->stats_total.perf_counter_vectors - - n->stats_last_clear.perf_counter_vectors; - - vec_add1 (c->counter_names, counter_name); - vec_add1 (c->counter_values, counter_value); - vec_add1 (c->vectors_this_counter, vectors_this_counter); + skip_this_node: clib_mem_free (n); } vec_free (nodes); @@ -339,7 +400,7 @@ static void handle_timeout (perfmon_main_t * pm, f64 now) { int i; - disable_event (pm); + disable_events (pm); /* And also on worker threads */ for (i = 1; i < vec_len (vlib_mains); i++) @@ -354,14 +415,14 @@ handle_timeout (perfmon_main_t * pm, f64 now) if (i > 1) vlib_process_suspend (pm->vlib_main, 1e-3); scrape_and_clear_counters (pm); - pm->current_event++; - if (pm->current_event >= vec_len (pm->events_to_collect)) + pm->current_event += pm->n_active; + if (pm->current_event >= vec_len (pm->single_events_to_collect)) { pm->current_event = 0; pm->state = PERFMON_STATE_OFF; return; } - enable_current_event (pm); + enable_current_events (pm); /* And also on worker threads */ for (i = 1; i < vec_len (vlib_mains); i++) |