352 lines
12 KiB
C++
352 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "tools/trace_to_text/trace_to_systrace.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <algorithm>
|
|
#include <cinttypes>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "perfetto/base/build_config.h"
|
|
#include "perfetto/base/logging.h"
|
|
#include "perfetto/ext/base/string_writer.h"
|
|
#include "perfetto/ext/base/utils.h"
|
|
#include "perfetto/trace_processor/trace_processor.h"
|
|
#include "tools/trace_to_text/utils.h"
|
|
|
|
#define FILTER_RAW_EVENTS \
|
|
" where not (name like \"chrome_event.%\" or name like \"track_event.%\")"
|
|
|
|
namespace perfetto {
|
|
namespace trace_to_text {
|
|
|
|
namespace {
|
|
|
|
const char kProcessDumpHeader[] =
|
|
"\"androidProcessDump\": "
|
|
"\"PROCESS DUMP\\nUSER PID PPID VSZ RSS WCHAN "
|
|
"PC S NAME COMM \\n";
|
|
|
|
const char kThreadHeader[] = "USER PID TID CMD \\n";
|
|
|
|
const char kProcessDumpFooter[] = "\"";
|
|
|
|
const char kSystemTraceEvents[] = " \"systemTraceEvents\": \"";
|
|
|
|
const char kFtraceHeader[] =
|
|
"# tracer: nop\n"
|
|
"#\n"
|
|
"# entries-in-buffer/entries-written: 30624/30624 #P:4\n"
|
|
"#\n"
|
|
"# _-----=> irqs-off\n"
|
|
"# / _----=> need-resched\n"
|
|
"# | / _---=> hardirq/softirq\n"
|
|
"# || / _--=> preempt-depth\n"
|
|
"# ||| / delay\n"
|
|
"# TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION\n"
|
|
"# | | | | |||| | |\n";
|
|
|
|
const char kFtraceJsonHeader[] =
|
|
"# tracer: nop\\n"
|
|
"#\\n"
|
|
"# entries-in-buffer/entries-written: 30624/30624 #P:4\\n"
|
|
"#\\n"
|
|
"# _-----=> irqs-off\\n"
|
|
"# / _----=> need-resched\\n"
|
|
"# | / _---=> hardirq/softirq\\n"
|
|
"# || / _--=> preempt-depth\\n"
|
|
"# ||| / delay\\n"
|
|
"# TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION\\n"
|
|
"# | | | | |||| | |\\n";
|
|
|
|
// The legacy trace viewer requires a clock sync marker to tie ftrace and
|
|
// userspace clocks together. Trace processor already aligned these clocks, so
|
|
// we just emit a clock sync for an equality mapping.
|
|
const char kSystemTraceEventsFooter[] =
|
|
"\\n<...>-12345 (-----) [000] ...1 0.000000: tracing_mark_write: "
|
|
"trace_event_clock_sync: parent_ts=0\\n\"";
|
|
|
|
inline void FormatProcess(uint32_t pid,
|
|
uint32_t ppid,
|
|
const base::StringView& name,
|
|
base::StringWriter* writer) {
|
|
writer->AppendLiteral("root ");
|
|
writer->AppendInt(pid);
|
|
writer->AppendLiteral(" ");
|
|
writer->AppendInt(ppid);
|
|
writer->AppendLiteral(" 00000 000 null 0000000000 S ");
|
|
writer->AppendString(name);
|
|
writer->AppendLiteral(" null");
|
|
}
|
|
|
|
inline void FormatThread(uint32_t tid,
|
|
uint32_t tgid,
|
|
const base::StringView& name,
|
|
base::StringWriter* writer) {
|
|
writer->AppendLiteral("root ");
|
|
writer->AppendInt(tgid);
|
|
writer->AppendChar(' ');
|
|
writer->AppendInt(tid);
|
|
writer->AppendChar(' ');
|
|
if (name.empty()) {
|
|
writer->AppendLiteral("<...>");
|
|
} else {
|
|
writer->AppendString(name);
|
|
}
|
|
}
|
|
|
|
class QueryWriter {
|
|
public:
|
|
QueryWriter(trace_processor::TraceProcessor* tp, TraceWriter* trace_writer)
|
|
: tp_(tp),
|
|
buffer_(base::PagedMemory::Allocate(kBufferSize)),
|
|
global_writer_(static_cast<char*>(buffer_.Get()), kBufferSize),
|
|
trace_writer_(trace_writer) {}
|
|
|
|
template <typename Callback>
|
|
bool RunQuery(const std::string& sql, Callback callback) {
|
|
char buffer[2048];
|
|
auto iterator = tp_->ExecuteQuery(sql);
|
|
for (uint32_t rows = 0; iterator.Next(); rows++) {
|
|
base::StringWriter line_writer(buffer, base::ArraySize(buffer));
|
|
callback(&iterator, &line_writer);
|
|
|
|
if (global_writer_.pos() + line_writer.pos() >= global_writer_.size()) {
|
|
fprintf(stderr, "Writing row %" PRIu32 "%c", rows, kProgressChar);
|
|
auto str = global_writer_.GetStringView();
|
|
trace_writer_->Write(str.data(), str.size());
|
|
global_writer_.reset();
|
|
}
|
|
global_writer_.AppendStringView(line_writer.GetStringView());
|
|
}
|
|
|
|
// Check if we have an error in the iterator and print if so.
|
|
auto status = iterator.Status();
|
|
if (!status.ok()) {
|
|
PERFETTO_ELOG("Error while writing systrace %s", status.c_message());
|
|
return false;
|
|
}
|
|
|
|
// Flush any dangling pieces in the global writer.
|
|
auto str = global_writer_.GetStringView();
|
|
trace_writer_->Write(str.data(), str.size());
|
|
global_writer_.reset();
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
static constexpr uint32_t kBufferSize = 1024u * 1024u * 16u;
|
|
|
|
trace_processor::TraceProcessor* tp_ = nullptr;
|
|
base::PagedMemory buffer_;
|
|
base::StringWriter global_writer_;
|
|
TraceWriter* trace_writer_;
|
|
};
|
|
|
|
int ExtractRawEvents(TraceWriter* trace_writer,
|
|
QueryWriter& q_writer,
|
|
bool wrapped_in_json,
|
|
Keep truncate_keep) {
|
|
using trace_processor::Iterator;
|
|
|
|
static const char kRawEventsCountSql[] =
|
|
"select count(1) from raw" FILTER_RAW_EVENTS;
|
|
uint32_t raw_events = 0;
|
|
auto e_callback = [&raw_events](Iterator* it, base::StringWriter*) {
|
|
raw_events = static_cast<uint32_t>(it->Get(0).long_value);
|
|
};
|
|
if (!q_writer.RunQuery(kRawEventsCountSql, e_callback))
|
|
return 1;
|
|
|
|
if (raw_events == 0) {
|
|
if (!wrapped_in_json) {
|
|
// Write out the normal header even if we won't actually have
|
|
// any events under it.
|
|
trace_writer->Write(kFtraceHeader);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
fprintf(stderr, "Converting ftrace events%c", kProgressChar);
|
|
fflush(stderr);
|
|
|
|
auto raw_callback = [wrapped_in_json](Iterator* it,
|
|
base::StringWriter* writer) {
|
|
const char* line = it->Get(0 /* col */).string_value;
|
|
if (wrapped_in_json) {
|
|
for (uint32_t i = 0; line[i] != '\0'; i++) {
|
|
char c = line[i];
|
|
switch (c) {
|
|
case '\n':
|
|
writer->AppendLiteral("\\n");
|
|
break;
|
|
case '\f':
|
|
writer->AppendLiteral("\\f");
|
|
break;
|
|
case '\b':
|
|
writer->AppendLiteral("\\b");
|
|
break;
|
|
case '\r':
|
|
writer->AppendLiteral("\\r");
|
|
break;
|
|
case '\t':
|
|
writer->AppendLiteral("\\t");
|
|
break;
|
|
case '\\':
|
|
writer->AppendLiteral("\\\\");
|
|
break;
|
|
case '"':
|
|
writer->AppendLiteral("\\\"");
|
|
break;
|
|
default:
|
|
writer->AppendChar(c);
|
|
break;
|
|
}
|
|
}
|
|
writer->AppendChar('\\');
|
|
writer->AppendChar('n');
|
|
} else {
|
|
writer->AppendString(line);
|
|
writer->AppendChar('\n');
|
|
}
|
|
};
|
|
|
|
// An estimate of 130b per ftrace event, allowing some space for the processes
|
|
// and threads.
|
|
const uint32_t max_ftrace_events = (140 * 1024 * 1024) / 130;
|
|
|
|
static const char kRawEventsQuery[] =
|
|
"select to_ftrace(id) from raw" FILTER_RAW_EVENTS;
|
|
|
|
// 1. Write the appropriate header for the file type.
|
|
if (wrapped_in_json) {
|
|
trace_writer->Write(",\n");
|
|
trace_writer->Write(kSystemTraceEvents);
|
|
trace_writer->Write(kFtraceJsonHeader);
|
|
} else {
|
|
trace_writer->Write(kFtraceHeader);
|
|
}
|
|
|
|
// 2. Write the actual events.
|
|
if (truncate_keep == Keep::kEnd && raw_events > max_ftrace_events) {
|
|
char end_truncate[150];
|
|
sprintf(end_truncate, "%s limit %d offset %d", kRawEventsQuery,
|
|
max_ftrace_events, raw_events - max_ftrace_events);
|
|
if (!q_writer.RunQuery(end_truncate, raw_callback))
|
|
return 1;
|
|
} else if (truncate_keep == Keep::kStart) {
|
|
char start_truncate[150];
|
|
sprintf(start_truncate, "%s limit %d", kRawEventsQuery, max_ftrace_events);
|
|
if (!q_writer.RunQuery(start_truncate, raw_callback))
|
|
return 1;
|
|
} else {
|
|
if (!q_writer.RunQuery(kRawEventsQuery, raw_callback))
|
|
return 1;
|
|
}
|
|
|
|
// 3. Write the footer for JSON.
|
|
if (wrapped_in_json)
|
|
trace_writer->Write(kSystemTraceEventsFooter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int TraceToSystrace(std::istream* input,
|
|
std::ostream* output,
|
|
bool ctrace,
|
|
Keep truncate_keep,
|
|
bool full_sort) {
|
|
std::unique_ptr<TraceWriter> trace_writer(
|
|
ctrace ? new DeflateTraceWriter(output) : new TraceWriter(output));
|
|
|
|
trace_processor::Config config;
|
|
config.sorting_mode = full_sort
|
|
? trace_processor::SortingMode::kForceFullSort
|
|
: trace_processor::SortingMode::kDefaultHeuristics;
|
|
std::unique_ptr<trace_processor::TraceProcessor> tp =
|
|
trace_processor::TraceProcessor::CreateInstance(config);
|
|
|
|
if (!ReadTrace(tp.get(), input))
|
|
return 1;
|
|
tp->NotifyEndOfFile();
|
|
|
|
if (ctrace)
|
|
*output << "TRACE:\n";
|
|
|
|
return ExtractSystrace(tp.get(), trace_writer.get(),
|
|
/*wrapped_in_json=*/false, truncate_keep);
|
|
}
|
|
|
|
int ExtractSystrace(trace_processor::TraceProcessor* tp,
|
|
TraceWriter* trace_writer,
|
|
bool wrapped_in_json,
|
|
Keep truncate_keep) {
|
|
using trace_processor::Iterator;
|
|
|
|
QueryWriter q_writer(tp, trace_writer);
|
|
if (wrapped_in_json) {
|
|
trace_writer->Write(kProcessDumpHeader);
|
|
|
|
// Write out all the processes in the trace.
|
|
// TODO(lalitm): change this query to actually use ppid when it is exposed
|
|
// by the process table.
|
|
static const char kPSql[] = "select pid, 0 as ppid, name from process";
|
|
auto p_callback = [](Iterator* it, base::StringWriter* writer) {
|
|
uint32_t pid = static_cast<uint32_t>(it->Get(0 /* col */).long_value);
|
|
uint32_t ppid = static_cast<uint32_t>(it->Get(1 /* col */).long_value);
|
|
const auto& name_col = it->Get(2 /* col */);
|
|
auto name_view = name_col.type == trace_processor::SqlValue::kString
|
|
? base::StringView(name_col.string_value)
|
|
: base::StringView();
|
|
FormatProcess(pid, ppid, name_view, writer);
|
|
};
|
|
if (!q_writer.RunQuery(kPSql, p_callback))
|
|
return 1;
|
|
|
|
trace_writer->Write(kThreadHeader);
|
|
|
|
// Write out all the threads in the trace.
|
|
static const char kTSql[] =
|
|
"select tid, COALESCE(upid, 0), thread.name "
|
|
"from thread left join process using (upid)";
|
|
auto t_callback = [](Iterator* it, base::StringWriter* writer) {
|
|
uint32_t tid = static_cast<uint32_t>(it->Get(0 /* col */).long_value);
|
|
uint32_t tgid = static_cast<uint32_t>(it->Get(1 /* col */).long_value);
|
|
const auto& name_col = it->Get(2 /* col */);
|
|
auto name_view = name_col.type == trace_processor::SqlValue::kString
|
|
? base::StringView(name_col.string_value)
|
|
: base::StringView();
|
|
FormatThread(tid, tgid, name_view, writer);
|
|
};
|
|
if (!q_writer.RunQuery(kTSql, t_callback))
|
|
return 1;
|
|
|
|
trace_writer->Write(kProcessDumpFooter);
|
|
}
|
|
return ExtractRawEvents(trace_writer, q_writer, wrapped_in_json,
|
|
truncate_keep);
|
|
}
|
|
|
|
} // namespace trace_to_text
|
|
} // namespace perfetto
|