From d7b1cbad8b10457c53748cfad163d28c67ef6de3 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Thu, 28 Apr 2022 16:09:33 +0200 Subject: [PATCH] cpumf/pai: Add Processor Activity Instrumentation tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Processor Activity Instrumentation facility (PAI) uses the perf_event_open system call to report data and the crypto counter values. The data can be inspected with the perf tool in a very generic way using raw data report and a hex dump utility. The pai tool also uses the perf_event_open system call to record the data, but uses specialized event attributes for recording detailed information such as the recording of fork, exit, exec system call events and context switch events. Cryptography counter values are appended as raw data attachments. The pai command can be used for recording and reporting. Also a list of CPUs can be specified. The recording ring buffers are created per CPU and are named paicrypto.XXX where XXX stands for the CPU number. The numbering scheme has leading zeros, such as 007 for CPU number 7. For invocation and possible command line options see the man page. Signed-off-by: Thomas Richter Reviewed-by: Jan Höppner Signed-off-by: Jan Höppner --- .gitignore | 1 + cpumf/Makefile | 7 +- cpumf/man/pai.1 | 224 ++++++++++ cpumf/pai.c | 1091 +++++++++++++++++++++++++++++++++++++++++++++++ cpumf/pai.h | 105 +++++ 5 files changed, 1425 insertions(+), 3 deletions(-) create mode 100644 cpumf/man/pai.1 create mode 100644 cpumf/pai.c create mode 100644 cpumf/pai.h diff --git a/.gitignore b/.gitignore index cdd2eaf4..fbead96c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ cpacfstats/cpacfstatsd cpumf/chcpumf cpumf/lscpumf cpumf/lshwc +cpumf/pai cpuplugd/cpuplugd dasdfmt/dasdfmt dasdinfo/dasdinfo diff --git a/cpumf/Makefile b/cpumf/Makefile index 9f3ebee9..e61cfe1c 100644 --- a/cpumf/Makefile +++ b/cpumf/Makefile @@ -1,15 +1,16 @@ include ../common.mak -BIN_FILES = lscpumf chcpumf lshwc -MAN_FILES = lscpumf.1 chcpumf.8 lshwc.1 +BIN_FILES = lscpumf chcpumf lshwc pai +MAN_FILES = lscpumf.1 chcpumf.8 lshwc.1 pai.1 all: $(BIN_FILES) -libs = $(rootdir)/libutil/libutil.a $(rootdir)/libcpumf/libcpumf.a +libs = $(rootdir)/libcpumf/libcpumf.a $(rootdir)/libutil/libutil.a lscpumf: lscpumf.o $(libs) chcpumf: chcpumf.o $(libs) lshwc: lshwc.o $(libs) +pai: pai.o $(libs) install: all install-man $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 diff --git a/cpumf/man/pai.1 b/cpumf/man/pai.1 new file mode 100644 index 00000000..2218b293 --- /dev/null +++ b/cpumf/man/pai.1 @@ -0,0 +1,224 @@ +.\" pai.1 +.\" +.\" +.\" Copyright IBM Corp. 2022 +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" ---------------------------------------------------------------------- +.ds c \fBpai\fP +. +.TH \*c "1" "May 2022" "s390-tools" "PAI Management Programs" +. +.SH NAME +\*c \- record and report Processor Activity Instrumentation (PAI) counters +. +.SH SYNOPSIS +\*c +.RB [ \-V ][ \-m | \-\-mapsize +.IR size ] +.RB [ \-i | \-\-interval +.IR ms ] +.BR \-c | \-\-crypto [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ] +.br +\*c +.RB [ \-V ][ \-H | \-\-humantime ][ \-S | \-\-summary "] " \-r | \-\-report " [" \fIfiles\fP ] +.br +\*c +.BR \-h | \-\-help +.br +\*c +.BR \-v | \-\-version +. +. +.SH DESCRIPTION +\*c +counters tally calls for specific CPU instructions. +The \*c command records PAI counters in a ring buffer. +\*c can record counter data for all CPUs or for selected CPUs. +The main command options are +.B \-c +for recording cryptographic CPU instructions and +.B \-r +for reporting. +If both options are omitted, option +.B \-r +is assumed and a message is printed. +Recording stores data, by CPU, in files +.I paicrypto., +where specifies the CPU number. +The files are created in the working directory, +existing files are overwritten. +Reporting evaluates files that are created by recording. + +.SH OPTIONS +.TP +.BR \-c ", " \-\-crypto "\fR[\fIcpulist\fR][:\fIdata\fR]" +Records data for all (default) or a specified list of CPUs. +The CPU list is a comma-separated list of CPU numbers and ranges. +In a range, a hyphen separates the first CPU number +from the last CPU number. +By default \*c lists all CPUs. +.RS +The optional data specification +follows the colon +and determines additional collection of data. +The specification consists of alphabetic +characters that can be upper or lower case: +.IP c|C +Include task rename system calls +.B exec +and +.BR prctl . +.IP f|F +Include task creation and deletion system calls +.B fork +and +.BR exit . +.IP s|S +Include context switch records created by the kernel scheduler. +.IP k|K +Include only PAI cryptographic counters changed during system call +execution. +.IP u|U +Include only PAI cryptographic counters changed during user space +execution. +.RE +. +.TP +.BR \-r ", " \-\-report +Generates a report from the specified files. +Files is a list of blank-separated file names. +If no files are specified, +uses all files named +.I paicrypto., +in the working directory, where +.I +is a CPU number that +identifies the CPU for which the data was recorded. +. +.TP +.BR \-h ", " \-\-help +Displays help information, then exits. +. +.TP +.BR \-v ", " \-\-version +Displays version information, then exits. +. +.TP +.BR \-V ", " \-\-verbose +Displays the following information during reporting: +the file name and the hexadecimal offset for each sample header. +Displays the following information during recording: +the read position in the ring buffer, +the file name, and the hexadecimal offset for each sample header. +. +.TP +.BR \-H ", " \-\-humantime +Changes the time stamp format for sample entries from a hexadecimal +number to a human readable . format. +The time stamp specifies the elapsed time since Linux was booted. +. +.TP +.BR \-S ", " \-\-summary +Prints a summary of counter names and +counter numbers with non-zero values. +The summary is the last line in the command output. +It shows the sum of the counter values of all processed files. +. +.TP +.BR \-i ", " \-\-interval "\ ms" +Specifies the waiting time, +in milliseconds, +between ring buffer read operations during recording. +The default is 1000 milliseconds. +Argument +.B loops +is an integer that specifies the number of +read operations during recording. The default is 1. +. +.TP +.BR \-m ", " \-\-mapsize "\ size" +Specifies the size of the ring buffer +that holds the collected data. +The value specifies the number of 4 KB pages +to be allocated and must be a power of 2. +The default size is 512 pages. +The ring buffer is created with the +.IR mmap (2) +system call. +. +.SH ARGUMENT +The command line options determine how command line +arguments are interpreted. +For option +.B \-c +to start recording, the argument specifies +the number of read operations. +If omitted, the default is one. +For option +.B \-r +to start reporting, the argument specifies +the file names to be read. +If omitted all files in the current directory +with file name +.I paicryto. +are read. +.SH "Concurrency with perf tool" +The following concurrency restrictions apply +for \*c and the +.B perf +tool. +Both tools use the +.IR perf_event_open () +system call and use the same device driver. +The device driver supports limited concurrency: +.IP Counting: +Counting pai events can run in parallel. +.IP Sampling: +Only one sampling event pai_crypto/CRYPTO_ALL can be active at any one time. +If a sampling event is active, no pai counting event can be active. +Both tools stop with an error message if they detect a collision +reported by the device driver. +.SH "EXAMPLES" +This example collects cryptographic counter on CPU 0. +The program runs for 10 seconds +(10 intervals of 1000 milliseconds). +.sp 1 +.nf +.ft CW +# \*c -c0 10 +.ft R +.fi +.sp 1 +This example displays the data +that is collected in the first example. +Each data line shows the time stamp in jiffies, +followed by the CPU number, +the event number, +process and thread identifier (pid/tid) separated by slash (/), +and a comma-separated list of counter number, colon (:), +and the counter value n hexadecimal notation. +.sp 1 +.nf +.ft CW +# \*c -r +0x62a668f2fa 0 event 4096 sample pid 4956/4956 9:0xa7,73:0x8,74:0x18 +0x6319c75653 0 event 4096 sample pid 4972/4972 32:0x1 +0x6319e2ddee 0 event 4096 sample pid 4972/4972 32:0x1 +0x631d3e44f5 0 event 4096 sample pid 4972/4972 32:0x2 +0x631d4cfc2e 0 event 4096 sample pid 4972/4972 32:0x3 +0x631d529fdd 0 event 4096 sample pid 4972/4972 32:0x2 +0x631d84cfa0 0 event 4096 sample pid 4972/4972 73:0x1 +0x636e9826bc 0 event 4096 sample pid 4984/4984 9:0xa7,73:0x8,74:0x18 +0x636f81a137 0 event 4096 sample pid 4984/4984 9:0x2,74:0x7 +0x6378026e54 0 event 4096 sample pid 4984/4984 73:0x2 +0x637bcdc8da 0 event 4096 sample pid 4984/4984 73:0x2 +0x637bd426cd 0 event 4096 sample pid 4984/4984 73:0x6 +0x637c503384 0 event 4096 sample pid 4984/4984 73:0x2 +0x64991d83ba 0 event 4096 sample pid 5026/5026 9:0xb0,73:0x8,74:0x18 +# +.ft R +.fi +.sp 1 +.SH "SEE ALSO" +perf(1) diff --git a/cpumf/pai.c b/cpumf/pai.c new file mode 100644 index 00000000..d16ee707 --- /dev/null +++ b/cpumf/pai.c @@ -0,0 +1,1091 @@ +/* + * pai - Extract CPU Processor Activity Instrumentation (PAI) facility data. + * + * Copyright IBM Corp. 2022 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/util_base.h" +#include "lib/util_libc.h" +#include "lib/util_list.h" +#include "lib/util_opt.h" +#include "lib/util_path.h" +#include "lib/util_prg.h" +#include "lib/util_scandir.h" +#include "lib/libcpumf.h" +#include "pai.h" + +#define S390_EVT_PAI_CRYPTO 0x1000 + +/* Default values for select() timeout: 1 second */ +static unsigned long read_interval = 1000; +/* Size of mapped perf event ring buffer in 4KB pages. + * It must be power of two and >= 4 which is the + * absolute minimum required for file descriptors returned by the + * perf_event_open system call. Default to 512 pages. + */ +static unsigned long mapsize = 512; +static cpu_set_t cpu_online_mask; +static int verbose, humantime; +static struct util_list list_pai_event; +static struct util_list list_pmu_event; + +/* System call to perf_event_open(2) */ +static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, + int cpu, int group_fd, unsigned long flags) +{ + return syscall(__NR_perf_event_open, hw_event, pid, cpu, + group_fd, flags); +} + +static void ev_dealloc(void) +{ + struct pai_event *next, *p; + + util_list_iterate_safe(&list_pai_event, p, next) { + util_list_remove(&list_pai_event, p); + free(p); + } +} + +static void ev_merge(struct pai_event *new) +{ + struct pai_event *p; + + util_list_iterate(&list_pai_event, p) { + if (p->cpu == new->cpu && p->attr.config == new->attr.config) { + warnx("dropped duplicate event %#llx for cpu %d", + new->attr.config, new->cpu); + free(new); + return; + } + } + util_list_add_head(&list_pai_event, new); +} + +static void ev_alloc(int enr, int cpu, int flags) +{ + struct pai_event *event = calloc(1, sizeof(*event)); + unsigned short as = (S390_EVTATTR_USERSPACE | + S390_EVTATTR_KERNELSPACE); + + if (!event) + errx(EXIT_FAILURE, "Not enough memory to allocate event"); + if (cpu > CPU_SETSIZE || !CPU_ISSET(cpu, &cpu_online_mask)) + errx(EXIT_FAILURE, "Invalid CPU %d specified", cpu); + event->file_fd = -1; + event->fd = -1; + event->flags = flags; + event->attr.size = sizeof(event->attr); + event->attr.config = enr; + event->attr.type = libcpumf_pmutype(S390_SYSFS_PAI_CRYPTO); + event->attr.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_CPU | + PERF_SAMPLE_TIME | PERF_SAMPLE_RAW; + event->attr.disabled = 1; + event->attr.sample_period = 1; + event->attr.sample_id_all = 1; + event->attr.watermark = 1; /* Wakeup on every event */ + event->attr.wakeup_watermark = 1; + if (flags & S390_EVTATTR_CTX_SWITCH) + event->attr.context_switch = 1; + if (flags & S390_EVTATTR_FORK) + event->attr.task = 1; + if (flags & S390_EVTATTR_COMM) { + event->attr.comm = 1; + event->attr.comm_exec = 1; + } + if ((flags & as) != as) { + /* User space or kernel space selector */ + if (flags & S390_EVTATTR_USERSPACE) + event->attr.exclude_kernel = 1; + if (flags & S390_EVTATTR_KERNELSPACE) + event->attr.exclude_user = 1; + } + event->cpu = cpu; + event->map_size = mapsize; + snprintf(event->file_name, sizeof(event->file_name), + "paicrypto.%03d", cpu); + ev_merge(event); +} + +static void ev_deinstall(void) +{ + struct pai_event *p; + + util_list_iterate(&list_pai_event, p) { + if (p->map_addr) + munmap(p->map_addr, PAGE_SIZE + p->map_size * PAGE_SIZE); + p->map_addr = NULL; + if (p->fd >= 0) + close(p->fd); + p->fd = -1; + if (p->file_fd >= 0) + close(p->file_fd); + p->file_fd = -1; + } +} + +static void ev_enable(void) +{ + struct pai_event *p; + int rc; + + util_list_iterate(&list_pai_event, p) { + rc = ioctl(p->fd, PERF_EVENT_IOC_RESET, 0); + rc |= ioctl(p->fd, PERF_EVENT_IOC_ENABLE, 0); + if (rc) + err(EXIT_FAILURE, "ioctl error for enable event %lld CPU %d", + p->attr.config, p->cpu); + } +} + +static void ev_disable(void) +{ + struct pai_event *p; + int rc; + + util_list_iterate(&list_pai_event, p) { + rc = ioctl(p->fd, PERF_EVENT_IOC_DISABLE, 0); + if (rc) + err(EXIT_FAILURE, "ioctl error for disable event %lld CPU %d", + p->attr.config, p->cpu); + } +} + +/* Map one event's ring buffer and create an output file for it. */ +static void ev_mapevent(struct pai_event *p) +{ + p->map_addr = mmap(NULL, PAGE_SIZE + p->map_size * PAGE_SIZE, + PROT_READ | PROT_WRITE, MAP_SHARED, p->fd, 0); + if (p->map_addr == MAP_FAILED) + err(EXIT_FAILURE, "mmap error for event %lld CPU %d", + p->attr.config, p->cpu); + + p->file_fd = open(p->file_name, + O_WRONLY | O_APPEND | O_CREAT | O_TRUNC, 0600); + if (p->file_fd < 0) + err(EXIT_FAILURE, "file error for event %lld CPU %d", + p->attr.config, p->cpu); + if (write(p->file_fd, &p->attr, sizeof(p->attr)) == -1) + err(EXIT_FAILURE, "write error for event %lld CPU %d", + p->attr.config, p->cpu); +} + +/* Install one event using perf_event_open system call. */ +static void ev_install(int group) +{ + unsigned long flags = 0; + int group_fd = -1, rc; + struct pai_event *p; + + util_list_iterate(&list_pai_event, p) { + if (group_fd == -1) { + p->attr.watermark = 1; + p->attr.wakeup_watermark = PAGE_SIZE * + p->map_size / 2; + } + + rc = perf_event_open(&p->attr, -1, p->cpu, group_fd, flags); + if (rc == -1) + err(EXIT_FAILURE, "perf_event_open error for event %lld CPU %d", + p->attr.config, p->cpu); + p->fd = rc; + if (group && group_fd == -1) { + flags = PERF_FLAG_FD_OUTPUT | PERF_FLAG_FD_NO_GROUP; + group_fd = rc; + ev_mapevent(p); + } else if (!group) { + ev_mapevent(p); + } + } +} + +/* Return pointer to event for a given perf event file descriptor returned + * by the perf_event_open system call. + */ +static struct pai_event *perffd_2_event(int fd) +{ + struct pai_event *p; + + util_list_iterate(&list_pai_event, p) + if (p->fd == fd) + return p; + return NULL; +} + +/* Read the perf event ring buffer and write output to a file. + * The file contents is interpreted later after the data collection + * phase. + */ +static int savemap(int fd, void *data, struct data_pos *dp) +{ + unsigned long d_head_old, d_head = dp->data_head; + unsigned long d_prev = dp->data_tail; + int diff = d_head - d_prev; + int wrapped; + + d_head_old = d_head; + printf("Data head:%#llx tail:%#llx offset:%#llx size:%#llx\n", + dp->data_head, dp->data_tail, dp->data_offset, + dp->data_size); + if (!diff) + return 0; + wrapped = d_head / dp->data_size != d_prev / dp->data_size; + d_head %= dp->data_size; + if (!d_head) { /* Head at buffer end is buffer end */ + d_head = dp->data_size; + wrapped = 0; + } + + d_prev %= dp->data_size; + if (wrapped) { /* Read from d_prev to buffer end */ + int part2 = dp->data_size - d_prev; + + diff -= part2; + printf("Write %d bytes [%ld,%lld)\n", part2, d_prev, + dp->data_size); + if (write(fd, data + d_prev, part2) == -1) + err(EXIT_FAILURE, "write error for event file"); + d_prev = 0; /* Start at position zero */ + } + printf("Write %d bytes [%ld,%ld)\n", diff, d_prev, d_head); + if (write(fd, data + d_prev, diff) == -1) + err(EXIT_FAILURE, "write error for event file"); + + dp->data_tail = d_head_old; /* Write last read position */ + return 0; +} + +static void readmap(int fd) +{ + struct pai_event *p = perffd_2_event(fd); + struct perf_event_mmap_page *area; + + printf("Ring buffer for fd %d %s(%d)\n", fd, p->file_name, p->file_fd); + area = p->map_addr; + savemap(p->file_fd, p->map_addr + area->data_offset, + (struct data_pos *)&area->data_head); + syncfs(p->file_fd); +} + +/* Collect the data in the event ring buffers. Since there might be one + * ring buffer per event, sleep some short time and always read all + * ring buffer for new contents. + */ +static void collect(unsigned long cnt) +{ + fd_set r_fds, e_fds, a_fds; + struct pai_event *p; + struct timeval tv; + int rc, max_fd; + + do { + max_fd = -1; + tv.tv_sec = read_interval / 1000; + tv.tv_usec = (1000 * read_interval) % 1000000; + FD_ZERO(&r_fds); + FD_ZERO(&e_fds); + FD_ZERO(&a_fds); + util_list_iterate(&list_pai_event, p) { + if (p->attr.watermark) { + FD_SET(p->fd, &r_fds); + FD_SET(p->fd, &e_fds); + FD_SET(p->fd, &a_fds); + if (p->fd > max_fd) + max_fd = p->fd; + } + } + + if (max_fd == -1) + break; + rc = select(max_fd + 1, &r_fds, NULL, &e_fds, &tv); + if (rc >= 0) { + if (rc == 0) + /* Termination, trigger final read */ + r_fds = a_fds; + for (int i = 0; i < max_fd + 1; ++i) { + if (FD_ISSET(i, &r_fds)) + readmap(i); + } + } + } while (rc != -1 && --cnt > 0); +} + +static void lookup_event(__u64 evtnum, __u16 ctr, __u64 value) +{ + struct pmu_events *p; + + util_list_iterate(&list_pmu_event, p) { + if (p->base == evtnum) { + struct event_name *n = p->lst; + + for (int i = 0; i < p->lstlen; ++n, ++i) { + if (p->base + ctr == n->config) { + n->total += value; + return; + } + } + } + } +} + +/* Display the raw data, which is a pair of counter number and values + * in the form of counter-nr:value. The first 4 bytes are the length + * of the raw-data area. Then follows a key/value pair of 2 bytes key + * and 8 bytes value. + */ +static int evtraw_show(__u64 evtnum, unsigned char *p) +{ + size_t offset = 4, bytes = *(__u32 *)p; + __u16 ctr; + __u64 value; + + while (offset < bytes) { + ctr = *(__u16 *)(p + offset); + offset += sizeof(ctr); + value = *(__u64 *)(p + offset); + offset += sizeof(value); + printf("%c%hd:%#llx", offset > 14 ? ',' : ' ', ctr, value); + lookup_event(evtnum, ctr, value); + if (offset + sizeof(ctr) + sizeof(value) > bytes) + break; + } + return 1; +} + +#define NSEC_PER_SEC 1000000000L +static void timestamp(u64 timestamp) +{ + if (humantime) + printf("%lld.%09lld ", timestamp / NSEC_PER_SEC, + timestamp % NSEC_PER_SEC); + else + printf("%#llx ", timestamp); +} + +static const char *evt_selector(struct perf_event_attr *pa) +{ + if (pa->exclude_kernel) + return ":u"; + if (pa->exclude_user) + return ":k"; + return ""; +} + +static void evt_show(__u64 evtnum, const char *evtsel, struct pai_event_out *ev) +{ + timestamp(ev->time); + printf("%d ", ev->cpu); + + switch (ev->type) { + case PERF_RECORD_EXIT: + case PERF_RECORD_FORK: + printf("%s pid %u ppid %u", + ev->type == PERF_RECORD_FORK ? "fork" : "exit", + ev->u.s_fork.pid, ev->u.s_fork.ppid); + break; + + case PERF_RECORD_COMM: + printf("%s %s pid %u/%u", + ev->misc ? "exec" : "prctl", + ev->u.s_comm.cmd, ev->u.s_comm.pid, + ev->u.s_comm.tid); + break; + + case PERF_RECORD_SWITCH_CPU_WIDE: + if (ev->misc & PERF_RECORD_MISC_SWITCH_OUT) { + short p = PERF_RECORD_MISC_SWITCH_OUT_PREEMPT; + + printf("cs-out %c nextpid %u/%u", + (ev->misc & p) ? 'P' : '-', + ev->u.s_cs.next_prev_pid, + ev->u.s_cs.next_prev_tid); + } else { + printf("cs-in prevpid %u/%u ", + ev->u.s_cs.next_prev_pid, + ev->u.s_cs.next_prev_tid); + if (ev->cs_switch.valid) + printf("SWITCH %u/%u->%u/%u", + ev->cs_switch.frompid, + ev->cs_switch.fromtid, + ev->cs_switch.topid, + ev->cs_switch.totid); + } + break; + + case PERF_RECORD_LOST_SAMPLES: + printf("lost %lld\n", ev->u.s_lost.lost); + break; + + case PERF_RECORD_THROTTLE: + case PERF_RECORD_UNTHROTTLE: + printf("%sthrottle id %lld stream_id %lld\n", + ev->type == PERF_RECORD_THROTTLE ? "" : "un", + ev->u.s_throttle.id, ev->u.s_throttle.stream_id); + break; + + case PERF_RECORD_SAMPLE: + printf("event %lld%s sample pid %u/%u", evtnum, evtsel, + ev->u.s_sample.pid, ev->u.s_sample.tid); + if (ev->raw) { + evtraw_show(evtnum, ev->raw); + ev->raw = NULL; + } + } + putchar('\n'); +} + +/* Collect the contents of the event ring buffer data which was saved in + * a file during data collection phase. + */ +static int evt_scan(char *fn, unsigned char *buf, size_t len, + struct perf_event_attr *pa) +{ + const char *evtsel = evt_selector(pa); + __u64 sample_type = pa->sample_type; + int allcnt = 0, cnt = 0, rawok = 0; + struct perf_event_header *hdr; + size_t offset = sizeof(*pa); + __u64 evtnum = pa->config; + struct pai_event_out ev; + size_t limit; + __u32 *ptr32; + __u64 *ptr; + struct { + __u32 pid, tid; + unsigned char valid; + } last_csout = { 0, 0, 0 }; + + while (offset < len) { + hdr = (struct perf_event_header *)(buf + offset); + memset(&ev, 0, sizeof(ev)); + + if (hdr->size < sizeof(*hdr)) + return 1; + ++allcnt; + if (verbose) + printf("[%#08zx] type %d misc %hd size %hx ", offset, + hdr->type, hdr->misc, hdr->size); + limit = offset + hdr->size; + offset += sizeof(*hdr); + ev.type = hdr->type; + ev.misc = hdr->misc; + + switch (hdr->type) { + case PERF_RECORD_EXIT: + case PERF_RECORD_FORK: + memcpy(&ev.u, buf + offset, sizeof(ev.u.s_fork)); + offset += sizeof(ev.u.s_fork); + ev.time = ev.u.s_fork.time; + break; + + case PERF_RECORD_COMM: + memcpy(&ev.u, buf + offset, sizeof(ev.u.s_comm)); + offset += sizeof(ev.u.s_comm); + /* The command name saved by the kernel is either + * 8 or 16 bytes in size. If it fits in 8 bytes, the + * entry size is eight bytes smaller, and not filled + * with terminating null bytes. Adjust offset in this + * case. + */ + if (strlen((const char *)ev.u.s_comm.cmd) < sizeof(__u64)) + offset -= sizeof(__u64); + break; + + case PERF_RECORD_SWITCH_CPU_WIDE: + memcpy(&ev.u, buf + offset, sizeof(ev.u.s_cs)); + offset += sizeof(ev.u.s_cs); + if (hdr->misc & PERF_RECORD_MISC_SWITCH_OUT) { + last_csout.valid = 1; + last_csout.pid = ev.u.s_cs.next_prev_pid; + last_csout.tid = ev.u.s_cs.next_prev_tid; + } else { + ev.cs_switch.valid = last_csout.valid; + ev.cs_switch.topid = last_csout.pid; + ev.cs_switch.totid = last_csout.tid; + ev.cs_switch.frompid = ev.u.s_cs.next_prev_pid; + ev.cs_switch.fromtid = ev.u.s_cs.next_prev_tid; + last_csout.valid = 0; + } + break; + + case PERF_RECORD_THROTTLE: + case PERF_RECORD_UNTHROTTLE: + memcpy(&ev.u, buf + offset, sizeof(ev.u.s_throttle)); + offset += sizeof(ev.u.s_throttle); + ev.time = ev.u.s_throttle.time; + break; + + case PERF_RECORD_LOST: + memcpy(&ev.u, buf + offset, sizeof(ev.u.s_lost)); + offset += sizeof(ev.u.s_lost); + break; + + case PERF_RECORD_SAMPLE: + ++cnt; /* Do nothing and collect below */ + break; + + default: + printf("unknown header-type %d ", hdr->type); + offset += hdr->size - sizeof(*hdr); + goto bypass; + } + + /* Now handle the data returned by samples and the fields + * mentioned in sample_id_all members which are appended + * to all PERF_RECORDS_xxx + * Note: SEQUENCE IS IMPORTANT. + */ + + /* The sample ip is __schedule() no benefit for output */ + if (sample_type & PERF_SAMPLE_IP) { + ptr = (__u64 *)(buf + offset); + offset += sizeof(*ptr); + ev.u.s_sample.ip = *ptr; + } + + if (sample_type & PERF_SAMPLE_TID) { + ptr = (__u64 *)(buf + offset); + offset += sizeof(*ptr); + ptr32 = (__u32 *)ptr; + ev.u.s_sample.pid = *ptr32; + ev.u.s_sample.tid = *(ptr32 + 1); + } + + if (sample_type & PERF_SAMPLE_TIME) { + ptr = (__u64 *)(buf + offset); + offset += sizeof(*ptr); + ev.time = *ptr; + } + + if (sample_type & PERF_SAMPLE_CPU) { + ptr = (__u64 *)(buf + offset); + offset += sizeof(*ptr); + ptr32 = (__u32 *)ptr; + ev.cpu = *ptr32; + } + + /* The period is always one, no benefit for output */ + if (sample_type & PERF_SAMPLE_PERIOD) { + ptr = (__u64 *)(buf + offset); + offset += sizeof(*ptr); + ev.u.s_sample.period = *ptr; + } + + if (hdr->type == PERF_RECORD_SAMPLE && + sample_type & PERF_SAMPLE_RAW) { + ptr32 = (__u32 *)(buf + offset); + offset += *ptr32 + sizeof(*ptr32); + if (*ptr32 > sizeof(*ptr32)) { + ev.raw = ptr32; + ++rawok; + } + } + evt_show(evtnum, evtsel, &ev); +bypass: + if (offset != limit) { + warnx("%s error at offset:%#zx limit:%#zx", + fn, offset, limit); + return 1; + } + } + if (verbose) + printf("%s records %d samples %d raw-data %d\n", fn, allcnt, + cnt, rawok); + return 0; +} + +/* Read perf_event_open() config value for event. */ +static int read_event(char *dirname, char *filename) +{ + int config, rc = 0; + char *pathname; + FILE *fp; + + util_asprintf(&pathname, "%s/%s", dirname, filename); + fp = fopen(pathname, "r"); + if (fp) { + rc = fscanf(fp, "event=%x", &config); + fclose(fp); + } + free(pathname); + return rc == 1 ? config : -1; +} + +/* Scan event directory and fill event list. */ +static int scan_events(struct pmu_events *p) +{ + struct dirent **de_vec; + struct event_name *ep; + char *evtdir, *path; + int count, rc; + + path = util_path_sysfs("devices"); + rc = util_asprintf(&evtdir, "%s/%s/events", path, p->name); + free(path); + if (rc == -1) + return rc; + + count = util_scandir(&de_vec, alphasort, evtdir, ".*"); + p->lst = calloc(count, sizeof(*p->lst)); + if (!p->lst) { + rc = -1; + goto out; + } + + p->lstlen = 0; + ep = p->lst; + for (int i = 0; i < count; i++) { + if (de_vec[i]->d_type == DT_DIR) + continue; + ep->name = util_strdup(de_vec[i]->d_name); + rc = read_event(evtdir, de_vec[i]->d_name); + if (rc == -1 || !ep->name) { + for (ep = p->lst, rc = 0; rc < p->lstlen; ++rc, ++ep) + free(ep->name); + free(p->lst); + p->lst = NULL; + rc = -1; + goto out; + } + ep->config = rc; + if (p->base > ep->config) + p->base = ep->config; + ++p->lstlen; + ++ep; + } + rc = 0; + +out: + util_scandir_free(de_vec, count); + free(evtdir); + return rc; +} + +/* Scan all event names of PMU type. */ +static int add_events(int type) +{ + struct pmu_events *p; + char *pmuname; + int rc; + + rc = libcpumf_pmuname(type, &pmuname); + if (rc) { + warnx("PMU type %d not found", type); + return rc; + } + + p = malloc(sizeof(*p)); + if (p) { + p->type = type; + p->name = pmuname; + p->base = ~0UL; + p->lst = NULL; + rc = scan_events(p); + if (rc) + free(p); + else + util_list_add_head(&list_pmu_event, p); + } else { + rc = -1; + } + + if (rc) { + free(pmuname); + warnx("failed building event list for %s", pmuname); + } + return rc; +} + +/* Check event list for events of PMU type. If it does not exist, build it + * and add it to the list all of PMU names. + */ +static void build_events(int type) +{ + struct pmu_events *p; + + util_list_iterate(&list_pmu_event, p) + if (p->type == type) /* PMU already scanned */ + return; + + /* PMU list not yet scanned read event names */ + add_events(type); +} + +/* Show all events with a total number of non-zero. */ +static void show_events(void) +{ + struct pmu_events *p; + bool header = false; + + util_list_iterate(&list_pmu_event, p) { + int i = 0; + + for (struct event_name *n = p->lst; i < p->lstlen; ++i, ++n) { + if (n->total) { + if (!header) { + printf("Summary\n"); + header = true; + } + printf("PMU %s event %s nr %lld total %lld\n", + p->name, n->name, n->config - p->base, + n->total); + } + } + } +} + +/* Free all memory allocated for event summary. */ +static void remove_events(void) +{ + struct pmu_events *next, *p; + + util_list_iterate_safe(&list_pmu_event, p, next) { + int i = 0; + + for (struct event_name *n = p->lst; i < p->lstlen; ++i, ++n) + free(n->name); + free(p->name); + free(p->lst); + free(p); + } +} + +/* Scan one file which contains event ring buffer output. Print out the + * entries to stdout. + */ +static int map_check(char *fn, int (*fct)(char *, unsigned char *, size_t, + struct perf_event_attr *)) +{ + struct perf_event_attr pa; + unsigned char *p; + struct stat sb; + int rc = 1, fd; + + fd = open(fn, O_RDONLY); + if (fd == -1) { + warnx("open() failed for %s", fn); + return rc; + } + + if (fstat(fd, &sb) == -1) { + warnx("stat() failed for %s", fn); + close(fd); + return rc; + } + + if (verbose) + printf("%s size:%zu\n", fn, sb.st_size); + if (!S_ISREG(sb.st_mode)) { + warn("%s is not a file", fn); + close(fd); + return rc; + } + + if (sb.st_size < (long)sizeof(pa)) { + /* Event grouped --> empty file */ + close(fd); + unlink(fn); + return 0; + } + + p = mmap(0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) { + warnx("%s cannot map file", fn); + close(fd); + return rc; + } + + memcpy(&pa, p, sizeof(pa)); + if (close(fd) == -1) { + warnx("close() failed for %s", fn); + return rc; + } + build_events(pa.type); + rc = fct(fn, p, sb.st_size, &pa); + munmap(p, sb.st_size); + + return rc; +} + +/* Parse event attribute specification */ +static int parse_event_attr(char *cp) +{ + int x = 0; + + for (; *cp; ++cp) { + switch (tolower(*cp)) { + case 's': + x |= S390_EVTATTR_CTX_SWITCH; + break; + case 'c': + x |= S390_EVTATTR_COMM; + break; + case 'f': + x |= S390_EVTATTR_FORK; + break; + case 'u': + x |= S390_EVTATTR_USERSPACE; + break; + case 'k': + x |= S390_EVTATTR_KERNELSPACE; + break; + default: + errx(EXIT_FAILURE, + "Invalid event specification '%c'", *cp); + } + } + return x; +} + +/* Parse CPU list and event specifications */ +static void parse_cpulist(int enr, const char *parm) +{ + unsigned int evt_attr = 0; + cpu_set_t cmdlist, result; + char *cp; + int rc; + + CPU_ZERO(&cmdlist); + if (parm) { + /* CPU list with optional event attribute */ + cp = strchr(parm, ':'); + if (cp) { /* Handle event specification */ + *cp = '\0'; + evt_attr = parse_event_attr(++cp); + } + + if (strlen(parm) > 0) { + CPU_ZERO(&result); + rc = libcpumf_cpuset(parm, &cmdlist); + if (rc) + errx(EXIT_FAILURE, "Cannot use CPU list %s", + parm); + CPU_AND(&result, &cmdlist, &cpu_online_mask); + } else { + CPU_OR(&result, &cmdlist, &cpu_online_mask); + } + } else { + CPU_OR(&result, &cmdlist, &cpu_online_mask); + evt_attr = S390_EVTATTR_CTX_SWITCH | S390_EVTATTR_COMM | + S390_EVTATTR_FORK; + } + + for (rc = 0; rc < CPU_SETSIZE; ++rc) { + if (CPU_ISSET(rc, &result)) + ev_alloc(enr, rc, evt_attr); + } + for (rc = 0; rc < CPU_SETSIZE; ++rc) { + if (CPU_ISSET(rc, &cmdlist) && !CPU_ISSET(rc, &cpu_online_mask)) + warnx("CPU %d not online, event dropped", rc); + } +} + +static struct util_opt opt_vec[] = { + UTIL_OPT_SECTION("OPTIONS"), + { + .option = { "crypto", optional_argument, NULL, 'c' }, + .argument = "CPULIST[:DATA]", + .desc = "Collect PAI crypto counters" + }, + { + .option = { "mapsize", required_argument, NULL, 'm' }, + .argument = "SIZE", + .desc = "Specifies number of 4KB pages for event ring buffer" + }, + { + .option = { "report", no_argument, NULL, 'r' }, + .desc = "Report file contents" + }, + { + .option = { "interval", required_argument, NULL, 'i' }, + .argument = "NUMBER", + .desc = "Specifies interval between read operations in milliseconds" + }, + { + .option = { "verbose", no_argument, NULL, 'V' }, + .desc = "Verbose output" + }, + { + .option = { "humantime", no_argument, NULL, 'H' }, + .desc = "Human readable timestamp in seconds.nanoseconds" + }, + { + .option = { "summary", no_argument, NULL, 'S' }, + .desc = "Print summary of all non-zero counter values" + }, + UTIL_OPT_HELP, + UTIL_OPT_VERSION, + UTIL_OPT_END +}; + +static const struct util_prg prg = { + .desc = "Record and report Processor Activity Instrumentation Facility Counters.", + .copyright_vec = { + { + .owner = "IBM Corp.", + .pub_first = 2022, + .pub_last = 2022, + }, + UTIL_PRG_COPYRIGHT_END + } +}; + +static void record_cpus(const char *cp) +{ + if (!libcpumf_have_pai_crypto()) + errx(EXIT_FAILURE, "No support for PAI crypto counters"); + parse_cpulist(S390_EVT_PAI_CRYPTO, cp); +} + +/* Mapsize must be power of 2 and larger than 4. Count bits in n and + * return 0 if input is invalid and has a bit count larger than one. + */ +static unsigned long check_mapsize(unsigned long n) +{ + int bit, cnt = 0; + + if (n < 4) + return 0; + for (bit = 0; bit < __BITS_PER_LONG; ++bit) + if (n & (1 << bit)) + ++cnt; + return cnt == 1 ? n : 0; +} + +int main(int argc, char **argv) +{ + bool record = false, report = false, summary = false; + unsigned long loop_count = 1; + int ch, group = 0; + char *slash; + + util_list_init(&list_pai_event, struct pai_event, node); + util_list_init(&list_pmu_event, struct pmu_events, node); + util_prg_init(&prg); + util_opt_init(opt_vec, NULL); + + /* Read currently online CPUs and create a bit mask. + * This bitmap of online CPUs is used to check command line parameter + * for valid CPUs + */ + ch = libcpumf_cpuset_fn(S390_CPUS_ONLINE, &cpu_online_mask); + if (ch) + err(EXIT_FAILURE, "Cannot read file " S390_CPUS_ONLINE); + + while ((ch = util_opt_getopt_long(argc, argv)) != -1) { + switch (ch) { + default: + util_opt_print_parse_error(ch, argv); + return EXIT_FAILURE; + case 'h': + util_prg_print_help(); + util_opt_print_help(); + return EXIT_SUCCESS; + case 'v': + util_prg_print_version(); + return EXIT_SUCCESS; + case 'c': + record_cpus(optarg); + record = true; + break; + case 'i': + errno = 0; + read_interval = (unsigned int)strtoul(optarg, &slash, 0); + if (errno || !read_interval || *slash) + errx(EXIT_FAILURE, "Invalid argument for -%c", ch); + break; + case 'm': + errno = 0; + mapsize = strtoul(optarg, &slash, 0); + mapsize = check_mapsize(mapsize); + if (errno || !mapsize || *slash) + errx(EXIT_FAILURE, "Invalid argument for -%c", ch); + break; + case 'r': + report = true; + break; + case 'S': + summary = true; + break; + case 'H': + humantime = 1; + break; + case 'V': + ++verbose; + break; + } + } + + /* Without options do report on all files */ + if (!record && !report) { + warnx("No action specified assume report"); + report = true; + } + if (record) { + /* In record mode command line parameter is run-time */ + if (optind < argc) { + errno = 0; + loop_count = strtoul(argv[optind], &slash, 0); + if (errno || !loop_count || *slash) + errx(EXIT_FAILURE, "Invalid argument for runtime"); + } + + ev_install(group); + ev_enable(); + + collect(loop_count); + + ev_disable(); + ev_deinstall(); + ev_dealloc(); + return EXIT_SUCCESS; + } + + /* Must be reporting */ + ch = 0; + if (optind < argc) { /* Report mode command line has files */ + for (; optind < argc; ++optind) + ch += map_check(argv[optind], evt_scan); + } else { /* Scan files in local directory */ + struct dirent **de_vec; + int count = util_scandir(&de_vec, alphasort, ".", + "paicrypto.[0-9]+"); + for (int i = 0; i < count; i++) + if (de_vec[i]->d_type == DT_REG) + ch += map_check(de_vec[i]->d_name, evt_scan); + util_scandir_free(de_vec, count); + } + + if (summary && report) { + show_events(); + remove_events(); + } + return ch ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/cpumf/pai.h b/cpumf/pai.h new file mode 100644 index 00000000..8a165b59 --- /dev/null +++ b/cpumf/pai.h @@ -0,0 +1,105 @@ +/* + * pai - Extract CPU Processor Activity Instrumentation (PAI) facility data. + * + * Copyright IBM Corp. 2022 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PAI_H +#define PAI_H + +#define S390_EVT_PAI_CRYPTO 0x1000 + +enum { /* Event attribute specifications */ + S390_EVTATTR_CTX_SWITCH = 1, + S390_EVTATTR_COMM = 2, + S390_EVTATTR_FORK = 4, + S390_EVTATTR_USERSPACE = 8, + S390_EVTATTR_KERNELSPACE = 16 +}; + +struct pai_event { + struct util_list_node node; /* List node */ + struct perf_event_attr attr; /* Perf_event_open(2) attr */ + int fd; /* Perf event file descriptor */ + void *map_addr; /* Sampling data mapping address */ + size_t map_size; /* Sampling size mapping size */ + char file_name[16]; /* File name of sampled data */ + int file_fd; /* Map data output file descriptor */ + int cpu; /* Perf_event_open(2) CPU */ + unsigned long flags; /* Perf_event_open(2) flags */ +}; + +struct pai_event_out { /* Output for CRYPTO_ALL event */ + __u32 type; /* Header type, see PERF_RECORD_xxx */ + __u16 misc; /* Header misc, value depends on type */ + __u64 time; /* Time stamp valid for all entries */ + __u32 cpu; /* CPU number valid for all entries */ + union { + /* Fields from PERF_RECORD_FORK|EXIT */ + struct { + __u32 pid, ppid; + __u32 tid, ptid; + __u64 time; + } s_fork; + /* Fields from PERF_RECORD_COMM */ + struct { + __u32 pid, tid; + __u8 cmd[16]; + } s_comm; + /* Fields from PERF_RECORD_SWITCH_CPU_WIDE */ + struct { + __u32 next_prev_pid, next_prev_tid; + } s_cs; + /* Fields from PERF_RECORD_LOST_SAMPLES */ + struct { + __u64 lost; + } s_lost; + /* Relevant fields from PERF_RECORD_SAMPLE, time and cpu + * are stored above + */ + struct { + __u64 ip; + __u64 period; + __u32 pid, tid; + } s_sample; + /* Fields from PERF_RECORD_[UN]THROTTLE */ + struct { + __u64 time; + __u64 id; + __u64 stream_id; + } s_throttle; + } u; + /* Information on last context switch out */ + struct cs_switch { + unsigned char valid; + __u32 topid, totid; + __u32 frompid, fromtid; + } cs_switch; + void *raw; /* Pointer to key/value array for crypto counters */ +}; + +struct data_pos { /* Perf event mapped ring buffer */ + __u64 data_head; /* Head in the data section */ + __u64 data_tail; /* User-space written tail */ + __u64 data_offset; /* Where the buffer starts */ + __u64 data_size; /* Data buffer size */ +}; + +struct event_name { /* Event list for number to name xlate */ + char *name; /* Event name */ + __u64 config; /* Event config value */ + __u64 total; /* Total counter value */ +}; + +struct pmu_events { /* Event list for PMU number to name xlate */ + struct util_list_node node; /* List node */ + char *name; /* PMU name */ + int type; /* PMU type */ + int lstlen; /* # of entries in lst */ + unsigned long base; /* Base event number */ + struct event_name *lst; /* List of event names */ +}; +#endif /* PAI_H */