Files
vpp/src/plugins/perfmon/perfmon.h
Zachary Leaf 268d7be66b perfmon: enable perfmon plugin for Arm
This patch enables statistics from the Arm PMUv3 through the perfmon
plugin.

In comparison to using the Linux "perf" tool, it allows obtaining
direct, per node level statistics (rather than per thread). By accessing
the PMU counter registers directly from userspace, we can avoid the
overhead of using a read() system call and get more accurate and fine
grained statistics about the running of individual nodes.

A demo of perfmon on Arm can be found at:
https://asciinema.org/a/egVNN1OF7JEKHYmfl5bpDYxfF

*Important Note*
Perfmon on Arm is dependent on and works only on Linux kernel versions
of v5.17+ as this is when userspace access to Arm perf counters was
included.

On most Arm systems, a maximum of 7 PMU events can be configured at once
- (6x PMU events + 1x CPU_CYCLE counter). If some perf counters are in
use elsewhere by other applications, and there are insufficient counters
remaining to open the bundle, the perf_event_open call will fail
(provided the events are grouped with the group_fd param, which perfmon
currently utilises).

See arm/events.h for a list of PMUv3 events available, although it is
implementation defined whether most events are implemented or not. Only
a small set of 7 events is required to be implemented in Armv8.0, with
some additional events required in later versions. As such, depending on
the implementation, some statistics may not be available. See Arm
Architecture Reference Manual for Armv8-A, D7.10.2 "The PMU event number
space and common events" for more information.

arm/events.c:arm_init() gets information from the sysfs about what
events are implemented on a particular CPU at runtime. Arm's
implementation of the perfmon source callback .bundle_support uses this
information to disable unsupported events in a bundle, or in the case
no events are supported, disable the entire bundle.

Where a particular event in a bundle is not implemented, the statistic
for that event is shown as '-' in the 'show perfmon statistics' cli
output, by disabling the column.

There is additional code in perfmon.c to only open events which are
marked as implemented. Since we're only opening and reading events that
are implemented, some extra logic is required in cli.c to re-align
either perfmon_node_stats_t or perfmon_reading_t with the column
headings configured in each bundle, taking into account disabled
columns.

Userspace access to perf counters is disabled by default, and needs to
be enabled with 'sudo sysctl kernel/perf_user_access=1'.

There is a check built into the Arm event source init function
(arm/events.c:arm_init) to check that userspace reading of perf counters
is enabled in the /proc/sys/kernel/perf_user_access file.

If the above file does not exist, it means the kernel version is
unsupported. Users without a supported kernel will see a warning
message, and no Arm bundles will be registered to use in perfmon.

Enabling/using plugin:
  - include the following in startup.conf:
    - plugins { plugin perfmon_plugin.so { enable }
  - 'show perfmon bundle [verbose]' - show available statistics bundles
  - 'perfmon start bundle <bundle-name>' - enable and start logging
  - 'perfmon stop' - stop logging
  - 'show perfmon statistics' - show output

For a general guide on using and understanding Arm PMUv3 events, see
https://community.arm.com/arm-community-blogs/b/tools-software-ides-blog/posts/arm-neoverse-n1-performance-analysis-methodology

Type: feature
Signed-off-by: Zachary Leaf <zachary.leaf@arm.com>
Tested-by: Jieqiang Wang <jieqiang.wang@arm.com>
Change-Id: I0620fe5b1bbe78842dfb1d0b6a060bb99e777651
2022-07-12 15:29:23 +00:00

285 lines
8.2 KiB
C

/*
* Copyright (c) 2020 Cisco and/or its affiliates.
* 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 __perfmon_perfmon_h
#define __perfmon_perfmon_h
#include <linux/perf_event.h>
#include <vppinfra/clib.h>
#include <vppinfra/format.h>
#include <vppinfra/error.h>
#include <vppinfra/cpu.h>
#include <vlib/vlib.h>
#if defined(__x86_64__)
#define PERF_MAX_EVENTS 12 /* 4 fixed and 8 programable on ICX */
#elif defined(__aarch64__)
#define PERF_MAX_EVENTS 7 /* 6 events + 1 CPU cycle counter */
#endif
typedef enum
{
PERFMON_EVENT_TYPE_GENERAL,
PERFMON_EVENT_TYPE_FIXED,
PERFMON_EVENT_TYPE_PSEUDO,
PERFMON_EVENT_TYPE_MAX,
} perfmon_event_type_t;
typedef enum
{
PERFMON_BUNDLE_TYPE_UNKNOWN,
PERFMON_BUNDLE_TYPE_NODE,
PERFMON_BUNDLE_TYPE_THREAD,
PERFMON_BUNDLE_TYPE_SYSTEM,
PERFMON_BUNDLE_TYPE_MAX,
PERFMON_BUNDLE_TYPE_NODE_OR_THREAD,
} perfmon_bundle_type_t;
#define foreach_perfmon_bundle_type \
_ (PERFMON_BUNDLE_TYPE_UNKNOWN, "not supported") \
_ (PERFMON_BUNDLE_TYPE_NODE, "node") \
_ (PERFMON_BUNDLE_TYPE_THREAD, "thread") \
_ (PERFMON_BUNDLE_TYPE_SYSTEM, "system")
typedef enum
{
#define _(e, str) e##_FLAG = 1 << e,
foreach_perfmon_bundle_type
#undef _
} perfmon_bundle_type_flag_t;
typedef struct
{
u32 type_from_instance : 1;
u32 exclude_kernel : 1;
u32 config1 : 2;
u32 implemented : 1;
union
{
u32 type;
u32 instance_type;
};
u64 config;
char *name;
char *description;
} perfmon_event_t;
typedef struct
{
u32 type;
int cpu;
pid_t pid;
char *name;
} perfmon_instance_t;
typedef struct
{
char *name;
perfmon_instance_t *instances;
} perfmon_instance_type_t;
struct perfmon_source;
typedef struct perfmon_bundle perfmon_bundle_t;
typedef clib_error_t *(perfmon_source_init_fn_t) (vlib_main_t *vm,
struct perfmon_source *);
typedef perfmon_event_type_t (perfmon_source_get_event_type) (u32 event);
typedef u8 (perfmon_source_bundle_support_t) (perfmon_bundle_t *);
typedef clib_error_t *(perfmon_source_config_dispatch_wrapper_t) (
perfmon_bundle_t *b, vlib_node_function_t **dispatch_wrapper);
typedef struct perfmon_source
{
char *name;
char *description;
struct perfmon_source *next;
perfmon_event_t *events;
u32 n_events;
perfmon_instance_type_t *instances_by_type;
format_function_t *format_config;
perfmon_source_get_event_type *get_event_type;
perfmon_source_init_fn_t *init_fn;
perfmon_source_bundle_support_t *bundle_support;
perfmon_source_config_dispatch_wrapper_t *config_dispatch_wrapper;
} perfmon_source_t;
typedef clib_error_t *(perfmon_bundle_init_fn_t) (vlib_main_t *vm,
struct perfmon_bundle *);
typedef struct
{
clib_cpu_supports_func_t cpu_supports;
perfmon_bundle_type_t bundle_type;
} perfmon_cpu_supports_t;
typedef struct perfmon_bundle
{
char *name;
char *description;
char *source;
char *footer;
union
{
perfmon_bundle_type_flag_t type_flags;
perfmon_bundle_type_t type;
};
perfmon_bundle_type_t active_type;
u32 events[PERF_MAX_EVENTS];
u32 n_events;
u32 n_columns;
uword *event_disabled;
uword *column_disabled;
u8 *column_events;
u16 preserve_samples;
perfmon_cpu_supports_t *cpu_supports;
u32 n_cpu_supports;
perfmon_bundle_init_fn_t *init_fn;
char **column_headers;
format_function_t *format_fn;
/* do not set manually */
perfmon_source_t *src;
struct perfmon_bundle *next;
} perfmon_bundle_t;
typedef struct
{
u64 nr;
u64 time_enabled;
u64 time_running;
u64 value[PERF_MAX_EVENTS];
} perfmon_reading_t;
typedef struct
{
CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
u64 n_calls;
u64 n_packets;
union
{
struct
{
u64 value[PERF_MAX_EVENTS];
} t[2];
u64 value[PERF_MAX_EVENTS * 2];
};
} perfmon_node_stats_t;
typedef struct
{
u8 n_events;
u16 n_nodes;
perfmon_node_stats_t *node_stats;
perfmon_bundle_t *bundle;
u32 indexes[PERF_MAX_EVENTS];
u16 preserve_samples;
struct perf_event_mmap_page *mmap_pages[PERF_MAX_EVENTS];
} perfmon_thread_runtime_t;
typedef struct
{
perfmon_thread_runtime_t *thread_runtimes;
perfmon_bundle_t *bundles;
uword *bundle_by_name;
perfmon_source_t *sources;
uword *source_by_name;
perfmon_bundle_t *active_bundle;
int is_running;
f64 sample_time;
int *group_fds;
int *fds_to_close;
perfmon_instance_type_t *default_instance_type;
perfmon_instance_type_t *active_instance_type;
} perfmon_main_t;
extern perfmon_main_t perfmon_main;
#define PERFMON_BUNDLE_TYPE_TO_FLAGS(type) \
({ \
uword rtype = 0; \
if (type == PERFMON_BUNDLE_TYPE_NODE_OR_THREAD) \
rtype = \
1 << PERFMON_BUNDLE_TYPE_THREAD | 1 << PERFMON_BUNDLE_TYPE_NODE; \
else \
rtype = 1 << type; \
rtype; \
})
always_inline uword
perfmon_cpu_update_bundle_type (perfmon_bundle_t *b)
{
perfmon_cpu_supports_t *supports = b->cpu_supports;
uword type = 0;
/* either supports or b->type should be set, but not both */
ASSERT (!!supports ^ !!b->type);
/* if nothing specific for this bundle, go with the defaults */
if (!supports)
type = PERFMON_BUNDLE_TYPE_TO_FLAGS (b->type);
else
{
/* more than one type may be supported by a given bundle */
for (int i = 0; i < b->n_cpu_supports; ++i)
if (supports[i].cpu_supports ())
type |= PERFMON_BUNDLE_TYPE_TO_FLAGS (supports[i].bundle_type);
}
return type;
}
#undef PERFMON_BUNDLE_TYPE_TO_FLAGS
#define PERFMON_REGISTER_SOURCE(x) \
perfmon_source_t __perfmon_source_##x; \
static void __clib_constructor __perfmon_source_registration_##x (void) \
{ \
perfmon_main_t *pm = &perfmon_main; \
__perfmon_source_##x.next = pm->sources; \
pm->sources = &__perfmon_source_##x; \
} \
perfmon_source_t __perfmon_source_##x
#define PERFMON_REGISTER_BUNDLE(x) \
perfmon_bundle_t __perfmon_bundle_##x; \
static void __clib_constructor __perfmon_bundle_registration_##x (void) \
{ \
perfmon_main_t *pm = &perfmon_main; \
__perfmon_bundle_##x.next = pm->bundles; \
__perfmon_bundle_##x.type_flags = \
perfmon_cpu_update_bundle_type (&__perfmon_bundle_##x); \
pm->bundles = &__perfmon_bundle_##x; \
} \
perfmon_bundle_t __perfmon_bundle_##x
void perfmon_reset (vlib_main_t *vm);
clib_error_t *perfmon_start (vlib_main_t *vm, perfmon_bundle_t *);
clib_error_t *perfmon_stop (vlib_main_t *vm);
#define PERFMON_STRINGS(...) \
(char *[]) { __VA_ARGS__, 0 }
#define PERFMON_COLUMN_EVENTS(...) \
(u8[]) { __VA_ARGS__ }
#endif