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 */