381 lines
8.0 KiB
C
381 lines
8.0 KiB
C
/*
|
|
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <dirent.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
|
|
#include "acrntrace.h"
|
|
|
|
#define TIMER_ID (128)
|
|
static uint32_t timeout = 0;
|
|
|
|
static int exiting = 0;
|
|
|
|
/* for opt */
|
|
static uint64_t period = 10000;
|
|
static const char optString[] = "i:hct:";
|
|
static const char dev_prefix[] = "acrn_trace_";
|
|
|
|
static uint32_t flags;
|
|
static char trace_file_dir[TRACE_FILE_DIR_LEN];
|
|
|
|
static reader_struct *reader;
|
|
static int pcpu_num = 0;
|
|
|
|
static void display_usage(void)
|
|
{
|
|
printf("acrntrace - tool to collect ACRN trace data\n"
|
|
"[Usage] acrntrace [-i period] [-t max_time] [-ch]\n\n"
|
|
"[Options]\n"
|
|
"\t-h: print this message\n"
|
|
"\t-i: period_in_ms: specify polling interval [1-999]\n"
|
|
"\t-t: max time to capture trace data (in second)\n"
|
|
"\t-c: clear the buffered old data\n");
|
|
}
|
|
|
|
static void timer_handler(union sigval sv)
|
|
{
|
|
exiting = 1;
|
|
exit(0);
|
|
}
|
|
|
|
static int init_timer(int timeout)
|
|
{
|
|
struct sigevent event;
|
|
struct itimerspec it;
|
|
timer_t tm_id;
|
|
int err;
|
|
|
|
memset(&event, 0, sizeof(struct sigevent));
|
|
|
|
event.sigev_value.sival_int = TIMER_ID;
|
|
event.sigev_notify = SIGEV_THREAD;
|
|
event.sigev_notify_function = timer_handler;
|
|
|
|
err = timer_create(CLOCK_MONOTONIC, &event, &tm_id);
|
|
if (err < 0) {
|
|
pr_err("Error to create timer, errno(%d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
it.it_interval.tv_sec = timeout;
|
|
it.it_interval.tv_nsec = 0;
|
|
it.it_value.tv_sec = timeout;
|
|
it.it_value.tv_nsec = 0;
|
|
|
|
err = timer_settime(tm_id, 0, &it, NULL);
|
|
if (err < 0) {
|
|
pr_err("Error to set timer, errno(%d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
pr_info("Capture trace data for about %ds and exit\n", timeout);
|
|
return 0;
|
|
}
|
|
|
|
static int parse_opt(int argc, char *argv[])
|
|
{
|
|
int opt, ret;
|
|
|
|
while ((opt = getopt(argc, argv, optString)) != -1) {
|
|
switch (opt) {
|
|
case 'i':
|
|
ret = atoi(optarg);
|
|
if (ret <= 0 || ret >=1000) {
|
|
pr_err("'-i' require integer between [1-999]\n");
|
|
return -EINVAL;
|
|
}
|
|
period = ret * 1000;
|
|
pr_dbg("Period is %lu\n", period);
|
|
break;
|
|
case 't':
|
|
ret = atoi(optarg);
|
|
if (ret <= 0) {
|
|
pr_err("'-t' require integer greater than 0\n");
|
|
return -EINVAL;
|
|
}
|
|
timeout = ret;
|
|
pr_dbg("Capture trace data for at most %ds\n", ret);
|
|
break;
|
|
case 'c':
|
|
flags |= FLAG_CLEAR_BUF;
|
|
break;
|
|
case 'h':
|
|
display_usage();
|
|
return -EINVAL;
|
|
default:
|
|
/* Undefined operation. */
|
|
display_usage();
|
|
return -EINVAL;
|
|
}
|
|
};
|
|
return 0;
|
|
}
|
|
|
|
static int get_cpu_num(void)
|
|
{
|
|
struct dirent *pdir;
|
|
int cpu_num = 0;
|
|
char *ret;
|
|
DIR *dir;
|
|
|
|
dir = opendir("/dev");
|
|
if (!dir) {
|
|
printf("Error opening /dev: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
while ((pdir = readdir(dir)) != NULL) {
|
|
ret = strstr(pdir->d_name, dev_prefix);
|
|
if (ret)
|
|
cpu_num++;
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
return cpu_num;
|
|
}
|
|
|
|
static int create_trace_file_dir(char *dir)
|
|
{
|
|
int err = 0, ret;
|
|
char time_str[TIME_STR_LEN + 1];
|
|
time_t timep;
|
|
struct tm *p;
|
|
struct stat st;
|
|
|
|
time(&timep);
|
|
p = localtime(&timep);
|
|
if (p) {
|
|
ret = snprintf(time_str, TIME_STR_LEN + 1,
|
|
"%04d%02d%02d-%02d%02d%02d",
|
|
(1900 + p->tm_year), (1 + p->tm_mon),
|
|
p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
|
|
if (ret > TIME_STR_LEN)
|
|
printf("WARN: time_str may be truncated\n");
|
|
} else
|
|
snprintf(time_str, TIME_STR_LEN, "00000000-000000");
|
|
|
|
pr_info("start tracing at %s\n", time_str);
|
|
|
|
if (stat(TRACE_FILE_ROOT, &st)) {
|
|
err = mkdir(TRACE_FILE_ROOT, 0644);
|
|
if (err) {
|
|
pr_err("Fail to create dir %s, Error: %s\n",
|
|
TRACE_FILE_ROOT, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ret = snprintf(dir, TRACE_FILE_DIR_LEN, "%s%s",
|
|
TRACE_FILE_ROOT, time_str);
|
|
if (ret < (strlen(TRACE_FILE_ROOT) + strlen(time_str)))
|
|
printf("WARN: trace file dir name is truncated\n");
|
|
if (stat(dir, &st)) {
|
|
err = mkdir(dir, 0644);
|
|
if (err) {
|
|
pr_err("Fail to create dir %s, Error: %s\n",
|
|
dir, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
pr_dbg("dir %s creted\n", dir);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* function executed in each consumer thread */
|
|
static void reader_fn(param_t * param)
|
|
{
|
|
int ret;
|
|
int fd = param->trace_fd;
|
|
shared_buf_t *sbuf = param->sbuf;
|
|
|
|
pr_dbg("reader thread[%lu] created for FILE*[0x%p]\n",
|
|
pthread_self(), fp);
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
|
|
|
|
/* Clear the old data in sbuf */
|
|
if (flags & FLAG_CLEAR_BUF)
|
|
sbuf_clear_buffered(sbuf);
|
|
|
|
while (1) {
|
|
do {
|
|
ret = sbuf_write(fd, sbuf);
|
|
} while (ret > 0);
|
|
|
|
usleep(period);
|
|
}
|
|
}
|
|
|
|
static int create_reader(reader_struct * reader, uint32_t cpu)
|
|
{
|
|
char trace_file_name[TRACE_FILE_NAME_LEN];
|
|
|
|
snprintf(reader->dev_name, DEV_PATH_LEN, "/dev/%s%u", dev_prefix, cpu);
|
|
reader->param.cpuid = cpu;
|
|
|
|
reader->dev_fd = open(reader->dev_name, O_RDWR);
|
|
if (reader->dev_fd < 0) {
|
|
pr_err("Failed to open %s, err %d\n", reader->dev_name, errno);
|
|
reader->dev_fd = 0;
|
|
return -1;
|
|
}
|
|
|
|
reader->param.sbuf = mmap(NULL, MMAP_SIZE,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_SHARED, reader->dev_fd, 0);
|
|
if (reader->param.sbuf == MAP_FAILED) {
|
|
pr_err("mmap failed for cpu%d, errno %d\n", cpu, errno);
|
|
reader->param.sbuf = NULL;
|
|
return -2;
|
|
}
|
|
|
|
pr_dbg("sbuf[%d]:\nmagic_num: %lx\nele_num: %u\n ele_size: %u\n",
|
|
cpu, reader->param.sbuf->magic, reader->param.sbuf->ele_num,
|
|
reader->param.sbuf->ele_size);
|
|
|
|
snprintf(trace_file_name, TRACE_FILE_NAME_LEN, "%s/%d", trace_file_dir,
|
|
cpu);
|
|
reader->param.trace_fd = open(trace_file_name,
|
|
O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
if (!reader->param.trace_fd) {
|
|
pr_err("Failed to open %s, err %d\n", trace_file_name, errno);
|
|
return -3;
|
|
}
|
|
|
|
pr_info("trace data file %s created for %s\n",
|
|
trace_file_name, reader->dev_name);
|
|
|
|
if (pthread_create(&reader->thrd, NULL,
|
|
(void *)&reader_fn, &reader->param)) {
|
|
pr_err("failed to create reader thread, %d\n", cpu);
|
|
return -4;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void destory_reader(reader_struct * reader)
|
|
{
|
|
if (reader->thrd) {
|
|
pthread_cancel(reader->thrd);
|
|
if (pthread_join(reader->thrd, NULL) != 0)
|
|
pr_err("failed to cancel thread[%lu]\n", reader->thrd);
|
|
else
|
|
reader->thrd = 0;
|
|
}
|
|
|
|
if (reader->param.sbuf) {
|
|
munmap(reader->param.sbuf, MMAP_SIZE);
|
|
reader->param.sbuf = NULL;
|
|
}
|
|
|
|
if (reader->dev_fd) {
|
|
close(reader->dev_fd);
|
|
reader->dev_fd = 0;
|
|
}
|
|
|
|
if (reader->param.trace_fd) {
|
|
close(reader->param.trace_fd);
|
|
}
|
|
}
|
|
|
|
static void handle_on_exit(void)
|
|
{
|
|
uint32_t cpu;
|
|
|
|
/* if nothing to release */
|
|
if (!(flags & FLAG_TO_REL))
|
|
return;
|
|
|
|
pr_info("exiting - to release resources...\n");
|
|
|
|
foreach_cpu(cpu)
|
|
destory_reader(&reader[cpu]);
|
|
}
|
|
|
|
static void signal_exit_handler(int sig)
|
|
{
|
|
pr_info("exit on signal %d\n", sig);
|
|
exit(0);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
uint32_t cpu = 0;
|
|
int err;
|
|
|
|
/* parse options */
|
|
if (parse_opt(argc, argv))
|
|
exit(EXIT_FAILURE);
|
|
|
|
/* how many cpus */
|
|
pcpu_num = get_cpu_num();
|
|
reader = calloc(1, sizeof(reader_struct) * pcpu_num);
|
|
if (!reader) {
|
|
pr_err("Failed to allocate reader memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* create dir for trace file */
|
|
if (create_trace_file_dir(trace_file_dir)) {
|
|
pr_err("Failed to create dir for trace files\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Set timer if timeout configured */
|
|
if (timeout) {
|
|
err = init_timer(timeout);
|
|
if (err < 0) {
|
|
pr_err("Failed to set timer\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
atexit(handle_on_exit);
|
|
|
|
/* acquair res for each trace dev */
|
|
flags |= FLAG_TO_REL;
|
|
foreach_cpu(cpu)
|
|
if (create_reader(&reader[cpu], cpu) < 0)
|
|
goto out_free;
|
|
|
|
/* for kill exit handling */
|
|
signal(SIGTERM, signal_exit_handler);
|
|
signal(SIGINT, signal_exit_handler);
|
|
|
|
/* wait for user input to stop */
|
|
printf("q <enter> to quit:\n");
|
|
while (!exiting && getchar() != 'q')
|
|
printf("q <enter> to quit:\n");
|
|
|
|
out_free:
|
|
foreach_cpu(cpu)
|
|
destory_reader(&reader[cpu]);
|
|
|
|
free(reader);
|
|
flags &= ~FLAG_TO_REL;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|