blob: d87311a7b487ccfbf8dd684d9a6e746a9d13f7da [file] [log] [blame]
#include "cpumap.h"
#include "evsel.h"
#include "evlist.h"
#include "ras.h"
#include "debugfs.h"
#include "trace_event.h"
#include <regex.h>
#define POLL_TIMEOUT_MS 1000
#define EVENT_STR_MAX_LENGTH 1024
#define SYS_NAME_SEPARATOR ":"
#define RASD_CFG_FILE "rasd.cfg"
#define RASD_LOG_FILE "/var/log/rasd.log"
#define RASD_DATA_FILE "/var/log/rasd.data"
unsigned int page_size;
struct perf_evlist *evlist;
struct perf_rasd rasd;
FILE *log_file, *data_file;
/* Check if the read line is a valid config file line.*/
static bool cfg_valid_line(char *str)
{
regex_t reg;
int ret;
ret = regcomp(&reg, "^[:space:]*[a-z]+:[a-z_]+[:space:]*$",
REG_EXTENDED |
REG_NEWLINE); /* for matching $ */
if (ret)
err("compiling cfg regex");
ret = regexec(&reg, str, 0, NULL, 0);
if (ret) {
if (ret != REG_NOMATCH)
printf("error matching %s\n", str);
return false;
}
return true;
}
/*
* Extract the event subsystem and event names from the command
* line argument. The syntax is 'sys:name'.
*/
static int extract_sys_name(char **str, char **sys, char **name)
{
/* Look for the sys:name separator */
char *sys_ptr = strsep(str, SYS_NAME_SEPARATOR);
if (!*str || !sys_ptr)
return -1;
/* Is there something after the separator? */
if (!strlen(*str))
return -1;
/* Alloc and fill the sys part */
*sys = strndup(sys_ptr, EVENT_STR_MAX_LENGTH);
/*
* Look for the newline in the rest of the string.
* If found alloc and fill the name part.
*/
*name = strndup(strsep(str, "\n\r"), EVENT_STR_MAX_LENGTH);
return 0;
}
/* Add the requested tracepoint event to evlist */
static void add_tp_event(char *event_str)
{
struct perf_evsel *tp;
char **str = &event_str;
static int idx;
if (extract_sys_name(str, &rasd.sys, &rasd.name))
err("invalid event specified, syntax is sys:name");
/*
* Initialize tracepoint evsel from the event name and
* fill in the attr fields
*/
tp = perf_evsel__newtp_idx(rasd.sys, rasd.name, idx);
if (!tp)
err("init tracepoint evsel, idx: %d", idx);
/* Add the event to the lists of events, enable events ids */
perf_evlist__add(evlist, tp);
perf_evsel__set_sample_id(tp, false);
perf_evsel__calc_id_pos(tp);
idx++;
}
/*
* Read the config file for events to enable
*/
static int read_config_file(void)
{
FILE *file;
char *event_str;
int ret = 0;
file = fopen(RASD_CFG_FILE, "r");
if (!file)
return -ENODEV;
event_str = malloc(EVENT_STR_MAX_LENGTH);
if (!event_str)
return -ENOMEM;
/* Read config file, line by line */
while (fgets(event_str, EVENT_STR_MAX_LENGTH, file))
if (cfg_valid_line(event_str))
add_tp_event(event_str);
if (ferror(file))
ret = -EINVAL;
free(event_str);
fclose(file);
return ret;
}
static int mmap_read_idx(int idx)
{
struct perf_sample sample;
struct perf_evsel *evsel;
union perf_event *event;
int ret, nr_samples = 0;
while ((event = perf_evlist__mmap_read(evlist, idx))) {
ret = perf_evlist__parse_sample(evlist, event, &sample);
if (ret) {
pr_err("Can't parse sample, err = %d\n", ret);
goto next_event;
}
evsel = perf_evlist__id2evsel(evlist, sample.id);
if (!evsel)
goto next_event;
if (event->header.type == PERF_RECORD_SAMPLE)
nr_samples++;
else
goto next_event;
if (!evsel->tp_format)
goto next_event;
/* Parse raw data and print out the tracepoint */
event_format__print(evsel->tp_format, sample.cpu,
sample.raw_data, sample.raw_size);
next_event:
perf_evlist__mmap_consume(evlist, idx);
}
pr_debug("read %d samples\n", nr_samples);
return nr_samples;
}
static int open_log_files(void)
{
log_file = fopen(RASD_LOG_FILE, "a");
if (!log_file) {
fprintf(stderr, "Cannot open log file " RASD_LOG_FILE " for writing\n");
exit(EXIT_FAILURE);
}
data_file = fopen(RASD_DATA_FILE, "a");
if (!data_file) {
err("Cannot open data file " RASD_DATA_FILE " for writing");
goto err;
}
return 0;
err:
fclose(log_file);
return -1;
}
/* Run as a daemon: fork off the parent, detach from TTY etc. */
static void daemonize(void)
{
pid_t pid;
if (open_log_files())
exit(EXIT_FAILURE);
pid = fork();
if (pid < 0)
err("1st fork()");
else if (pid > 0)
/* Let the parent terminate */
exit(EXIT_SUCCESS);
/* The child process becomes session leader */
if (setsid() < 0)
err("setsid()");
/*
* Catch, ignore and handle signals, ignore for now
*/
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
/* Fork off for the second time to get rid of the TTY sessions */
pid = fork();
if (pid < 0)
err("2nd fork()");
else if (pid > 0)
exit(EXIT_SUCCESS);
/* Set new file permissions */
umask(0);
/* Change the working directory to the root directory */
if (!chdir("/"))
err("Cannot chdir");
fprintf(log_file, "\n*** rasd starting ***\n");
/* Close the standard file descriptors */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
int main()
{
struct perf_evsel *c;
struct thread_map *threads;
struct cpu_map *cpus;
int i;
daemonize();
page_size = sysconf(_SC_PAGE_SIZE);
if (!debugfs_mount(NULL))
err("mounting debugfs");
evlist = perf_evlist__new();
if (!evlist)
err("allocating evlist");
if (read_config_file())
err("error reading config file");
threads = thread_map__new(-1, getpid(), UINT_MAX);
if (!threads)
err("allocating threads_map\n");
cpus = cpu_map__new(NULL);
if (!cpus)
err("allocating cpu_map\n");
perf_evlist__set_maps(evlist, cpus, threads);
/* On all online CPUs by default, system wide tracing */
evlist__for_each(evlist, c) {
if (perf_evsel__open(c, evlist->cpus, NULL) < 0)
err("opening tracepoint, are you root?");
}
perf_evlist__set_id_pos(evlist);
/* mmap buffers */
if (perf_evlist__mmap(evlist, 4 /* opts->mmap_pages */, false) < 0)
err("Failed to mmap with %d (%s)\n", errno, strerror(errno));
/* Enable at kernel level */
perf_evlist__enable(evlist);
while (1) {
/*
* Relax the CPUs by polling for events coming in, with a
* timeout
*/
poll(evlist->pollfd, evlist->nr_fds, POLL_TIMEOUT_MS);
/* Read, parse and print out the events buffers */
for (i = 0; i < evlist->nr_mmaps; i++)
mmap_read_idx(i);
}
perf_evlist__delete(evlist);
free(rasd.name);
free(rasd.sys);
return 0;
}