| #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(®, "^[:space:]*[a-z]+:[a-z_]+[:space:]*$", |
| REG_EXTENDED | |
| REG_NEWLINE); /* for matching $ */ |
| if (ret) |
| err("compiling cfg regex"); |
| |
| ret = regexec(®, 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; |
| } |