libevl/utils/evl-ps.c

854 lines
17 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019 Philippe Gerum <rpm@xenomai.org>
*/
#include <unistd.h>
#include <stdint.h>
#include <getopt.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sched.h>
#include <libgen.h>
#include <memory.h>
#include <search.h>
#include <limits.h>
#include <error.h>
#include <errno.h>
#include <ftw.h>
#include <uapi/evl/control.h>
#include <uapi/evl/thread.h>
static cpu_set_t cpu_restrict;
#define DISPLAY_STATE 1
#define DISPLAY_TIMES 2
#define DISPLAY_SCHED 4
#define DISPLAY_WAIT 8
#define DISPLAY_NUMERIC 32
#define DISPLAY_MODIFIERS DISPLAY_NUMERIC
#define DISPLAY_DEFAULT_FORMAT DISPLAY_SCHED
#define DISPLAY_LONG_FORMAT (DISPLAY_SCHED|DISPLAY_TIMES|DISPLAY_STATE)
static int display_format;
#define SORT_BY_PCPU 1
#define SORT_BY_ISW 2
#define SORT_BY_CPUTIME 4
#define SORT_BY_CTXSW 8
#define SORT_BY_RWA 16
#define SORT_REVERSE 128
static int sort_key;
#define ONE_BILLION 1000000000ULL
#define ONE_MILLION 1000000ULL
#define for_each_cpu(__cpu) \
for (__cpu = 0; __cpu < CPU_SETSIZE; __cpu++) \
if (CPU_ISSET(__cpu, &cpu_restrict))
#define short_optlist "@hnlstwpc:S:"
static const struct option options[] = {
{
.name = "cpu",
.has_arg = required_argument,
.val = 'c',
},
{
.name = "state",
.has_arg = no_argument,
.val = 's',
},
{
.name = "times",
.has_arg = no_argument,
.val = 't',
},
{
.name = "wait",
.has_arg = no_argument,
.val = 'w',
},
{
.name = "policy",
.has_arg = no_argument,
.val = 'p',
},
{
.name = "long",
.has_arg = no_argument,
.val = 'l',
},
{
.name = "numeric",
.has_arg = no_argument,
.val = 'n',
},
{
.name = "sort",
.has_arg = required_argument,
.val = 'S',
},
{
.name = "help",
.has_arg = no_argument,
.val = 'h',
},
{ /* Sentinel */ }
};
#define ARRAY_SIZE(__array) (sizeof(__array) / sizeof((__array)[0]))
static struct thread_info {
const char *name;
int cpu;
int bprio;
int cprio;
char *policy;
char *policy_details;
unsigned long nr_inbandsw;
unsigned long nr_ctxsw;
unsigned long nr_syscalls;
unsigned long nr_rwakeups;
unsigned long long cpu_time;
unsigned long long timeout;
char *wchan;
int percent_cpu;
unsigned int state;
pid_t pid;
bool parse_ok;
struct thread_info *next;
} *thread_list;
static int thread_nr;
static bool collect_sched_info(struct thread_info *ti, char *buf)
{
int ret = sscanf(buf, "%d %d %d %ms %ms",
&ti->cpu, &ti->bprio, &ti->cprio,
&ti->policy, &ti->policy_details);
return ret >= 4 && ret <= 5;
}
static bool collect_statistics(struct thread_info *ti, char *buf)
{
int ret = sscanf(buf, "%lu %lu %lu %lu %Lu %d",
&ti->nr_inbandsw,
&ti->nr_ctxsw,
&ti->nr_syscalls,
&ti->nr_rwakeups,
&ti->cpu_time,
&ti->percent_cpu);
return ret == 5;
}
static bool collect_state_info(struct thread_info *ti, char *buf)
{
int ret = sscanf(buf, "%x", &ti->state);
return ret == 1;
}
static bool collect_pid(struct thread_info *ti, char *buf)
{
int ret = sscanf(buf, "%d", &ti->pid);
return ret == 1;
}
static bool collect_timeout(struct thread_info *ti, char *buf)
{
int ret = sscanf(buf, "%Lu", &ti->timeout);
return ret == 1;
}
static bool collect_wchan(struct thread_info *ti, char *buf)
{
int ret = sscanf(buf, "%ms", &ti->wchan);
return ret == 1;
}
static struct collect_handler {
const char *name;
bool (*handle)(struct thread_info *ti, char *buf);
} collect_handlers[] = {
{
.name = "sched",
.handle = collect_sched_info,
},
{
.name = "stats",
.handle = collect_statistics,
},
{
.name = "state",
.handle = collect_state_info,
},
{
.name = "pid",
.handle = collect_pid,
},
{
.name = "timeout",
.handle = collect_timeout,
},
{
.name = "wchan",
.handle = collect_wchan,
},
};
static int scan_thread(const char *fpath, const struct stat *sb,
int typeflag, struct FTW *ftwbuf)
{
struct collect_handler *h;
struct thread_info *ti;
bool parse_ok = false;
char *name, *buf, *p;
ENTRY entry, *e;
FILE *fp;
int n;
if (typeflag == FTW_D && !strcmp(fpath + ftwbuf->base, "clone"))
return FTW_SKIP_SUBTREE;
if (typeflag != FTW_F || ftwbuf->level != 2 || sb->st_size == 0)
return FTW_CONTINUE;
for (n = 0; n < ARRAY_SIZE(collect_handlers); n++) {
h = collect_handlers + n;
if (!strcmp(fpath + ftwbuf->base, h->name))
break;
}
if (n >= ARRAY_SIZE(collect_handlers))
return FTW_CONTINUE;
buf = malloc(sb->st_size + 1); /* +EOL */
if (buf == NULL)
error(1, errno, "malloc()");
/* Ignore stale threads, but mark them as !parse_ok. */
fp = fopen(fpath, "r");
if (fp) {
if (fgets(buf, sb->st_size, fp)) {
parse_ok = true;
p = strrchr(buf, '\n');
if (p)
*p = '\0';
}
fclose(fp);
}
name = basename(dirname(strdup(fpath)));
entry.key = name;
entry.data = NULL;
e = hsearch(entry, FIND);
if (e == NULL) {
ti = malloc(sizeof(*ti));
if (ti == NULL)
error(1, errno, "malloc()");
memset(ti, 0, sizeof(*ti));
ti->name = name;
entry.data = ti;
hsearch(entry, ENTER);
ti->next = thread_list;
thread_list = ti;
thread_nr++;
} else
ti = e->data;
if (parse_ok && !h->handle(ti, buf))
parse_ok = false; /* wrong ABI format? */
ti->parse_ok = parse_ok;
free(buf);
return FTW_CONTINUE;
}
static int compare_info(const void *lhs, const void *rhs)
{
const struct thread_info *til = *((const struct thread_info **)lhs),
*tir = *((const struct thread_info **)rhs);
int ret;
switch (sort_key & ~SORT_REVERSE) {
case SORT_BY_PCPU:
ret = (int)(til->percent_cpu - tir->percent_cpu);
break;
case SORT_BY_ISW:
ret = (int)((long)til->nr_inbandsw - (long)tir->nr_inbandsw);
break;
case SORT_BY_CTXSW:
ret = (int)((long)til->nr_ctxsw - (long)tir->nr_ctxsw);
break;
case SORT_BY_RWA:
ret = (int)((long)til->nr_rwakeups - (long)tir->nr_rwakeups);
break;
case SORT_BY_CPUTIME:
ret = (int)((long long)til->cpu_time - (long)tir->cpu_time);
break;
default:
ret = (int)(til->pid - tir->pid); /* Defaults to pid */
}
if (sort_key & SORT_REVERSE)
ret = -ret;
return ret;
}
static void sort_thread_info(void)
{
struct thread_info **sarray, *ti, *sorted_list, **last;
int nr, n;
if (thread_nr == 0)
return;
sarray = malloc(thread_nr * sizeof(ti));
if (sarray == NULL)
error(1, errno, "malloc()");
/*
* We will be leaving as soon as the output is done, so let's
* leak memory happily for non-parsable entries.
*/
for (ti = thread_list, nr = 0; ti; ti = ti->next) {
if (ti->parse_ok)
sarray[nr++] = ti;
}
if (nr > 0) {
qsort(sarray, nr, sizeof(ti), compare_info);
sorted_list = NULL;
last = &sorted_list;
for (n = 0; n < nr; n++) {
ti = sarray[n];
*last = ti;
ti->next = NULL;
last = &ti->next;
}
thread_list = sorted_list;
} else
thread_list = NULL;
thread_nr = nr;
free(sarray);
}
static void collect_thread_info(void)
{
const char *sysfs = getenv("EVL_SYSDIR");
char *dir;
int ret;
if (sysfs == NULL)
error(1, EINVAL, "EVL_SYSDIR unset -- use 'evl ps' instead");
ret = asprintf(&dir, "%s/thread", sysfs);
if (ret < 0)
error(1, ENOMEM, "asprintf()");
ret = hcreate(2048);
if (!ret)
error(1, errno, "hcreate()");
ret = nftw(dir, scan_thread, 256, FTW_PHYS|FTW_ACTIONRETVAL);
if (ret)
error(1, errno, "cannot walk %s", dir);
free(dir);
}
struct display_handler {
const char *header;
const char *header_fmt;
void (*display_data)(struct thread_info *ti);
struct display_handler *next;
};
static void display_pid(struct thread_info *ti)
{
printf("%-6d", ti->pid);
}
static struct display_handler pid_handler = {
.header = "PID",
.header_fmt = "%-6s",
.display_data = display_pid,
};
static void display_name(struct thread_info *ti)
{
if (ti->state & T_USER)
printf("%s", ti->name);
else
printf("[%s]", ti->name);
}
static struct display_handler name_handler = { /* Always last. */
.header = "NAME",
.header_fmt = "%s",
.display_data = display_name,
};
static void display_cpu(struct thread_info *ti)
{
printf("%3d ", ti->cpu);
}
static struct display_handler cpu_handler = {
.header = "CPU",
.header_fmt = "%-6s",
.display_data = display_cpu,
};
static void display_policy(struct thread_info *ti)
{
printf("%5s ", ti->policy);
}
static struct display_handler policy_handler = {
.header = "SCHED",
.header_fmt = "%-8s",
.display_data = display_policy,
};
static void display_prio(struct thread_info *ti)
{
printf("%3d ", ti->cprio);
}
static struct display_handler prio_handler = {
.header = "PRIO",
.header_fmt = "%-6s",
.display_data = display_prio,
};
static void display_time(struct thread_info *ti)
{
int secs, msecs, usecs;
unsigned long long t;
secs = (int)(ti->cpu_time / ONE_BILLION);
t = ti->cpu_time % ONE_BILLION;
msecs = t / ONE_MILLION;
t = (int)(ti->cpu_time % ONE_MILLION);
usecs = (int)(t / 1000ULL);
printf("%.2d:%.3d.%.3d ", secs, msecs, usecs);
}
static struct display_handler time_handler = {
.header = "CPUTIME",
.header_fmt = "%-12s",
.display_data = display_time,
};
static void display_percent_cpu(struct thread_info *ti)
{
printf("%3u.%u ", ti->percent_cpu / 10, ti->percent_cpu % 10);
}
static struct display_handler percent_cpu_handler = {
.header = "%CPU",
.header_fmt = "%-7s",
.display_data = display_percent_cpu,
};
static void display_timeout(struct thread_info *ti)
{
const int bufsz = 32;
char buf[bufsz], *p = buf;
unsigned long long sec;
unsigned long ms, us;
int len = bufsz;
if (ti->timeout >= 1000) {
sec = ti->timeout / ONE_BILLION;
ms = (ti->timeout % ONE_BILLION) / ONE_MILLION;
us = (ti->timeout % ONE_MILLION) / 1000UL;
if (sec) {
p += snprintf(p, bufsz - (p - buf), "%Lu", sec);
len = bufsz - (p - buf);
}
if (len > 0 && (ms || (sec && us))) {
if (p > buf)
*p++ = ':';
p += snprintf(p, bufsz - (p - buf), "%.3lu", ms);
len = bufsz - (p - buf);
}
if (len > 0 && us) {
if (p > buf)
*p++ = '.';
p += snprintf(p, bufsz - (p - buf), "%.3lu", us);
}
} else
strcpy(buf, " -");
printf("%-13s", buf);
}
static struct display_handler timeout_handler = {
.header = "TIMEOUT",
.header_fmt = "%-13s",
.display_data = display_timeout,
};
static char *format_state(struct thread_info *ti, char *buf)
{
static const char labels[] = EVL_THREAD_STATE_LABELS;
unsigned int mask;
int flag, c;
char *wp;
for (mask = ti->state, flag = 0, wp = buf; mask; mask >>= 1, flag++) {
if (!(mask & 1))
continue;
c = labels[flag];
switch (1 << flag) {
case T_DELAY:
/*
* Only report genuine delays here, not timed
* waits for resources.
*/
if (ti->state & T_PEND)
continue;
break;
case T_PEND:
/* Report timed waits with lowercase symbol. */
if (ti->state & T_DELAY)
c |= 0x20;
break;
default:
if (c == '.')
continue;
}
*wp++ = c;
}
*wp = '\0';
return buf;
}
static void display_state(struct thread_info *ti)
{
char buf[CHAR_BIT * sizeof(int) + 1]; /* UINT_WIDTH+1 */
if (display_format & DISPLAY_NUMERIC)
printf(" %-8x", ti->state);
else
printf(" %-8s", format_state(ti, buf));
}
static struct display_handler state_handler = {
.header = "STAT",
.header_fmt = "%-9s",
.display_data = display_state,
};
static void display_inbandsw(struct thread_info *ti)
{
printf("%-8ld", ti->nr_inbandsw);
}
static struct display_handler inbandsw_handler = {
.header = "ISW",
.header_fmt = "%-8s",
.display_data = display_inbandsw,
};
static void display_ctxsw(struct thread_info *ti)
{
printf("%-10ld", ti->nr_ctxsw);
}
static struct display_handler ctxsw_handler = {
.header = "CTXSW",
.header_fmt = "%-10s",
.display_data = display_ctxsw,
};
static void display_syscall(struct thread_info *ti)
{
printf("%-10ld", ti->nr_syscalls);
}
static struct display_handler syscall_handler = {
.header = "SYS",
.header_fmt = "%-10s",
.display_data = display_syscall,
};
static void display_rwakeups(struct thread_info *ti)
{
printf("%-10ld", ti->nr_rwakeups);
}
static struct display_handler rwakeups_handler = {
.header = "RWA",
.header_fmt = "%-10s",
.display_data = display_rwakeups,
};
static void display_wchan(struct thread_info *ti)
{
printf("%-15s", ti->wchan);
}
static struct display_handler wchan_handler = {
.header = "WCHAN",
.header_fmt = "%-15s",
.display_data = display_wchan,
};
static void print_thread_info(void)
{
int nr_restrict = CPU_COUNT(&cpu_restrict);
struct display_handler *chain, *h;
struct thread_info *ti;
/*
* Prepare the display chain according to the format
* requested.
*/
chain = &cpu_handler;
cpu_handler.next = &pid_handler;
h = &pid_handler;
if (display_format & DISPLAY_SCHED) {
h->next = &policy_handler;
h = &policy_handler;
h->next = &prio_handler;
h = &prio_handler;
}
if (display_format & DISPLAY_STATE) {
h->next = &inbandsw_handler;
h = &inbandsw_handler;
h->next = &ctxsw_handler;
h = &ctxsw_handler;
h->next = &syscall_handler;
h = &syscall_handler;
h->next = &rwakeups_handler;
h = &rwakeups_handler;
h->next = &state_handler;
h = &state_handler;
}
if (display_format & DISPLAY_TIMES) {
h->next = &timeout_handler;
h = &timeout_handler;
h->next = &percent_cpu_handler;
h = &percent_cpu_handler;
h->next = &time_handler;
h = &time_handler;
}
if (display_format & DISPLAY_WAIT) {
h->next = &timeout_handler;
h = &timeout_handler;
h->next = &rwakeups_handler;
h = &rwakeups_handler;
h->next = &state_handler;
h = &state_handler;
h->next = &wchan_handler;
h = &wchan_handler;
}
h->next = &name_handler;
for (h = chain; h; h = h->next)
printf(h->header_fmt, h->header);
putchar('\n');
for (ti = thread_list; ti; ti = ti->next) {
if (nr_restrict > 0 && !CPU_ISSET(ti->cpu, &cpu_restrict))
continue;
for (h = chain; h; h = h->next) {
h->display_data(ti);
}
putchar('\n');
}
}
static void usage(char *arg0)
{
fprintf(stderr, "usage: %s [options]:\n", basename(arg0));
fprintf(stderr, "-c --cpu=<n>[,<n>] restrict report to CPU(s)\n");
fprintf(stderr, "-s --state display thread state\n");
fprintf(stderr, "-t --times display CPU utilization\n");
fprintf(stderr, "-p --policy display scheduling policy\n");
fprintf(stderr, "-w --wait display wait channel\n");
fprintf(stderr, "-l --long long format, same as -stp\n");
fprintf(stderr, "-n --numeric numeric output for STAT\n");
fprintf(stderr, "-S --sort=<c|i|t|x|w> sort key: c=%%CPU, i=ISW, t=CPUTIME, x=CTXSW, w=RWA\n");
fprintf(stderr, "-h --help this help\n");
}
static inline int resolve_cpuid(const char *s)
{
return isdigit(*s) ? atoi(s) : -1;
}
static void build_cpu_mask(const char *cpu_list)
{
char *s, *n, *range, *range_p = NULL, *id, *id_r;
int start, end, cpu, nr_cpus;
nr_cpus = (int)sysconf(_SC_NPROCESSORS_CONF);
if (nr_cpus < 0)
error(1, errno, "sysconf(_SC_NPROCESSORS_CONF)");
CPU_ZERO(&cpu_restrict);
s = n = strdup(cpu_list);
while ((range = strtok_r(n, ",", &range_p)) != NULL) {
if (*range == '\0' || *range == '\n')
goto next;
end = -1;
if (range[strlen(range)-1] == '-')
end = nr_cpus - 1;
id = strtok_r(range, "-", &id_r);
if (id) {
start = resolve_cpuid(id);
if (*range == '-') {
end = start;
start = 0;
}
id = strtok_r(NULL, "-", &id_r);
if (id)
end = resolve_cpuid(id);
else if (end < 0)
end = start;
if (start < 0 || start >= nr_cpus ||
end < 0 || end >= nr_cpus)
goto fail;
} else {
start = 0;
end = nr_cpus - 1;
}
for (cpu = start; cpu <= end; cpu++)
CPU_SET(cpu, &cpu_restrict);
next:
n = NULL;
}
free(s);
return;
fail:
error(1, EINVAL, "bad CPU number/range in '%s'", cpu_list);
free(s);
}
int main(int argc, char *const argv[])
{
bool reverse_sort = false;
const char *p;
int c;
opterr = 0;
for (;;) {
c = getopt_long(argc, argv, short_optlist, options, NULL);
if (c == EOF)
break;
switch (c) {
case 0:
break;
case 'c':
build_cpu_mask(optarg);
break;
case 's':
display_format |= DISPLAY_STATE;
break;
case 'p':
display_format |= DISPLAY_SCHED;
break;
case 't':
display_format |= DISPLAY_TIMES;
break;
case 'l':
display_format |= DISPLAY_LONG_FORMAT;
break;
case 'n':
display_format |= DISPLAY_NUMERIC;
break;
case 'w':
display_format = DISPLAY_WAIT;
break;
case 'S':
for (p = optarg; *p; p++) {
switch (*p) {
case 'c':
sort_key = SORT_BY_PCPU;
break;
case 'i':
sort_key = SORT_BY_ISW;
break;
case 't':
sort_key = SORT_BY_CPUTIME;
break;
case 'x':
sort_key = SORT_BY_CTXSW;
break;
case 'w':
sort_key = SORT_BY_RWA;
break;
case 'r':
reverse_sort = true;
break;
default:
error(1, EINVAL, "bad sort key '%c'", *p);
}
}
break;
case 'h':
usage(argv[0]);
return 0;
case '@':
printf("report a snapshot of the current EVL threads\n");
return 0;
default:
usage(argv[0]);
return 1;
}
}
if (optind < argc) {
usage(argv[0]);
return 1;
}
if (access(RROS_CONTROL_DEV, 0) && errno == ENOENT)
error(1, ENOSYS, "EVL core is not present/enabled");
if (!(display_format & ~DISPLAY_MODIFIERS))
display_format |= DISPLAY_DEFAULT_FORMAT;
if (reverse_sort)
sort_key |= SORT_REVERSE;
collect_thread_info();
sort_thread_info();
print_thread_info();
return 0;
}