mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
cpumf/lshwc: Program to extract complete counter sets
Program reads complete counter sets from any CPU by opening device /dev/hwctr which is available with linux version 5.12. The counter sets and CPUs can be specified on the command line. Signed-off-by: Thomas Richter <tmricht@linux.ibm.com> Reviewed-by: Sumanth Korikkar <sumanthk@linux.ibm.com> Reviewed-by: Jan Hoeppner <hoeppner@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
cd532cb6cd
commit
27a562da0a
@@ -1,7 +1,7 @@
|
||||
include ../common.mak
|
||||
|
||||
BIN_FILES = lscpumf chcpumf
|
||||
MAN_FILES = lscpumf.1 chcpumf.8
|
||||
BIN_FILES = lscpumf chcpumf lshwc
|
||||
MAN_FILES = lscpumf.1 chcpumf.8 lshwc.1
|
||||
|
||||
all: $(BIN_FILES)
|
||||
|
||||
@@ -9,6 +9,7 @@ libs = $(rootdir)/libutil/libutil.a
|
||||
|
||||
lscpumf: lscpumf.o $(libs)
|
||||
chcpumf: chcpumf.o $(libs)
|
||||
lshwc: lshwc.o $(libs)
|
||||
|
||||
install: all install-man
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8
|
||||
|
||||
775
cpumf/lshwc.c
Normal file
775
cpumf/lshwc.c
Normal file
@@ -0,0 +1,775 @@
|
||||
/* Copyright IBM Corp. 2021
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
/* CPU Measurements counter facility counter sets can be extracted by a
|
||||
* device driver accessible by opening device /dev/hwctr.
|
||||
* This program extracts complete counter set using this device.
|
||||
* Counter sets are per CPU, the interface allows to specify counter sets
|
||||
* for individual CPUs. The supported flags are executed from left to
|
||||
* right, the first error encountered stops the execution of the program.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/user.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_prg.h"
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_libc.h"
|
||||
|
||||
#include "lshwc.h"
|
||||
|
||||
#define SERVICELEVEL "/proc/service_levels"
|
||||
#define CPUS_ONLINE "/sys/devices/system/cpu/online"
|
||||
#define CPUS_POSSIBLE "/sys/devices/system/cpu/possible"
|
||||
#define CPUS_KERNELMAX "/sys/devices/system/cpu/kernel_max"
|
||||
#define MAXCTRS 512
|
||||
|
||||
static const unsigned int ioctlsleep = 60;
|
||||
static unsigned int read_interval = ioctlsleep, cfvn, csvn, authorization;
|
||||
static unsigned long loop_count = 1;
|
||||
static unsigned char *ioctlbuffer;
|
||||
static bool allcpu;
|
||||
|
||||
static unsigned int max_possible_cpus; /* No of possible CPUs */
|
||||
struct ctrname { /* List of defined counters */
|
||||
char *name; /* Counter name */
|
||||
bool hitcnt; /* Counter number read from ioctl() */
|
||||
unsigned long total; /* Total counter value */
|
||||
unsigned long *ccv; /* Per CPU counter value */
|
||||
} ctrname[MAXCTRS];
|
||||
|
||||
/* Open file and extract counter number */
|
||||
static int read_counter(const char *p)
|
||||
{
|
||||
FILE *fp = fopen(p, "r");
|
||||
int rc = 0, ctr;
|
||||
|
||||
if (fp) {
|
||||
rc = fscanf(fp, "event=%x", &ctr);
|
||||
fclose(fp);
|
||||
}
|
||||
return rc == 1 ? ctr : -EINVAL;
|
||||
}
|
||||
|
||||
static int add_countername(char *name, int nr)
|
||||
{
|
||||
ctrname[nr].name = strdup(name);
|
||||
return ctrname[nr].name ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
static bool read_counternames(void)
|
||||
{
|
||||
struct dirent **namelist = NULL;
|
||||
int i, ctr = 0, count = 0;
|
||||
char *path, *ctrpath;
|
||||
|
||||
path = util_path_sysfs("/bus/event_source/devices/cpum_cf/events/");
|
||||
count = util_scandir(&namelist, alphasort, path, "[^.]");
|
||||
if (count <= 0) {
|
||||
warnx("Cannot open %s", path);
|
||||
free(path);
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < count && ctr >= 0; i++) {
|
||||
util_asprintf(&ctrpath, "%s/%s", path, namelist[i]->d_name);
|
||||
ctr = read_counter(ctrpath);
|
||||
free(ctrpath);
|
||||
if (ctr >= 0)
|
||||
ctr = add_countername(namelist[i]->d_name, ctr);
|
||||
}
|
||||
if (ctr < 0)
|
||||
warnx("Cannot parse %s", path);
|
||||
util_scandir_free(namelist, count);
|
||||
free(path);
|
||||
return ctr < 0 ? false : true;
|
||||
}
|
||||
|
||||
static void free_counternames(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
free(ctrname[i].name);
|
||||
free(ctrname[i].ccv);
|
||||
}
|
||||
}
|
||||
|
||||
static struct check_result {
|
||||
bool cpu_pos; /* CPU Number possible */
|
||||
bool cpu_req; /* CPU Number requested */
|
||||
bool cpu_hit; /* CPU Number received */
|
||||
unsigned char sets_req; /* Counters sets requested */
|
||||
unsigned char sets_hit; /* Counters sets received */
|
||||
} *check;
|
||||
|
||||
static bool check_set(unsigned long a, unsigned long b, unsigned long sets)
|
||||
{
|
||||
if (a > b)
|
||||
return false;
|
||||
for (; a <= b; ++a) {
|
||||
if (a >= max_possible_cpus || !check[a].cpu_pos)
|
||||
return false;
|
||||
check[a].cpu_req = true;
|
||||
check[a].sets_req = sets;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions to parse command line parameters
|
||||
* Convert a number from ascii to int.
|
||||
*/
|
||||
static unsigned long getnumber(char *word, char stopchar)
|
||||
{
|
||||
unsigned long no;
|
||||
char *endp;
|
||||
|
||||
no = strtoul(word, &endp, 0);
|
||||
if (*endp != stopchar)
|
||||
errx(EXIT_FAILURE, "Invalid parameter %s", word);
|
||||
return no;
|
||||
}
|
||||
|
||||
/* Remove all whitespace from string. */
|
||||
static void kill_whitespace(char *s)
|
||||
{
|
||||
char *cp = s;
|
||||
|
||||
for (; *s != '\0'; ++s) {
|
||||
if (isspace(*s))
|
||||
continue;
|
||||
if (isprint(*s))
|
||||
*cp++ = *s;
|
||||
}
|
||||
*cp = '\0';
|
||||
}
|
||||
|
||||
/* Read file to get all online CPUs */
|
||||
static bool get_cpus(char *file, char *buf, size_t bufsz)
|
||||
{
|
||||
char fmt[16];
|
||||
FILE *slp;
|
||||
int rc;
|
||||
|
||||
slp = fopen(file, "r");
|
||||
if (!slp) {
|
||||
warnx("Cannot open %s", file);
|
||||
return false;
|
||||
}
|
||||
snprintf(fmt, sizeof(fmt), "%%%zus", bufsz - 1);
|
||||
rc = fscanf(slp, fmt, buf);
|
||||
fclose(slp);
|
||||
if (rc != 1)
|
||||
warnx("Cannot parse %s", file);
|
||||
return rc == 1 ? true : false;
|
||||
}
|
||||
|
||||
/* Parse counter set specification */
|
||||
static unsigned long parse_ctrset(char *cp)
|
||||
{
|
||||
unsigned long x = 0;
|
||||
|
||||
for (; *cp; ++cp) {
|
||||
switch (tolower(*cp)) {
|
||||
case 'b':
|
||||
x |= S390_HWCTR_BASIC;
|
||||
break;
|
||||
case 'c':
|
||||
x |= S390_HWCTR_CRYPTO;
|
||||
break;
|
||||
case 'e':
|
||||
x |= S390_HWCTR_EXT;
|
||||
break;
|
||||
case 'm':
|
||||
x |= S390_HWCTR_MT_DIAG;
|
||||
break;
|
||||
case 'p':
|
||||
case 'u':
|
||||
x |= S390_HWCTR_USER;
|
||||
break;
|
||||
case 'a':
|
||||
x |= S390_HWCTR_ALL;
|
||||
break;
|
||||
default:
|
||||
errx(EXIT_FAILURE,
|
||||
"Invalid counter set specification '%c'", *cp);
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static char *show_ctrset(unsigned long set)
|
||||
{
|
||||
static char text[16];
|
||||
int i = 0;
|
||||
|
||||
if (set & S390_HWCTR_BASIC)
|
||||
text[i++] = 'B';
|
||||
if (set & S390_HWCTR_CRYPTO)
|
||||
text[i++] = 'C';
|
||||
if (set & S390_HWCTR_EXT)
|
||||
text[i++] = 'E';
|
||||
if (set & S390_HWCTR_MT_DIAG)
|
||||
text[i++] = 'M';
|
||||
if (set & S390_HWCTR_USER)
|
||||
text[i++] = 'U';
|
||||
text[i] = '\0';
|
||||
return text;
|
||||
}
|
||||
|
||||
/* Parse CPU list and counter sets */
|
||||
static void parse_cpulist(char *parm, struct s390_hwctr_start *start)
|
||||
{
|
||||
__u64 *words = start->cpumask;
|
||||
unsigned long i, no_a, no_b;
|
||||
char *cp, *tokens[16]; /* Used to parse command line params */
|
||||
char cpubuf[256];
|
||||
|
||||
start->data_bytes = 0;
|
||||
if (parm)
|
||||
kill_whitespace(parm);
|
||||
if (!parm || *parm == ':') {
|
||||
/* No CPU list or just counter sets */
|
||||
if (!get_cpus(CPUS_ONLINE, cpubuf, sizeof(cpubuf)))
|
||||
exit(EXIT_FAILURE);
|
||||
if (parm)
|
||||
strcat(cpubuf, parm);
|
||||
parm = cpubuf;
|
||||
}
|
||||
|
||||
cp = strchr(parm, ':');
|
||||
if (cp) { /* Handle counter set */
|
||||
*cp = '\0';
|
||||
start->counter_sets = parse_ctrset(++cp);
|
||||
} else {
|
||||
start->counter_sets = S390_HWCTR_ALL;
|
||||
}
|
||||
/* Check with authorized counter sets */
|
||||
if ((start->counter_sets & authorization) != start->counter_sets) {
|
||||
unsigned int noton = ~(start->counter_sets & authorization);
|
||||
|
||||
start->counter_sets &= authorization;
|
||||
if (!start->counter_sets)
|
||||
errx(EXIT_FAILURE, "No counter sets are authorized");
|
||||
warnx("One or more counter sets are not authorized: %s",
|
||||
show_ctrset(noton));
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tokens) && (tokens[i] = strtok(parm, ",")) != 0;
|
||||
++i, parm = 0) {
|
||||
cp = strchr(tokens[i], '-'); /* Range character? */
|
||||
if (cp) {
|
||||
no_a = getnumber(tokens[i], *cp);
|
||||
no_b = getnumber(++cp, '\0');
|
||||
} else {
|
||||
no_b = getnumber(tokens[i], '\0');
|
||||
no_a = no_b;
|
||||
}
|
||||
if (!check_set(no_a, no_b, start->counter_sets))
|
||||
errx(EXIT_FAILURE, "Invalid CPU list %s", tokens[i]);
|
||||
}
|
||||
|
||||
/* Convert the CPU list to a bitmask for kernel cpumask_t */
|
||||
for (i = 0, no_b = 0; i < max_possible_cpus; ++i) {
|
||||
if (check[i].cpu_req) {
|
||||
no_a = i % __BITS_PER_LONG;
|
||||
no_b = i / __BITS_PER_LONG;
|
||||
words[no_b] |= 1ULL << no_a;
|
||||
}
|
||||
}
|
||||
/* no_b is highest used index, swap array */
|
||||
start->cpumask_len = (no_b + 1) * 8;
|
||||
for (no_a = 0; no_a < no_b; ++no_a, --no_b) {
|
||||
__u64 tmp = words[no_a];
|
||||
|
||||
words[no_a] = words[no_b];
|
||||
words[no_b] = tmp;
|
||||
}
|
||||
start->version = S390_HWCTR_START_VERSION;
|
||||
}
|
||||
|
||||
static bool check_setpossible(void)
|
||||
{
|
||||
char *cp, *parm, *tokens[16]; /* Used to parse command line params */
|
||||
unsigned long i, no_a, no_b;
|
||||
char cpubuf[1024];
|
||||
|
||||
if (!get_cpus(CPUS_KERNELMAX, cpubuf, sizeof(cpubuf)))
|
||||
return false;
|
||||
max_possible_cpus = getnumber(cpubuf, '\0') + 1;
|
||||
check = calloc(max_possible_cpus, sizeof(*check));
|
||||
if (!check)
|
||||
err(EXIT_FAILURE, "Maximum CPUs %u", max_possible_cpus);
|
||||
if (!get_cpus(CPUS_POSSIBLE, cpubuf, sizeof(cpubuf))) {
|
||||
free(check);
|
||||
return false;
|
||||
}
|
||||
parm = cpubuf;
|
||||
for (i = 0; i < ARRAY_SIZE(tokens) && (tokens[i] = strtok(parm, ","));
|
||||
++i, parm = 0) {
|
||||
cp = strchr(tokens[i], '-');
|
||||
if (cp) { /* Range */
|
||||
no_a = getnumber(tokens[i], *cp);
|
||||
no_b = getnumber(++cp, '\0');
|
||||
} else {
|
||||
no_b = getnumber(tokens[i], '\0');
|
||||
no_a = no_b;
|
||||
}
|
||||
for (; no_a <= no_b; ++no_a)
|
||||
check[no_a].cpu_pos = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void show_header(void)
|
||||
{
|
||||
static bool header;
|
||||
bool comma = false;
|
||||
|
||||
if (header)
|
||||
return; /* Printed already */
|
||||
printf("Date,Time,CPU,"); /* Print counter name and number */
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf("%s(%ld)", ctrname[i].name ?: "Counter", i);
|
||||
comma = true;
|
||||
}
|
||||
putchar('\n');
|
||||
header = true;
|
||||
}
|
||||
|
||||
static void line(char *header)
|
||||
{
|
||||
bool comma;
|
||||
|
||||
show_header();
|
||||
if (allcpu) {
|
||||
for (unsigned int h = 0; h < max_possible_cpus; ++h) {
|
||||
char txt[16];
|
||||
|
||||
if (!check[h].cpu_hit)
|
||||
continue;
|
||||
comma = false;
|
||||
snprintf(txt, sizeof(txt), "CPU%d,", h);
|
||||
printf("%s%s", header, txt);
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf("%ld", ctrname[i].ccv[h]);
|
||||
comma = true;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/* Print total count of all CPUs */
|
||||
printf("%sTotal,", header);
|
||||
comma = false;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf("%ld", ctrname[i].total);
|
||||
comma = true;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void show(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
struct tm *now_tm;
|
||||
char now_text[32];
|
||||
|
||||
now_tm = localtime(&now);
|
||||
strftime(now_text, sizeof(now_text), "%F,%T,", now_tm);
|
||||
line(now_text);
|
||||
}
|
||||
|
||||
/* Return Counter set size numbers (in counters) */
|
||||
static unsigned int ctrset_size(int set)
|
||||
{
|
||||
switch (set) {
|
||||
case S390_HWCTR_BASIC:
|
||||
return 6;
|
||||
case S390_HWCTR_USER:
|
||||
return (cfvn == 1) ? 6 : 2;
|
||||
case S390_HWCTR_CRYPTO:
|
||||
return (csvn <= 5) ? 16 : 20;
|
||||
case S390_HWCTR_EXT:
|
||||
switch (csvn) {
|
||||
case 1: return 32;
|
||||
case 2: return 48;
|
||||
case 3:
|
||||
case 4:
|
||||
case 5: return 128;
|
||||
}
|
||||
return 160;
|
||||
case S390_HWCTR_MT_DIAG:
|
||||
switch (csvn) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3: return 0;
|
||||
}
|
||||
return 48;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return counter set offset numbers */
|
||||
static int ctrset_offset(int set)
|
||||
{
|
||||
switch (set) {
|
||||
case S390_HWCTR_BASIC:
|
||||
return 0;
|
||||
case S390_HWCTR_USER:
|
||||
return 32;
|
||||
case S390_HWCTR_CRYPTO:
|
||||
return 64;
|
||||
case S390_HWCTR_EXT:
|
||||
return 128;
|
||||
case S390_HWCTR_MT_DIAG:
|
||||
return 448;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool set_and_size_ok(struct s390_hwctr_setdata *p)
|
||||
{
|
||||
switch (p->set) {
|
||||
case S390_HWCTR_BASIC:
|
||||
case S390_HWCTR_USER:
|
||||
case S390_HWCTR_CRYPTO:
|
||||
case S390_HWCTR_EXT:
|
||||
case S390_HWCTR_MT_DIAG:
|
||||
return p->no_cnts == ctrset_size(p->set);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool add_countervalue(size_t idx, unsigned int cpu, unsigned long value)
|
||||
{
|
||||
if (idx >= ARRAY_SIZE(ctrname)) {
|
||||
warnx("Invalid counter number %zu", idx);
|
||||
return false;
|
||||
}
|
||||
if (cpu >= max_possible_cpus) {
|
||||
warnx("Invalid CPU number %d", cpu);
|
||||
return false;
|
||||
}
|
||||
if (!ctrname[idx].ccv) /* Unknown counter */
|
||||
ctrname[idx].ccv = calloc(max_possible_cpus,
|
||||
sizeof(unsigned long));
|
||||
if (ctrname[idx].ccv)
|
||||
ctrname[idx].ccv[cpu] += value;
|
||||
ctrname[idx].total += value;
|
||||
ctrname[idx].hitcnt = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int test_read(struct s390_hwctr_read *read)
|
||||
{
|
||||
void *base = &read->data;
|
||||
size_t offset = 0;
|
||||
|
||||
/* Clear previous hit counters */
|
||||
for (unsigned int i = 0; i < max_possible_cpus; ++i) {
|
||||
check[i].sets_hit = 0;
|
||||
check[i].cpu_hit = false;
|
||||
}
|
||||
|
||||
/* Iterate over all CPUs */
|
||||
for (unsigned int i = 0; i < read->no_cpus; ++i) {
|
||||
struct s390_hwctr_cpudata *cp = base + offset;
|
||||
|
||||
check[cp->cpu_nr].cpu_hit = true;
|
||||
check[cp->cpu_nr].sets_hit = 0;
|
||||
|
||||
offset += sizeof(cp->cpu_nr) + sizeof(cp->no_sets);
|
||||
/* Iterate over all counter sets */
|
||||
for (unsigned int j = 0; j < cp->no_sets; ++j) {
|
||||
struct s390_hwctr_setdata *sp = base + offset;
|
||||
|
||||
check[cp->cpu_nr].sets_hit |= sp->set;
|
||||
offset += sizeof(sp->set) + sizeof(sp->no_cnts);
|
||||
if (!set_and_size_ok(sp)) {
|
||||
warnx("CPU %d inconsistent set %d size %d",
|
||||
cp->cpu_nr, sp->set, sp->no_cnts);
|
||||
return -1;
|
||||
}
|
||||
/* Iterate over all counters in each set */
|
||||
for (unsigned int k = 0; k < sp->no_cnts; ++k) {
|
||||
__u64 value;
|
||||
void *addr = base + offset;
|
||||
size_t idx = ctrset_offset(sp->set) + k;
|
||||
|
||||
memcpy(&value, addr, sizeof(value));
|
||||
offset += sizeof(value);
|
||||
if (!add_countervalue(idx, cp->cpu_nr, value))
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
show();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_open(void)
|
||||
{
|
||||
int fd = open(S390_HWCTR_DEVICE, O_RDWR);
|
||||
|
||||
if (fd < 0)
|
||||
warn(S390_HWCTR_DEVICE);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int do_stop(int ioctlfd)
|
||||
{
|
||||
int rc = ioctl(ioctlfd, S390_HWCTR_STOP, 0);
|
||||
|
||||
if (rc < 0)
|
||||
warn("ioctl S390_HWCTR_STOP");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int do_start(int ioctlfd, struct s390_hwctr_start *start)
|
||||
{
|
||||
int rc = ioctl(ioctlfd, S390_HWCTR_START, start);
|
||||
|
||||
if (rc < 0)
|
||||
warn("ioctl S390_HWCTR_START");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int do_read(int ioctlfd)
|
||||
{
|
||||
size_t ioctlbuffer_len = PAGE_SIZE * max_possible_cpus +
|
||||
sizeof(struct s390_hwctr_read);
|
||||
struct s390_hwctr_read *read;
|
||||
int rc;
|
||||
|
||||
if (!ioctlbuffer) {
|
||||
ioctlbuffer = malloc(ioctlbuffer_len);
|
||||
if (!ioctlbuffer) {
|
||||
warn("ioctl S390_HWCTR_START");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
read = (struct s390_hwctr_read *)ioctlbuffer;
|
||||
rc = ioctl(ioctlfd, S390_HWCTR_READ, read);
|
||||
if (!rc)
|
||||
rc = test_read(read);
|
||||
else
|
||||
warn("ioctl S390_HWCTR_READ");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void do_sleep(void)
|
||||
{
|
||||
struct timespec req = {
|
||||
.tv_sec = read_interval,
|
||||
.tv_nsec = 0
|
||||
};
|
||||
|
||||
nanosleep(&req, NULL);
|
||||
}
|
||||
|
||||
/* Execute commands and report first error */
|
||||
static int do_it(char *s)
|
||||
{
|
||||
struct s390_hwctr_start start;
|
||||
int ioctlfd;
|
||||
int rc;
|
||||
|
||||
memset(&start, 0, sizeof(start));
|
||||
rc = max_possible_cpus / sizeof(__u64);
|
||||
start.cpumask = alloca(max_possible_cpus / sizeof(__u64));
|
||||
memset(start.cpumask, 0, rc);
|
||||
parse_cpulist(s, &start);
|
||||
errno = 0;
|
||||
ioctlfd = do_open();
|
||||
if (ioctlfd < 0)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
rc = do_start(ioctlfd, &start);
|
||||
if (rc < 0) {
|
||||
close(ioctlfd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
for (unsigned long i = 0; !rc && i < loop_count; ++i) {
|
||||
rc = do_read(ioctlfd);
|
||||
if (rc) {
|
||||
close(ioctlfd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (read_interval && i + 1 < loop_count)
|
||||
do_sleep();
|
||||
}
|
||||
rc = do_stop(ioctlfd);
|
||||
close(ioctlfd);
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* Read counter first and second version number */
|
||||
static bool get_cvn(void)
|
||||
{
|
||||
char *linep = NULL;
|
||||
bool good = false;
|
||||
size_t line_sz;
|
||||
ssize_t nbytes;
|
||||
FILE *slp;
|
||||
|
||||
slp = fopen(SERVICELEVEL, "r");
|
||||
if (!slp) {
|
||||
warn(SERVICELEVEL);
|
||||
return false;
|
||||
}
|
||||
while ((nbytes = getline(&linep, &line_sz, slp)) != EOF) {
|
||||
if (!strncmp(linep, "CPU-MF: Counter facility:", 25)) {
|
||||
int rc;
|
||||
|
||||
rc = sscanf(linep, "CPU-MF: Counter facility: version=%d.%d authorization=%x",
|
||||
&cfvn, &csvn, &authorization);
|
||||
good = rc == 3;
|
||||
if (!good)
|
||||
warnx("Cannot parse line %s", linep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(slp);
|
||||
free(linep);
|
||||
return good;
|
||||
}
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
UTIL_OPT_SECTION("OPTIONS"),
|
||||
{
|
||||
.option = { "all", no_argument, NULL, 'a' },
|
||||
.desc = "Displays all CPUs in output"
|
||||
},
|
||||
{
|
||||
.option = { "loop", required_argument, NULL, 'l' },
|
||||
.argument = "NUMBER",
|
||||
.desc = "Specifies loop count for next read"
|
||||
},
|
||||
{
|
||||
.option = { "interval", required_argument, NULL, 'i' },
|
||||
.argument = "NUMBER",
|
||||
.desc = "Specifies interval between read operations (seconds)"
|
||||
},
|
||||
UTIL_OPT_HELP,
|
||||
UTIL_OPT_VERSION,
|
||||
UTIL_OPT_END
|
||||
};
|
||||
|
||||
static const struct util_prg prg = {
|
||||
.desc = "Read CPU Measurement facility counter sets",
|
||||
.copyright_vec = {
|
||||
{
|
||||
.owner = "IBM Corp.",
|
||||
.pub_first = 2021,
|
||||
.pub_last = 2021,
|
||||
},
|
||||
UTIL_PRG_COPYRIGHT_END
|
||||
}
|
||||
};
|
||||
|
||||
/* Check for hardware support and exit if not available */
|
||||
static void have_support(void)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (stat(S390_HWCTR_DEVICE, &statbuf) == -1)
|
||||
errx(EXIT_FAILURE,
|
||||
"No support for CPU Measurement Counter set facility");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char *slash;
|
||||
int ch;
|
||||
|
||||
util_prg_init(&prg);
|
||||
util_opt_init(opt_vec, NULL);
|
||||
|
||||
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 'l':
|
||||
errno = 0;
|
||||
loop_count = strtoul(optarg, &slash, 0);
|
||||
if (errno || *slash)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c",
|
||||
ch);
|
||||
break;
|
||||
case 'i':
|
||||
errno = 0;
|
||||
read_interval = (unsigned int)strtoul(optarg, &slash, 0);
|
||||
if (errno || *slash)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
break;
|
||||
case 'a':
|
||||
allcpu = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
have_support();
|
||||
if (!get_cvn())
|
||||
return EXIT_FAILURE;
|
||||
if (!check_setpossible())
|
||||
return EXIT_FAILURE;
|
||||
if (!read_counternames()) {
|
||||
free(check);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
ch = do_it(NULL);
|
||||
} else {
|
||||
while (optind < argc) {
|
||||
ch = do_it(argv[optind++]);
|
||||
if (ch)
|
||||
break;
|
||||
}
|
||||
}
|
||||
free_counternames();
|
||||
free(check);
|
||||
return ch;
|
||||
}
|
||||
92
cpumf/lshwc.h
Normal file
92
cpumf/lshwc.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/* Copyright IBM Corp. 2021
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
/*
|
||||
* CPU Measurement counter facility application for device driver.
|
||||
*
|
||||
* Ioctl system call definitions.
|
||||
*/
|
||||
|
||||
#ifndef LSHWC_H
|
||||
#define LSHWC_H
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
enum {
|
||||
S390_HWCTR_BASIC = 0x2, /* BASIC counter set */
|
||||
S390_HWCTR_USER = 0x4, /* Problem-State Counter Set */
|
||||
S390_HWCTR_CRYPTO = 0x8, /* Crypto-Activity Counter Set */
|
||||
S390_HWCTR_EXT = 0x1, /* Extended Counter Set */
|
||||
S390_HWCTR_MT_DIAG = 0x20, /* MT-diagnostic Counter Set */
|
||||
S390_HWCTR_ALL = S390_HWCTR_BASIC | S390_HWCTR_USER |
|
||||
S390_HWCTR_CRYPTO | S390_HWCTR_EXT |
|
||||
S390_HWCTR_MT_DIAG
|
||||
};
|
||||
|
||||
/* The ioctl(..., S390_HWCTR_READ, ...) is the only subcommand which returns
|
||||
* data. It requires member data_bytes to be positive and indicates the
|
||||
* maximum amount of data available to store counter set data. The other
|
||||
* ioctl() subcommands do not use this member and it should be set to zero.
|
||||
*
|
||||
* The cpuset data is flattened using the following scheme, stored in member
|
||||
* data:
|
||||
*
|
||||
* 0x0 0x8 0xc 0x10 0x14 0x18 0x20 0x28 0xU-1
|
||||
* +---------+-----+---------+-----+---------+-----+-----+------+------+
|
||||
* | no_cpus | cpu | no_sets | set | no_cnts | cv1 | cv2 | .... | cv_n |
|
||||
* +---------+-----+---------+-----+---------+-----+-----+------+------+
|
||||
*
|
||||
* 0xU 0xU+4 0xU+8 0xU+10 0xV-1
|
||||
* +-----+---------+-----+-----+------+------+
|
||||
* | set | no_cnts | cv1 | cv2 | .... | cv_n |
|
||||
* +-----+---------+-----+-----+------+------+
|
||||
*
|
||||
* 0xV 0xV+4 0xV+8 0xV+c
|
||||
* +-----+---------+-----+---------+-----+-----+------+------+
|
||||
* | cpu | no_sets | set | no_cnts | cv1 | cv2 | .... | cv_n |
|
||||
* +-----+---------+-----+---------+-----+-----+------+------+
|
||||
*
|
||||
* U and V denote arbitrary hexadezimal addresses.
|
||||
* In fact the first int represents the number of CPUs data was extracted
|
||||
* from. This is followed by CPU number and number of counter sets extracted.
|
||||
* Both are two integer values. This is followed by the set number and number
|
||||
* of counters extracted. Both are two integer values. This is followed by
|
||||
* the counter values, each element is eight bytes in size.
|
||||
*/
|
||||
|
||||
struct s390_hwctr_start { /* Set CPUs to operate on */
|
||||
__u64 version; /* Version of interface */
|
||||
__u64 data_bytes; /* # of bytes required */
|
||||
__u64 cpumask_len; /* Length of CPU mask in bytes */
|
||||
__u64 *cpumask; /* Pointer to CPU mask */
|
||||
__u64 counter_sets; /* Bit mask of counter set to get */
|
||||
};
|
||||
|
||||
struct s390_hwctr_setdata { /* Counter set data */
|
||||
__u32 set; /* Counter set number */
|
||||
__u32 no_cnts; /* # of counters stored in cv[] */
|
||||
__u64 cv[0]; /* Counter values (variable length) */
|
||||
};
|
||||
|
||||
struct s390_hwctr_cpudata { /* Counter set data per CPU */
|
||||
__u32 cpu_nr; /* Counter set number */
|
||||
__u32 no_sets; /* # of counters sets in data[] */
|
||||
struct s390_hwctr_setdata data[0];
|
||||
};
|
||||
|
||||
struct s390_hwctr_read { /* Structure to get all ctr sets */
|
||||
__u64 no_cpus; /* Total # of CPUs data taken from */
|
||||
struct s390_hwctr_cpudata data[0];
|
||||
};
|
||||
|
||||
#define S390_HWCTR_MAGIC 'C' /* Random magic # for ioctls */
|
||||
#define S390_HWCTR_START _IOWR(S390_HWCTR_MAGIC, 1, struct s390_hwctr_start)
|
||||
#define S390_HWCTR_STOP _IO(S390_HWCTR_MAGIC, 2)
|
||||
#define S390_HWCTR_READ _IOWR(S390_HWCTR_MAGIC, 3, struct s390_hwctr_read)
|
||||
|
||||
#define S390_HWCTR_START_VERSION 1 /* Version # s390_hwctr_start */
|
||||
#define S390_HWCTR_DEVICE "/dev/hwctr" /* Device name */
|
||||
#endif
|
||||
134
cpumf/man/lshwc.1
Normal file
134
cpumf/man/lshwc.1
Normal file
@@ -0,0 +1,134 @@
|
||||
.\" lshwc.1
|
||||
.\"
|
||||
.\"
|
||||
.\" Copyright IBM Corp. 2021
|
||||
.\" 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 \fBlshwc\fP
|
||||
.
|
||||
.TH \*c "1" "February 2021" "s390-tools" "CPU-MF management programs"
|
||||
.
|
||||
.SH NAME
|
||||
\*c \- extract CPU Measurement Facilities counter sets
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
\*c
|
||||
.RB [ \-a ]
|
||||
.RB [ \-l
|
||||
.IR count ]
|
||||
.RB [ \-i
|
||||
.IR interval ]
|
||||
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
|
||||
.br
|
||||
\*c
|
||||
.BR \-h | \-\-help
|
||||
.br
|
||||
\*c
|
||||
.BR \-v | \-\-version
|
||||
.
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
The \*c command extracts complete counter sets from the CPU
|
||||
Measurement Facilities for Linux on Z.
|
||||
Counter sets can be specified and extracted for individual CPUs.
|
||||
The output is a comma-separated values file.
|
||||
Each line starts with a timestamp and the CPU number,
|
||||
followed by the extracted counter values.
|
||||
.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
Displays help information, then exits.
|
||||
.
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
Displays version information, then exits.
|
||||
.
|
||||
.TP
|
||||
.BR \-a ", " \-\-allcpu
|
||||
Displays counter values from each CPU.
|
||||
The default is a total summary line of all counters from all CPUs.
|
||||
.
|
||||
.TP
|
||||
.BR \-i ", " \-\-interval \fI\ seconds\fP
|
||||
Specifies a time interval, in seconds,
|
||||
that the command waits between read operations.
|
||||
The default is 60 seconds.
|
||||
.
|
||||
.TP
|
||||
.BR \-l ", " \-\-loop \fI\ count\fP
|
||||
Performs the specified number of read operations.
|
||||
.
|
||||
.TP
|
||||
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
|
||||
A comma-separated list of CPUs.
|
||||
Each CPU can optionally be followed by characters that specify the counter set.
|
||||
See below for details.
|
||||
.
|
||||
.SS "CPU List and counter-set specification"
|
||||
In the comma-separated list of CPUs,
|
||||
each element is a CPU or a range of CPUs.
|
||||
By default, \*c lists all CPUs.
|
||||
.P
|
||||
The CPU list can be followed by an optional list
|
||||
of characters that specify the counter sets to be extracted,
|
||||
preceded by a colon.
|
||||
The characters can be upper or lower case.
|
||||
By default, all counter sets are used.
|
||||
.IP b
|
||||
Include the basic counter set.
|
||||
.IP c
|
||||
Include the crypto counter set.
|
||||
.IP e
|
||||
Include the extended counter set.
|
||||
.IP m
|
||||
Include the MT_Diagnostic counter set.
|
||||
.IP p|u
|
||||
Include the problem counter set.
|
||||
.IP a
|
||||
Include all known counter sets (default).
|
||||
.SH "Concurrency with perf tool"
|
||||
The \*c tool and the linux
|
||||
.B perf
|
||||
tool use the same hardware and cannot be used concurrently.
|
||||
Both tools print an error message and abort when they
|
||||
detect this situation.
|
||||
.SH "EXAMPLES"
|
||||
The first example enables the basic and problem counter sets on CPU 0 and 1.
|
||||
Two read operations are performed and a summary line is printed for each
|
||||
read operation.
|
||||
.sp 1
|
||||
.nf
|
||||
.ft CW
|
||||
# lshwc -l2 0-1:BP
|
||||
Date,Time,CPU,CPU_CYCLES(0),INSTRUCTIONS(1),L1I_DIR_WRITES(2),L1I_PENALTY_CYCLES(3),L1D_DIR_WRITES(4),
|
||||
L1D_PENALTY_CYCLES(5),PROBLEM_STATE_CPU_CYCLES(32),PROBLEM_STATE_INSTRUCTIONS(33)
|
||||
2021-04-01,11:50:32,Total,125422,39421,304,13953,454,
|
||||
97489,0,0
|
||||
2021-04-01,11:51:32,Total,68074231,16386850,194028,21382384,317227,
|
||||
104503489,777383,14198
|
||||
.ft
|
||||
.fi
|
||||
.sp 1
|
||||
This example shows the counter values of the problem state counter set
|
||||
per CPU. CPU 0 and CPU 1 is selected.
|
||||
.nf
|
||||
.ft CW
|
||||
.sp 1
|
||||
# lshwc -l3 -a 0-1:P
|
||||
Date,Time,CPU,PROBLEM_STATE_CPU_CYCLES(32),PROBLEM_STATE_INSTRUCTIONS(33)
|
||||
2021-04-01,11:54:47,CPU0,0,0
|
||||
2021-04-01,11:54:47,CPU1,0,0
|
||||
2021-04-01,11:54:47,Total,0,0
|
||||
2021-04-01,11:55:47,CPU0,818775,14198
|
||||
2021-04-01,11:55:47,CPU1,125689,1306
|
||||
2021-04-01,11:55:47,Total,944464,15504
|
||||
2021-04-01,11:56:47,CPU0,3207071426,1489122591
|
||||
2021-04-01,11:56:47,CPU1,3225092021,1489278312
|
||||
2021-04-01,11:56:47,Total,6432163447,2978400903
|
||||
.ft
|
||||
.fi
|
||||
.SH "SEE ALSO"
|
||||
.BR lscpumf (1)
|
||||
.BR chcpumf (8)
|
||||
Reference in New Issue
Block a user