blob: 0520f49d0d063eb0f1e6bc58d65d37a5da44fce9 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2020, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
#include "tracefs.h"
#define TRACEFS_SUITE "trasefs library"
#define TEST_INSTANCE_NAME "cunit_test_iter"
#define TEST_ARRAY_SIZE 500
static struct tracefs_instance *test_instance;
static struct tep_handle *test_tep;
struct test_sample {
int cpu;
int value;
};
static struct test_sample test_array[TEST_ARRAY_SIZE];
static int test_found;
static int test_callback(struct tep_event *event, struct tep_record *record,
int cpu, void *context)
{
struct tep_format_field *field;
struct test_sample *sample;
int *cpu_test = (int *)context;
int i;
if (cpu_test && *cpu_test >= 0 && *cpu_test != cpu)
return 0;
field = tep_find_field(event, "buf");
if (field) {
sample = ((struct test_sample *)(record->data + field->offset));
for (i = 0; i < TEST_ARRAY_SIZE; i++) {
if (test_array[i].value == sample->value &&
test_array[i].cpu == cpu) {
test_array[i].value = 0;
test_found++;
break;
}
}
}
return 0;
}
static void test_iter_write(void)
{
int cpus = sysconf(_SC_NPROCESSORS_CONF);
cpu_set_t *cpuset, *cpusave;
int cpu_size;
char *path;
int i, fd;
int ret;
cpuset = CPU_ALLOC(cpus);
cpusave = CPU_ALLOC(cpus);
cpu_size = CPU_ALLOC_SIZE(cpus);
CPU_ZERO_S(cpu_size, cpuset);
sched_getaffinity(0, cpu_size, cpusave);
path = tracefs_instance_get_file(test_instance, "trace_marker");
CU_TEST(path != NULL);
fd = open(path, O_WRONLY);
tracefs_put_tracing_file(path);
CU_TEST(fd >= 0);
for (i = 0; i < TEST_ARRAY_SIZE; i++) {
test_array[i].cpu = rand() % cpus;
test_array[i].value = random();
if (!test_array[i].value)
test_array[i].value++;
CU_TEST(test_array[i].cpu < cpus);
CPU_ZERO_S(cpu_size, cpuset);
CPU_SET(test_array[i].cpu, cpuset);
sched_setaffinity(0, cpu_size, cpuset);
ret = write(fd, test_array + i, sizeof(struct test_sample));
CU_TEST(ret == sizeof(struct test_sample));
}
sched_setaffinity(0, cpu_size, cpusave);
close(fd);
}
static void iter_raw_events_on_cpu(int cpu)
{
int check = 0;
int ret;
int i;
test_found = 0;
test_iter_write();
ret = tracefs_iterate_raw_events(test_tep, test_instance, NULL, 0,
test_callback, &cpu);
CU_TEST(ret == 0);
if (cpu < 0) {
CU_TEST(test_found == TEST_ARRAY_SIZE);
} else {
for (i = 0; i < TEST_ARRAY_SIZE; i++) {
if (test_array[i].cpu == cpu) {
check++;
CU_TEST(test_array[i].value == 0)
} else {
CU_TEST(test_array[i].value != 0)
}
}
CU_TEST(test_found == check);
}
}
static void test_iter_raw_events(void)
{
int cpus = sysconf(_SC_NPROCESSORS_CONF);
int ret;
int i;
ret = tracefs_iterate_raw_events(NULL, test_instance, NULL, 0, test_callback, NULL);
CU_TEST(ret < 0);
ret = tracefs_iterate_raw_events(test_tep, NULL, NULL, 0, test_callback, NULL);
CU_TEST(ret == 0);
ret = tracefs_iterate_raw_events(test_tep, test_instance, NULL, 0, NULL, NULL);
CU_TEST(ret < 0);
iter_raw_events_on_cpu(-1);
for (i = 0; i < cpus; i++)
iter_raw_events_on_cpu(i);
}
#define RAND_STR_SIZE 20
#define RAND_ASCII "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
static const char *get_rand_str(void)
{
static char str[RAND_STR_SIZE];
static char sym[] = RAND_ASCII;
struct timespec clk;
int i;
clock_gettime(CLOCK_REALTIME, &clk);
srand(clk.tv_nsec);
for (i = 0; i < RAND_STR_SIZE; i++)
str[i] = sym[rand() % (sizeof(sym) - 1)];
str[RAND_STR_SIZE - 1] = 0;
return str;
}
static void test_trace_file(void)
{
const char *tmp = get_rand_str();
const char *tdir;
struct stat st;
char *file;
tdir = tracefs_tracing_dir();
CU_TEST(tdir != NULL);
CU_TEST(stat(tdir, &st) == 0);
CU_TEST(S_ISDIR(st.st_mode));
file = tracefs_get_tracing_file(NULL);
CU_TEST(file == NULL);
file = tracefs_get_tracing_file(tmp);
CU_TEST(file != NULL);
CU_TEST(stat(file, &st) != 0);
tracefs_put_tracing_file(file);
file = tracefs_get_tracing_file("trace");
CU_TEST(file != NULL);
CU_TEST(stat(file, &st) == 0);
tracefs_put_tracing_file(file);
}
static void test_instance_file_read(struct tracefs_instance *inst, const char *fname)
{
const char *tdir = tracefs_tracing_dir();
char buf[BUFSIZ];
char *fpath;
char *file;
size_t fsize = 0;
int size = 0;
int fd;
if (inst) {
CU_TEST(asprintf(&fpath, "%s/instances/%s/%s",
tdir, tracefs_instance_get_name(inst), fname) > 0);
} else {
CU_TEST(asprintf(&fpath, "%s/%s", tdir, fname) > 0);
}
memset(buf, 0, BUFSIZ);
fd = open(fpath, O_RDONLY);
CU_TEST(fd >= 0);
fsize = read(fd, buf, BUFSIZ);
CU_TEST(fsize >= 0);
close(fd);
buf[BUFSIZ - 1] = 0;
file = tracefs_instance_file_read(inst, fname, &size);
CU_TEST(file != NULL);
CU_TEST(size == fsize);
CU_TEST(strcmp(file, buf) == 0);
free(fpath);
free(file);
}
#define ALL_TRACERS "available_tracers"
#define CUR_TRACER "current_tracer"
#define PER_CPU "per_cpu"
#define TRACE_ON "tracing_on"
static void test_instance_file(void)
{
struct tracefs_instance *instance = NULL;
struct tracefs_instance *second = NULL;
const char *name = get_rand_str();
const char *inst_name = NULL;
const char *tdir;
char *inst_file;
char *inst_dir;
struct stat st;
char *fname;
char *file1;
char *file2;
char *tracer;
int size;
int ret;
tdir = tracefs_tracing_dir();
CU_TEST(tdir != NULL);
CU_TEST(asprintf(&inst_dir, "%s/instances/%s", tdir, name) > 0);
CU_TEST(stat(inst_dir, &st) != 0);
CU_TEST(tracefs_instance_exists(name) == false);
instance = tracefs_instance_create(name);
CU_TEST(instance != NULL);
CU_TEST(tracefs_instance_is_new(instance));
second = tracefs_instance_create(name);
CU_TEST(second != NULL);
CU_TEST(!tracefs_instance_is_new(second));
tracefs_instance_free(second);
CU_TEST(tracefs_instance_exists(name) == true);
CU_TEST(stat(inst_dir, &st) == 0);
CU_TEST(S_ISDIR(st.st_mode));
inst_name = tracefs_instance_get_name(instance);
CU_TEST(inst_name != NULL);
CU_TEST(strcmp(inst_name, name) == 0);
fname = tracefs_instance_get_dir(NULL);
CU_TEST(fname != NULL);
CU_TEST(strcmp(fname, tdir) == 0);
free(fname);
fname = tracefs_instance_get_dir(instance);
CU_TEST(fname != NULL);
CU_TEST(strcmp(fname, inst_dir) == 0);
free(fname);
CU_TEST(asprintf(&fname, "%s/"ALL_TRACERS, tdir) > 0);
CU_TEST(fname != NULL);
inst_file = tracefs_instance_get_file(NULL, ALL_TRACERS);
CU_TEST(inst_file != NULL);
CU_TEST(strcmp(fname, inst_file) == 0);
tracefs_put_tracing_file(inst_file);
free(fname);
CU_TEST(asprintf(&fname, "%s/instances/%s/"ALL_TRACERS, tdir, name) > 0);
CU_TEST(fname != NULL);
CU_TEST(stat(fname, &st) == 0);
inst_file = tracefs_instance_get_file(instance, ALL_TRACERS);
CU_TEST(inst_file != NULL);
CU_TEST(strcmp(fname, inst_file) == 0);
test_instance_file_read(NULL, ALL_TRACERS);
test_instance_file_read(instance, ALL_TRACERS);
file1 = tracefs_instance_file_read(instance, ALL_TRACERS, NULL);
CU_TEST(file1 != NULL);
tracer = strtok(file1, " ");
CU_TEST(tracer != NULL);
ret = tracefs_instance_file_write(instance, CUR_TRACER, tracer);
CU_TEST(ret == strlen(tracer));
file2 = tracefs_instance_file_read(instance, CUR_TRACER, &size);
CU_TEST(file2 != NULL);
CU_TEST(size >= strlen(tracer));
CU_TEST(strncmp(file2, tracer, strlen(tracer)) == 0);
free(file1);
free(file2);
tracefs_put_tracing_file(inst_file);
free(fname);
CU_TEST(tracefs_file_exists(NULL, (char *)name) == false);
CU_TEST(tracefs_dir_exists(NULL, (char *)name) == false);
CU_TEST(tracefs_file_exists(instance, (char *)name) == false);
CU_TEST(tracefs_dir_exists(instance, (char *)name) == false);
CU_TEST(tracefs_file_exists(NULL, CUR_TRACER) == true);
CU_TEST(tracefs_dir_exists(NULL, CUR_TRACER) == false);
CU_TEST(tracefs_file_exists(instance, CUR_TRACER) == true);
CU_TEST(tracefs_dir_exists(instance, CUR_TRACER) == false);
CU_TEST(tracefs_file_exists(NULL, PER_CPU) == false);
CU_TEST(tracefs_dir_exists(NULL, PER_CPU) == true);
CU_TEST(tracefs_file_exists(instance, PER_CPU) == false);
CU_TEST(tracefs_dir_exists(instance, PER_CPU) == true);
CU_TEST(tracefs_instance_destroy(NULL) != 0);
CU_TEST(tracefs_instance_destroy(instance) == 0);
CU_TEST(tracefs_instance_destroy(instance) != 0);
tracefs_instance_free(instance);
CU_TEST(stat(inst_dir, &st) != 0);
free(inst_dir);
}
static bool check_fd_name(int fd, char *name)
{
char link[PATH_MAX + 1];
char path[PATH_MAX + 1];
struct stat st;
char *file;
int ret;
snprintf(link, PATH_MAX, "/proc/self/fd/%d", fd);
ret = lstat(link, &st);
CU_TEST(ret == 0);
if (ret < 0)
return false;
CU_TEST(S_ISLNK(st.st_mode));
if (!S_ISLNK(st.st_mode))
return false;
ret = readlink(link, path, PATH_MAX);
CU_TEST(ret > 0);
if (ret > PATH_MAX || ret < 0)
return false;
path[ret] = 0;
file = basename(path);
CU_TEST(file != NULL);
if (!file)
return false;
ret = strcmp(file, name);
CU_TEST(ret == 0);
if (ret)
return false;
return true;
}
#define FLAGS_STR "flags:"
static bool check_fd_mode(int fd, int mode)
{
char path[PATH_MAX + 1];
long fmode = -1;
char *line = NULL;
struct stat st;
size_t len = 0;
ssize_t size;
FILE *file;
int ret;
snprintf(path, PATH_MAX, "/proc/self/fdinfo/%d", fd);
ret = stat(path, &st);
CU_TEST(ret == 0);
if (ret < 0)
return false;
file = fopen(path, "r");
if (!file)
return false;
while ((size = getline(&line, &len, file)) > 0) {
if (strncmp(line, FLAGS_STR, strlen(FLAGS_STR)))
continue;
fmode = strtol(line + strlen(FLAGS_STR), NULL, 8);
break;
}
free(line);
fclose(file);
if (fmode < 0 ||
(O_ACCMODE & fmode) != (O_ACCMODE & mode))
return false;
return true;
}
static void test_instance_file_fd(void)
{
const char *name = get_rand_str();
long long res = -1;
char rd[2];
int fd;
fd = tracefs_instance_file_open(test_instance, name, -1);
CU_TEST(fd == -1);
fd = tracefs_instance_file_open(test_instance, TRACE_ON, O_RDONLY);
CU_TEST(fd >= 0);
CU_TEST(check_fd_name(fd, TRACE_ON));
CU_TEST(check_fd_mode(fd, O_RDONLY));
CU_TEST(tracefs_instance_file_read_number(test_instance, "available_tracer", &res) != 0);
CU_TEST(tracefs_instance_file_read_number(test_instance, name, &res) != 0);
CU_TEST(tracefs_instance_file_read_number(test_instance, TRACE_ON, &res) == 0);
CU_TEST((res == 0 || res == 1));
CU_TEST(read(fd, &rd, 1) == 1);
rd[1] = 0;
CU_TEST(res == atoi(rd));
close(fd);
}
static void test_tracing_onoff(void)
{
long long res = -1;
int fd;
fd = tracefs_trace_on_get_fd(test_instance);
CU_TEST(fd >= 0);
CU_TEST(check_fd_name(fd, TRACE_ON));
CU_TEST(check_fd_mode(fd, O_RDWR));
CU_TEST(tracefs_instance_file_read_number(test_instance, TRACE_ON, &res) == 0);
if (res == 1) {
CU_TEST(tracefs_trace_is_on(test_instance) == 1);
CU_TEST(tracefs_trace_off(test_instance) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 0);
CU_TEST(tracefs_trace_on(test_instance) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 1);
CU_TEST(tracefs_trace_off_fd(fd) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 0);
CU_TEST(tracefs_trace_on_fd(fd) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 1);
} else {
CU_TEST(tracefs_trace_is_on(test_instance) == 0);
CU_TEST(tracefs_trace_on(test_instance) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 1);
CU_TEST(tracefs_trace_off(test_instance) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 0);
CU_TEST(tracefs_trace_on_fd(fd) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 1);
CU_TEST(tracefs_trace_off_fd(fd) == 0);
CU_TEST(tracefs_trace_is_on(test_instance) == 0);
}
if (fd >= 0)
close(fd);
}
static void exclude_string(char **strings, char *name)
{
int i;
for (i = 0; strings[i]; i++) {
if (strcmp(strings[i], name) == 0) {
free(strings[i]);
strings[i] = strdup("/");
return;
}
}
}
static void test_check_files(const char *fdir, char **files)
{
struct dirent *dent;
DIR *dir;
int i;
dir = opendir(fdir);
CU_TEST(dir != NULL);
while ((dent = readdir(dir)))
exclude_string(files, dent->d_name);
closedir(dir);
for (i = 0; files[i]; i++)
CU_TEST(files[i][0] == '/');
}
static void test_system_event(void)
{
const char *tdir;
char **systems;
char **events;
char *sdir = NULL;
tdir = tracefs_tracing_dir();
CU_TEST(tdir != NULL);
systems = tracefs_event_systems(tdir);
CU_TEST(systems != NULL);
events = tracefs_system_events(tdir, systems[0]);
CU_TEST(events != NULL);
asprintf(&sdir, "%s/events/%s", tdir, systems[0]);
CU_TEST(sdir != NULL);
test_check_files(sdir, events);
free(sdir);
sdir = NULL;
asprintf(&sdir, "%s/events", tdir);
CU_TEST(sdir != NULL);
test_check_files(sdir, systems);
tracefs_list_free(systems);
tracefs_list_free(events);
free(sdir);
}
static void test_tracers(void)
{
const char *tdir;
char **tracers;
char *tfile;
char *tracer;
int i;
tdir = tracefs_tracing_dir();
CU_TEST(tdir != NULL);
tracers = tracefs_tracers(tdir);
CU_TEST(tracers != NULL);
tfile = tracefs_instance_file_read(NULL, ALL_TRACERS, NULL);
tracer = strtok(tfile, " ");
while (tracer) {
exclude_string(tracers, tracer);
tracer = strtok(NULL, " ");
}
for (i = 0; tracers[i]; i++)
CU_TEST(tracers[i][0] == '/');
tracefs_list_free(tracers);
free(tfile);
}
static void test_check_events(struct tep_handle *tep, char *system, bool exist)
{
struct dirent *dent;
char file[PATH_MAX];
char buf[1024];
char *edir = NULL;
const char *tdir;
DIR *dir;
int fd;
tdir = tracefs_tracing_dir();
CU_TEST(tdir != NULL);
asprintf(&edir, "%s/events/%s", tdir, system);
dir = opendir(edir);
CU_TEST(dir != NULL);
while ((dent = readdir(dir))) {
if (dent->d_name[0] == '.')
continue;
sprintf(file, "%s/%s/id", edir, dent->d_name);
fd = open(file, O_RDONLY);
if (fd < 0)
continue;
CU_TEST(read(fd, buf, 1024) > 0);
if (exist) {
CU_TEST(tep_find_event(tep, atoi(buf)) != NULL);
} else {
CU_TEST(tep_find_event(tep, atoi(buf)) == NULL);
}
close(fd);
}
closedir(dir);
free(edir);
}
static void test_local_events(void)
{
struct tep_handle *tep;
const char *tdir;
char **systems;
char *lsystems[3];
int i;
tdir = tracefs_tracing_dir();
CU_TEST(tdir != NULL);
tep = tracefs_local_events(tdir);
CU_TEST(tep != NULL);
systems = tracefs_event_systems(tdir);
CU_TEST(systems != NULL);
for (i = 0; systems[i]; i++)
test_check_events(tep, systems[i], true);
tep_free(tep);
memset(lsystems, 0, sizeof(lsystems));
for (i = 0; systems[i]; i++) {
if (!lsystems[0])
lsystems[0] = systems[i];
else if (!lsystems[2])
lsystems[2] = systems[i];
else
break;
}
if (lsystems[0] && lsystems[2]) {
tep = tracefs_local_events_system(tdir,
(const char * const *)lsystems);
CU_TEST(tep != NULL);
test_check_events(tep, lsystems[0], true);
test_check_events(tep, lsystems[2], false);
}
tep_free(tep);
tep = tep_alloc();
CU_TEST(tep != NULL);
CU_TEST(tracefs_fill_local_events(tdir, tep, NULL) == 0);
for (i = 0; systems[i]; i++)
test_check_events(tep, systems[i], true);
tep_free(tep);
tracefs_list_free(systems);
}
struct test_walk_instance {
struct tracefs_instance *instance;
bool found;
};
#define WALK_COUNT 10
int test_instances_walk_cb(const char *name, void *data)
{
struct test_walk_instance *instances = (struct test_walk_instance *)data;
int i;
CU_TEST(instances != NULL);
CU_TEST(name != NULL);
for (i = 0; i < WALK_COUNT; i++) {
if (!strcmp(name,
tracefs_instance_get_name(instances[i].instance))) {
instances[i].found = true;
break;
}
}
return 0;
}
static void test_instances_walk(void)
{
struct test_walk_instance instances[WALK_COUNT];
int i;
memset(instances, 0, WALK_COUNT * sizeof(struct test_walk_instance));
for (i = 0; i < WALK_COUNT; i++) {
instances[i].instance = tracefs_instance_create(get_rand_str());
CU_TEST(instances[i].instance != NULL);
}
CU_TEST(tracefs_instances_walk(test_instances_walk_cb, instances) == 0);
for (i = 0; i < WALK_COUNT; i++) {
CU_TEST(instances[i].found);
tracefs_instance_destroy(instances[i].instance);
instances[i].found = false;
}
CU_TEST(tracefs_instances_walk(test_instances_walk_cb, instances) == 0);
for (i = 0; i < WALK_COUNT; i++) {
CU_TEST(!instances[i].found);
tracefs_instance_free(instances[i].instance);
}
}
static void current_clock_check(const char *clock)
{
int size = 0;
char *clocks;
char *str;
clocks = tracefs_instance_file_read(test_instance, "trace_clock", &size);
CU_TEST(clocks != NULL);
CU_TEST(size > strlen(clock));
str = strstr(clocks, clock);
CU_TEST(str != NULL);
CU_TEST(str != clocks);
CU_TEST(*(str - 1) == '[');
CU_TEST(*(str + strlen(clock)) == ']');
free(clocks);
}
static void test_get_clock(void)
{
const char *clock;
clock = tracefs_get_clock(test_instance);
CU_TEST(clock != NULL);
current_clock_check(clock);
free((char *)clock);
}
static int test_suite_destroy(void)
{
tracefs_instance_destroy(test_instance);
tracefs_instance_free(test_instance);
tep_free(test_tep);
return 0;
}
static int test_suite_init(void)
{
const char *systems[] = {"ftrace", NULL};
test_tep = tracefs_local_events_system(NULL, systems);
if (test_tep == NULL)
return 1;
test_instance = tracefs_instance_create(TEST_INSTANCE_NAME);
if (!test_instance)
return 1;
return 0;
}
void test_tracefs_lib(void)
{
CU_pSuite suite = NULL;
suite = CU_add_suite(TRACEFS_SUITE, test_suite_init, test_suite_destroy);
if (suite == NULL) {
fprintf(stderr, "Suite \"%s\" cannot be ceated\n", TRACEFS_SUITE);
return;
}
CU_add_test(suite, "tracing file / directory APIs",
test_trace_file);
CU_add_test(suite, "instance file / directory APIs",
test_instance_file_fd);
CU_add_test(suite, "instance file descriptor",
test_instance_file);
CU_add_test(suite, "systems and events APIs",
test_system_event);
CU_add_test(suite, "tracefs_iterate_raw_events API",
test_iter_raw_events);
CU_add_test(suite, "tracefs_tracers API",
test_tracers);
CU_add_test(suite, "tracefs_local events API",
test_local_events);
CU_add_test(suite, "tracefs_instances_walk API",
test_instances_walk);
CU_add_test(suite, "tracefs_get_clock API",
test_get_clock);
CU_add_test(suite, "tracing on / off",
test_tracing_onoff);
}