You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
575 lines
15 KiB
575 lines
15 KiB
/*
|
|
* Copyright (c) 2018 Google, Inc.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "tst_res_flags.h"
|
|
#include "tst_safe_file_ops.h"
|
|
#include "tst_safe_macros.h"
|
|
|
|
#include "trace_parse.h"
|
|
|
|
#define TRACEFS_MAX_PATH_LEN 512
|
|
|
|
int num_trace_records = 0;
|
|
struct trace_record *trace = NULL;
|
|
|
|
static int trace_fd = -1;
|
|
static char *trace_buffer = NULL;
|
|
|
|
const char *tracefs_root = "/sys/kernel/tracing/";
|
|
const char *debugfs_tracefs_root = "/sys/kernel/debug/tracing/";
|
|
char *ftrace_root_dir;
|
|
|
|
static int parse_event_type(char *event_name)
|
|
{
|
|
if (!strcmp(event_name, "sched_process_exit"))
|
|
return TRACE_RECORD_SCHED_PROCESS_EXIT;
|
|
if (!strcmp(event_name, "sched_process_fork"))
|
|
return TRACE_RECORD_SCHED_PROCESS_FORK;
|
|
if (!strcmp(event_name, "sched_switch"))
|
|
return TRACE_RECORD_SCHED_SWITCH;
|
|
if (!strcmp(event_name, "sched_wakeup"))
|
|
return TRACE_RECORD_SCHED_WAKEUP;
|
|
if (!strcmp(event_name, "sugov_util_update"))
|
|
return TRACE_RECORD_SUGOV_UTIL_UPDATE;
|
|
if (!strcmp(event_name, "sugov_next_freq"))
|
|
return TRACE_RECORD_SUGOV_NEXT_FREQ;
|
|
if (!strcmp(event_name, "cpu_frequency"))
|
|
return TRACE_RECORD_CPU_FREQUENCY;
|
|
if (!strcmp(event_name, "tracing_mark_write"))
|
|
return TRACE_RECORD_TRACING_MARK_WRITE;
|
|
return -1;
|
|
}
|
|
|
|
void print_trace_record(struct trace_record *tr)
|
|
{
|
|
printf("task: %s pid %d cpu %d flags %d irq_context %c preempt_depth %d timestamp %d.%.6d event_type %d ",
|
|
tr->task, tr->pid, tr->cpu, tr->flags, tr->irq_context,
|
|
tr->preempt_depth, tr->ts.sec, tr->ts.usec,
|
|
tr->event_type);
|
|
if (tr->event_type == TRACE_RECORD_SCHED_PROCESS_EXIT)
|
|
printf("(sched_process_exit)\n");
|
|
else if (tr->event_type == TRACE_RECORD_SCHED_PROCESS_FORK)
|
|
printf("(sched_process_fork)\n");
|
|
else if (tr->event_type == TRACE_RECORD_SCHED_SWITCH) {
|
|
struct trace_sched_switch *t = tr->event_data;
|
|
printf("(sched_switch) %s pid=%d prio=%d state=%c => "
|
|
"%s pid=%d prio=%d\n", t->prev_comm, t->prev_pid,
|
|
t->prev_prio, t->prev_state, t->next_comm, t->next_pid,
|
|
t->next_prio);
|
|
} else if (tr->event_type == TRACE_RECORD_SCHED_WAKEUP) {
|
|
struct trace_sched_wakeup *t = tr->event_data;
|
|
printf("(sched_wakeup) %s pid=%d prio=%d cpu=%d\n",
|
|
t->comm, t->pid, t->prio, t->cpu);
|
|
} else if (tr->event_type == TRACE_RECORD_SUGOV_UTIL_UPDATE) {
|
|
struct trace_sugov_util_update *t = tr->event_data;
|
|
printf("(sugov_util_update) cpu=%d util=%d avg_cap=%d "
|
|
"max_cap=%d\n", t->cpu, t->util, t->avg_cap,
|
|
t->max_cap);
|
|
} else if (tr->event_type == TRACE_RECORD_SUGOV_NEXT_FREQ) {
|
|
struct trace_sugov_next_freq *t = tr->event_data;
|
|
printf("(sugov_next_freq) cpu=%d util=%d max=%d freq=%d\n",
|
|
t->cpu, t->util, t->max, t->freq);
|
|
} else if (tr->event_type == TRACE_RECORD_CPU_FREQUENCY) {
|
|
struct trace_cpu_frequency *t = tr->event_data;
|
|
printf("(cpu_frequency) state=%d cpu=%d\n",
|
|
t->state, t->cpu);
|
|
} else if (tr->event_type == TRACE_RECORD_TRACING_MARK_WRITE)
|
|
printf("(tracing_mark_write)\n");
|
|
else
|
|
printf("(other)\n");
|
|
}
|
|
|
|
void trace_setup(void)
|
|
{
|
|
struct stat buf;
|
|
|
|
if (stat("/sys/kernel/tracing/trace", &buf) == 0)
|
|
ftrace_root_dir = tracefs_root;
|
|
else
|
|
ftrace_root_dir = debugfs_tracefs_root;
|
|
|
|
}
|
|
|
|
void tracefs_write(const char *file_name, const char *value)
|
|
{
|
|
char buf[TRACEFS_MAX_PATH_LEN];
|
|
|
|
snprintf(buf, sizeof(buf), "%s%s", ftrace_root_dir, file_name);
|
|
SAFE_FILE_PRINTF(buf, value);
|
|
}
|
|
|
|
void trace_cleanup(void)
|
|
{
|
|
tracefs_write("tracing_on", "0");
|
|
|
|
}
|
|
|
|
static void *parse_event_data(unsigned int event_type, char *data)
|
|
{
|
|
if (event_type == TRACE_RECORD_TRACING_MARK_WRITE) {
|
|
char *buf = SAFE_MALLOC(strlen(data) + 1);
|
|
strcpy(buf, data);
|
|
return buf;
|
|
}
|
|
if (event_type == TRACE_RECORD_CPU_FREQUENCY) {
|
|
struct trace_cpu_frequency *t;
|
|
t = SAFE_MALLOC(sizeof(struct trace_cpu_frequency));
|
|
if (sscanf(data, "state=%d cpu_id=%hd", &t->state, &t->cpu)
|
|
!= 2) {
|
|
printf("Error parsing cpu_frequency event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
return t;
|
|
}
|
|
if (event_type == TRACE_RECORD_SUGOV_UTIL_UPDATE) {
|
|
struct trace_sugov_util_update *t;
|
|
t = SAFE_MALLOC(sizeof(struct trace_sugov_util_update));
|
|
if (sscanf(data, "cpu=%d util=%d avg_cap=%d max_cap=%d",
|
|
&t->cpu, &t->util, &t->avg_cap, &t->max_cap) != 4) {
|
|
printf("Error parsing sugov_util_update event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
return t;
|
|
}
|
|
if (event_type == TRACE_RECORD_SUGOV_NEXT_FREQ) {
|
|
struct trace_sugov_next_freq *t;
|
|
t = SAFE_MALLOC(sizeof(struct trace_sugov_next_freq));
|
|
if (sscanf(data, "cpu=%d util=%d max=%d freq=%d",
|
|
&t->cpu, &t->util, &t->max, &t->freq) != 4) {
|
|
printf("Error parsing sugov_next_freq event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
return t;
|
|
}
|
|
if (event_type == TRACE_RECORD_SCHED_SWITCH) {
|
|
struct trace_sched_switch *t;
|
|
char *tmp, *tmp2;
|
|
t = SAFE_MALLOC(sizeof(struct trace_sched_switch));
|
|
/*
|
|
* The prev_comm could have spaces in it. Find start of
|
|
* "prev_pid=" as that is just after end of prev_comm.
|
|
*/
|
|
if (strstr(data, "prev_comm=") != data) {
|
|
printf("Malformatted sched_switch event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
tmp = strstr(data, " prev_pid=");
|
|
if (!tmp) {
|
|
printf("Malformatted sched_switch event, "
|
|
"no prev_pid:\n%s\n", data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
*tmp = 0;
|
|
snprintf(t->prev_comm, sizeof(t->prev_comm), "%s", data + 10);
|
|
*tmp = ' ';
|
|
tmp++;
|
|
if (sscanf(tmp, "prev_pid=%hd prev_prio=%hd "
|
|
"prev_state=%c ==>\n",
|
|
&t->prev_pid, &t->prev_prio, &t->prev_state) != 3) {
|
|
printf("Malformatted sched_switch event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
tmp = strstr(tmp, "==> next_comm=");
|
|
if (!tmp) {
|
|
printf("Malformatted sched_switch event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
tmp += 14;
|
|
tmp2 = strstr(tmp, " next_pid=");
|
|
if (!tmp2) {
|
|
printf("Malformatted sched_switch event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
*tmp2 = 0;
|
|
if (strlen(tmp) > 16) {
|
|
printf("next_comm %s is greater than 16!\n",
|
|
tmp);
|
|
t->next_comm[0] = 0;
|
|
}
|
|
snprintf(t->next_comm, sizeof(t->next_comm), "%s", tmp);
|
|
*tmp2 = ' ';
|
|
tmp2++;
|
|
if (sscanf(tmp2, "next_pid=%hd next_prio=%hd", &t->next_pid,
|
|
&t->next_prio) != 2) {
|
|
printf("Malformatted sched_switch event:\n%s\n",
|
|
data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
return t;
|
|
}
|
|
if (event_type == TRACE_RECORD_SCHED_WAKEUP) {
|
|
struct trace_sched_wakeup *t;
|
|
char *tmp;
|
|
t = SAFE_MALLOC(sizeof(struct trace_sched_wakeup));
|
|
tmp = strstr(data, " pid=");
|
|
if (!tmp) {
|
|
printf("Malformatted sched_wakeup event:\n%s\n", data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
*tmp = 0;
|
|
snprintf(t->comm, sizeof(t->comm), "%s", data + 5);
|
|
*tmp = ' ';
|
|
if (sscanf(tmp, " pid=%hd prio=%hd target_cpu=%hd", &t->pid,
|
|
&t->prio, &t->cpu) != 3) {
|
|
printf("Malformatted sched_wakeup event:\n%s\n", data);
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
return t;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Given a line of text from a trace containing a trace record, parse the trace
|
|
* record into a struct trace_record.
|
|
* First 16 chars are the currently running thread name. Drop leading spaces.
|
|
* Next char is a dash
|
|
* Next 5 chars are PID. Drop trailing spaces.
|
|
* Next is some number of spaces.
|
|
* Next five chars are the CPU, i.e. [001].
|
|
* Next is some number of spaces.
|
|
* Next letter is whether IRQs are off.
|
|
* Next letter is if NEED_RESCHED is set.
|
|
* Next letter is if this is in hard or soft IRQ context.
|
|
* Next letter is the preempt disable depth.
|
|
* Next is some number of spaces.
|
|
* Next twelve letters are the timestamp.
|
|
* Next char is colon.
|
|
* Next char is space.
|
|
* Next twelve letters are the event name.
|
|
* Next char is colon.
|
|
* Rest of line is string specific to event.
|
|
*/
|
|
static int parse_trace_record(struct trace_record *tr, char *line) {
|
|
unsigned int field_start, idx = 0;
|
|
char *found;
|
|
|
|
/* Skip leading spaces in the task name. */
|
|
while (idx < 16 && line[idx] == ' ')
|
|
idx++;
|
|
if (idx == 16) {
|
|
printf("Malformatted trace record, no task name:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
memcpy(tr->task, &line[idx], 16-idx);
|
|
if (line[16] != '-') {
|
|
printf("Malformatted trace record, no dash after task "
|
|
"name:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
idx = 17;
|
|
while(line[idx] && line[idx] != '[') idx++;
|
|
if (!line[idx]) {
|
|
printf("Malformatted trace record, no open bracket for"
|
|
"CPU after pid:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
line[idx-1] = 0;
|
|
if (sscanf(&line[17], "%hd", &tr->pid) != 1) {
|
|
printf("Malformatted trace record, error parsing"
|
|
"pid:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
field_start = idx;
|
|
while(line[idx] && line[idx] != ']') idx++;
|
|
if (!line[idx]) {
|
|
printf("Malformatted trace record, no closing bracket for"
|
|
"CPU:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
idx++;
|
|
if (line[idx] != ' ') {
|
|
printf("Malformatted trace record, no space following CPU:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
line[idx] = 0;
|
|
idx++;
|
|
if (sscanf(&line[field_start], "[%hd]", &tr->cpu) != 1) {
|
|
printf("Malformatted trace record, error parsing CPU:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
|
|
if (line[idx] == 'd') {
|
|
tr->flags |= TRACE_RECORD_IRQS_OFF;
|
|
} else if (line[idx] != '.') {
|
|
printf("Malformatted trace record, error parsing irqs-off:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
idx++;
|
|
if (line[idx] == 'N') {
|
|
tr->flags |= TRACE_RECORD_TIF_NEED_RESCHED;
|
|
tr->flags |= TRACE_RECORD_PREEMPT_NEED_RESCHED;
|
|
} else if (line[idx] == 'n') {
|
|
tr->flags |= TRACE_RECORD_TIF_NEED_RESCHED;
|
|
} else if (line[idx] == 'p') {
|
|
tr->flags |= TRACE_RECORD_PREEMPT_NEED_RESCHED;
|
|
} else if (line[idx] != '.') {
|
|
printf("Malformatted trace record, error parsing "
|
|
"need-resched:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
idx++;
|
|
|
|
if (line[idx] != IRQ_CONTEXT_NORMAL && line[idx] != IRQ_CONTEXT_SOFT &&
|
|
line[idx] != IRQ_CONTEXT_HARD &&
|
|
line[idx] != IRQ_CONTEXT_HARD_IN_SOFT &&
|
|
line[idx] != IRQ_CONTEXT_NMI && line[idx] != IRQ_CONTEXT_NMI_IN_HARD) {
|
|
printf("Malformatted trace record, error parsing irq "
|
|
"context:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
tr->irq_context = line[idx];
|
|
idx++;
|
|
|
|
if (line[idx+1] != ' ') {
|
|
printf("Malformatted trace record, no space between"
|
|
"flags and timestamp:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
line[idx+1] = 0;
|
|
if (line[idx] == '.') {
|
|
tr->preempt_depth = 0;
|
|
} else if (sscanf(&line[idx], "%hx", &tr->preempt_depth) != 1) {
|
|
printf("Malformatted trace record, error parsing "
|
|
"preempt-depth:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
idx += 2;
|
|
|
|
while (line[idx] && line[idx] == ' ') idx++;
|
|
if (!line[idx]) {
|
|
printf("Malformatted trace record, missing timestamp:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
if (sscanf(&line[idx], "%d.%d: ", &tr->ts.sec,
|
|
&tr->ts.usec) != 2) {
|
|
printf("Malformatted trace record, error parsing "
|
|
"timestamp:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
/* we know a space appears in the string because sscanf parsed it */
|
|
found = strchr(&line[idx], ' ');
|
|
idx = (found - line + 1);
|
|
found = strchr(&line[idx], ':');
|
|
if (!found) {
|
|
printf("Malformatted trace record, error parsing "
|
|
"event name:\n");
|
|
printf("%s", line);
|
|
return -1;
|
|
}
|
|
*found = 0;
|
|
tr->event_type = parse_event_type(&line[idx]);
|
|
|
|
/*
|
|
* there is a space between the ':' after the event name and the event
|
|
* data
|
|
*/
|
|
if (tr->event_type > 0)
|
|
tr->event_data = parse_event_data(tr->event_type, found + 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define TRACE_BUFFER_SIZE 8192
|
|
static int refill_buffer(char *buffer, char *idx)
|
|
{
|
|
int bytes_in_buffer;
|
|
int bytes_to_read;
|
|
int bytes_read = 0;
|
|
int rv;
|
|
char buf[256];
|
|
|
|
bytes_in_buffer = TRACE_BUFFER_SIZE - (idx - buffer) - 1;
|
|
bytes_to_read = TRACE_BUFFER_SIZE - bytes_in_buffer - 1;
|
|
|
|
if (trace_fd == -1) {
|
|
snprintf(buf, sizeof(buf), "%strace", ftrace_root_dir);
|
|
|
|
trace_fd = open(buf, O_RDONLY);
|
|
if (trace_fd == -1) {
|
|
printf("Could not open trace file!\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Shift existing bytes in buffer to front. */
|
|
memmove(buffer, idx, bytes_in_buffer);
|
|
idx = buffer + bytes_in_buffer;
|
|
|
|
while(bytes_to_read) {
|
|
rv = read(trace_fd, idx, bytes_to_read);
|
|
if (rv == -1) {
|
|
printf("Could not read trace file! (%d)\n", errno);
|
|
return -1;
|
|
}
|
|
if (rv == 0)
|
|
break;
|
|
idx += rv;
|
|
bytes_read += rv;
|
|
bytes_to_read -= rv;
|
|
}
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
/*
|
|
* Returns a pointer to a null (not newline) terminated line
|
|
* of the trace.
|
|
*/
|
|
static char *read_trace_line(void)
|
|
{
|
|
static char *idx;
|
|
char *line, *newline;
|
|
int rv;
|
|
|
|
if (!trace_buffer) {
|
|
trace_buffer = SAFE_MALLOC(TRACE_BUFFER_SIZE);
|
|
idx = trace_buffer + TRACE_BUFFER_SIZE - 1;
|
|
*idx = 0;
|
|
}
|
|
|
|
line = idx;
|
|
newline = strchr(idx, '\n');
|
|
if (!newline) {
|
|
rv = refill_buffer(trace_buffer, idx);
|
|
if (rv == -1 || rv == 0)
|
|
return NULL;
|
|
idx = trace_buffer;
|
|
line = idx;
|
|
newline = strchr(idx, '\n');
|
|
}
|
|
if (newline) {
|
|
*newline = 0;
|
|
idx = newline + 1;
|
|
return line;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct trace_record *load_trace(void)
|
|
{
|
|
int buflines, unused;
|
|
int tmp_num_trace_records = 0;
|
|
char *line;
|
|
enum parse_state_type {
|
|
PARSE_STATE_SEEK_NUM_ENTRIES = 0,
|
|
PARSE_STATE_REMAINING_COMMENTS,
|
|
PARSE_STATE_TRACE_ENTRIES,
|
|
};
|
|
int parse_state = PARSE_STATE_SEEK_NUM_ENTRIES;
|
|
|
|
num_trace_records = 0;
|
|
|
|
#ifdef PRINT_PARSING_UPDATES
|
|
printf("\n");
|
|
#endif
|
|
while((line = read_trace_line())) {
|
|
if (line[0] == '#') {
|
|
if (parse_state == PARSE_STATE_TRACE_ENTRIES) {
|
|
printf("Malformatted trace output, comment"
|
|
"after start of trace entries.\n");
|
|
if (trace)
|
|
free(trace);
|
|
return NULL;
|
|
}
|
|
if (parse_state == PARSE_STATE_REMAINING_COMMENTS)
|
|
continue;
|
|
if (sscanf(line,
|
|
"# entries-in-buffer/entries-written: "
|
|
"%d/%d",
|
|
&buflines, &unused) != 2) {
|
|
continue;
|
|
}
|
|
|
|
trace = SAFE_MALLOC(sizeof(struct trace_record) *
|
|
buflines);
|
|
parse_state = PARSE_STATE_REMAINING_COMMENTS;
|
|
} else {
|
|
if (parse_state == PARSE_STATE_SEEK_NUM_ENTRIES) {
|
|
printf("Malformatted trace output, non-comment "
|
|
"before number of entries.\n");
|
|
if (trace)
|
|
free(trace);
|
|
return NULL;
|
|
}
|
|
|
|
if (parse_state == PARSE_STATE_REMAINING_COMMENTS)
|
|
parse_state = PARSE_STATE_TRACE_ENTRIES;
|
|
|
|
if (parse_trace_record(&trace[tmp_num_trace_records++],
|
|
line)) {
|
|
printf("Malformatted trace record entry:\n");
|
|
printf("%s\n", line);
|
|
if (trace)
|
|
free(trace);
|
|
return NULL;
|
|
}
|
|
#ifdef PRINT_PARSING_UPDATES
|
|
if (tmp_num_trace_records%1000 == 0) {
|
|
printf("\r%d/%d records processed",
|
|
tmp_num_trace_records, buflines);
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef PRINT_PARSING_UPDATES
|
|
printf("\n");
|
|
#endif
|
|
num_trace_records = tmp_num_trace_records;
|
|
if (trace_fd >= 0) {
|
|
close(trace_fd);
|
|
trace_fd = -1;
|
|
}
|
|
if (trace_buffer) {
|
|
free(trace_buffer);
|
|
trace_buffer = NULL;
|
|
}
|
|
return trace;
|
|
}
|