trace_processor: add support for systrace output

Bug: 122513680
Change-Id: I399a4a52f6db4da3d56ae949dbcff1ab984b6b2e
diff --git a/Android.bp b/Android.bp
index a002229..72ca25d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1662,6 +1662,47 @@
   ],
 }
 
+// GN target: //protos/perfetto/trace_processor:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_processor_lite_gen",
+  srcs: [
+    "protos/perfetto/trace_processor/raw_query.proto",
+    "protos/perfetto/trace_processor/sched.proto",
+    "protos/perfetto/trace_processor/trace_processor.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace_processor/raw_query.pb.cc",
+    "external/perfetto/protos/perfetto/trace_processor/sched.pb.cc",
+    "external/perfetto/protos/perfetto/trace_processor/trace_processor.pb.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/trace_processor:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_processor_lite_gen_headers",
+  srcs: [
+    "protos/perfetto/trace_processor/raw_query.proto",
+    "protos/perfetto/trace_processor/sched.proto",
+    "protos/perfetto/trace_processor/trace_processor.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace_processor/raw_query.pb.h",
+    "external/perfetto/protos/perfetto/trace_processor/sched.pb.h",
+    "external/perfetto/protos/perfetto/trace_processor/trace_processor.pb.h",
+  ],
+  export_include_dirs: [
+    "protos",
+  ],
+}
+
 // GN target: //protos/perfetto/trace/profiling:lite_gen
 genrule {
   name: "perfetto_protos_perfetto_trace_profiling_lite_gen",
@@ -2816,6 +2857,7 @@
     ":perfetto_protos_perfetto_trace_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_power_lite_gen",
+    ":perfetto_protos_perfetto_trace_processor_lite_gen",
     ":perfetto_protos_perfetto_trace_profiling_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
@@ -2833,6 +2875,49 @@
     "src/base/unix_task_runner.cc",
     "src/base/virtual_destructors.cc",
     "src/base/watchdog_posix.cc",
+    "src/protozero/message.cc",
+    "src/protozero/message_handle.cc",
+    "src/protozero/proto_decoder.cc",
+    "src/protozero/proto_field_descriptor.cc",
+    "src/protozero/scattered_heap_buffer.cc",
+    "src/protozero/scattered_stream_null_delegate.cc",
+    "src/protozero/scattered_stream_writer.cc",
+    "src/trace_processor/android_logs_table.cc",
+    "src/trace_processor/args_table.cc",
+    "src/trace_processor/args_tracker.cc",
+    "src/trace_processor/clock_tracker.cc",
+    "src/trace_processor/counters_table.cc",
+    "src/trace_processor/event_tracker.cc",
+    "src/trace_processor/filtered_row_index.cc",
+    "src/trace_processor/ftrace_descriptors.cc",
+    "src/trace_processor/ftrace_utils.cc",
+    "src/trace_processor/instants_table.cc",
+    "src/trace_processor/process_table.cc",
+    "src/trace_processor/process_tracker.cc",
+    "src/trace_processor/proto_trace_parser.cc",
+    "src/trace_processor/proto_trace_tokenizer.cc",
+    "src/trace_processor/query_constraints.cc",
+    "src/trace_processor/raw_table.cc",
+    "src/trace_processor/row_iterators.cc",
+    "src/trace_processor/sched_slice_table.cc",
+    "src/trace_processor/slice_table.cc",
+    "src/trace_processor/slice_tracker.cc",
+    "src/trace_processor/span_join_operator_table.cc",
+    "src/trace_processor/sql_stats_table.cc",
+    "src/trace_processor/stats_table.cc",
+    "src/trace_processor/storage_columns.cc",
+    "src/trace_processor/storage_schema.cc",
+    "src/trace_processor/storage_table.cc",
+    "src/trace_processor/string_table.cc",
+    "src/trace_processor/table.cc",
+    "src/trace_processor/thread_table.cc",
+    "src/trace_processor/trace_processor.cc",
+    "src/trace_processor/trace_processor_context.cc",
+    "src/trace_processor/trace_processor_impl.cc",
+    "src/trace_processor/trace_sorter.cc",
+    "src/trace_processor/trace_storage.cc",
+    "src/trace_processor/virtual_destructors.cc",
+    "src/trace_processor/window_operator_table.cc",
     "tools/trace_to_text/ftrace_event_formatter.cc",
     "tools/trace_to_text/main.cc",
     "tools/trace_to_text/proto_full_utils.cc",
@@ -2848,6 +2933,7 @@
   ],
   static_libs: [
     "libgtest_prod",
+    "libsqlite",
   ],
   generated_headers: [
     "perfetto_protos_perfetto_common_lite_gen_headers",
@@ -2859,6 +2945,7 @@
     "perfetto_protos_perfetto_trace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_power_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_processor_lite_gen_headers",
     "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
diff --git a/include/perfetto/base/scoped_file.h b/include/perfetto/base/scoped_file.h
index 59ca75f..0329003 100644
--- a/include/perfetto/base/scoped_file.h
+++ b/include/perfetto/base/scoped_file.h
@@ -100,6 +100,12 @@
 
 using ScopedFstream = ScopedResource<FILE*, fclose, nullptr>;
 
+inline int FreeString(char* ptr) {
+  free(ptr);
+  return 0;
+}
+using ScopedString = ScopedResource<char*, FreeString, nullptr>;
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/base/string_writer.h b/include/perfetto/base/string_writer.h
index 73b0b13..c438626 100644
--- a/include/perfetto/base/string_writer.h
+++ b/include/perfetto/base/string_writer.h
@@ -21,6 +21,7 @@
 #include <limits>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/scoped_file.h"
 #include "perfetto/base/string_view.h"
 
 namespace perfetto {
@@ -111,6 +112,11 @@
     return buffer_;
   }
 
+  // Creates a copy of the internal buffer.
+  base::ScopedString CreateStringCopy() {
+    return base::ScopedString(strndup(buffer_, pos_));
+  }
+
  private:
   char* buffer_ = nullptr;
   size_t size_ = 0;
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 6981806..c64b392 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -123,6 +123,7 @@
     "../../gn:default_deps",
     "../../include/perfetto/traced:sys_stats_counters",
     "../../protos/perfetto/trace:lite",
+    "../../protos/perfetto/trace/ftrace:lite",
     "../../protos/perfetto/trace_processor:lite",
     "../base",
     "../protozero",
diff --git a/src/trace_processor/ftrace_utils.cc b/src/trace_processor/ftrace_utils.cc
index d3cc831..8ca1657 100644
--- a/src/trace_processor/ftrace_utils.cc
+++ b/src/trace_processor/ftrace_utils.cc
@@ -16,14 +16,26 @@
 
 #include "src/trace_processor/ftrace_utils.h"
 
+#include <stdint.h>
 #include <algorithm>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/string_writer.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace ftrace_utils {
 
+namespace {
+struct FtraceTime {
+  FtraceTime(int64_t ns)
+      : secs(ns / 1000000000LL), micros((ns - secs * 1000000000LL) / 1000) {}
+
+  const int64_t secs;
+  const int64_t micros;
+};
+}  // namespace
+
 TaskState::TaskState(const char* state_str) {
   bool invalid_char = false;
   bool is_runnable = false;
@@ -138,6 +150,37 @@
   return output;
 }
 
+void FormatSystracePrefix(int64_t timestamp,
+                          uint32_t cpu,
+                          uint32_t pid,
+                          uint32_t tgid,
+                          base::StringView name,
+                          base::StringWriter* writer) {
+  FtraceTime ftrace_time(timestamp);
+  if (pid == 0) {
+    name = "<idle>";
+  }
+
+  writer->AppendString(name);
+  writer->AppendChar('-');
+  writer->AppendInt(pid);
+
+  writer->AppendLiteral("     (");
+  if (tgid == 0) {
+    writer->AppendLiteral("-----");
+  } else {
+    writer->AppendPaddedInt<' ', 5>(tgid);
+  }
+  writer->AppendLiteral(") [");
+  writer->AppendPaddedInt<'0', 3>(cpu);
+  writer->AppendLiteral("] .... ");
+
+  writer->AppendInt(ftrace_time.secs);
+  writer->AppendChar('.');
+  writer->AppendPaddedInt<'0', 6>(ftrace_time.micros);
+  writer->AppendChar(':');
+}
+
 }  // namespace ftrace_utils
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/ftrace_utils.h b/src/trace_processor/ftrace_utils.h
index e89904e..0bff23c 100644
--- a/src/trace_processor/ftrace_utils.h
+++ b/src/trace_processor/ftrace_utils.h
@@ -18,10 +18,11 @@
 #define SRC_TRACE_PROCESSOR_FTRACE_UTILS_H_
 
 #include <stddef.h>
-
 #include <array>
 
-#include "perfetto/base/optional.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/string_view.h"
+#include "perfetto/base/string_writer.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -82,6 +83,13 @@
   uint16_t state_ = 0;
 };
 
+void FormatSystracePrefix(int64_t timestamp,
+                          uint32_t cpu,
+                          uint32_t pid,
+                          uint32_t tgid,
+                          base::StringView name,
+                          base::StringWriter* writer);
+
 }  // namespace ftrace_utils
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/raw_table.cc b/src/trace_processor/raw_table.cc
index 22e335c..0585e59 100644
--- a/src/trace_processor/raw_table.cc
+++ b/src/trace_processor/raw_table.cc
@@ -16,12 +16,26 @@
 
 #include "src/trace_processor/raw_table.h"
 
+#include <inttypes.h>
+
+#include "src/trace_processor/ftrace_descriptors.h"
 #include "src/trace_processor/sqlite_utils.h"
 
+#include "perfetto/trace/ftrace/ftrace_event.pb.h"
+
 namespace perfetto {
 namespace trace_processor {
 
-RawTable::RawTable(sqlite3*, const TraceStorage* storage) : storage_(storage) {}
+RawTable::RawTable(sqlite3* db, const TraceStorage* storage)
+    : storage_(storage) {
+  auto fn = [](sqlite3_context* ctx, int argc, sqlite3_value** argv) {
+    auto* thiz = static_cast<RawTable*>(sqlite3_user_data(ctx));
+    thiz->ToSystrace(ctx, argc, argv);
+  };
+  sqlite3_create_function(db, "to_ftrace", 1,
+                          SQLITE_UTF8 | SQLITE_DETERMINISTIC, this, fn, nullptr,
+                          nullptr);
+}
 
 void RawTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
   Table::Register<RawTable>(db, storage, "raw");
@@ -56,5 +70,105 @@
   return SQLITE_OK;
 }
 
+void RawTable::FormatSystraceArgs(const std::string& event_name,
+                                  ArgSetId arg_set_id,
+                                  base::StringWriter* writer) {
+  const auto& set_ids = storage_->args().set_ids();
+  auto lb = std::lower_bound(set_ids.begin(), set_ids.end(), arg_set_id);
+  auto ub = std::find(lb, set_ids.end(), arg_set_id + 1);
+
+  auto start_row = static_cast<uint32_t>(std::distance(set_ids.begin(), lb));
+
+  using Variadic = TraceStorage::Args::Variadic;
+  using ValueWriter = std::function<void(const Variadic&)>;
+  auto write_value = [this, writer](const Variadic& value) {
+    switch (value.type) {
+      case TraceStorage::Args::Variadic::kInt:
+        writer->AppendInt(value.int_value);
+        break;
+      case TraceStorage::Args::Variadic::kReal:
+        writer->AppendDouble(value.real_value);
+        break;
+      case TraceStorage::Args::Variadic::kString: {
+        const auto& str = storage_->GetString(value.string_value);
+        writer->AppendString(str.c_str(), str.size());
+      }
+    }
+  };
+  auto write_arg = [this, writer, start_row](uint32_t arg_idx,
+                                             ValueWriter value_fn) {
+    uint32_t arg_row = start_row + arg_idx;
+    if (arg_row != 0)
+      writer->AppendChar(' ');
+
+    const auto& args = storage_->args();
+    const auto& key = storage_->GetString(args.keys()[arg_row]);
+    const auto& value = args.arg_values()[arg_row];
+
+    writer->AppendString(key.c_str(), key.length());
+    writer->AppendChar('=');
+    value_fn(value);
+  };
+
+  if (event_name == "sched_switch") {
+    using SS = protos::SchedSwitchFtraceEvent;
+    write_arg(SS::kPrevCommFieldNumber - 1, write_value);
+    write_arg(SS::kPrevPidFieldNumber - 1, write_value);
+    write_arg(SS::kPrevPrioFieldNumber - 1, write_value);
+    write_arg(SS::kPrevStateFieldNumber - 1, [writer](const Variadic& value) {
+      auto state = static_cast<uint16_t>(value.int_value);
+      writer->AppendString(ftrace_utils::TaskState(state).ToString().data());
+    });
+
+    writer->AppendLiteral(" ==>");
+    write_arg(SS::kNextCommFieldNumber - 1, write_value);
+    write_arg(SS::kNextPidFieldNumber - 1, write_value);
+    write_arg(SS::kNextPrioFieldNumber - 1, write_value);
+  }
+
+  uint32_t arg = 0;
+  for (auto it = lb; it != ub; it++) {
+    write_arg(arg++, write_value);
+  }
+}
+
+void RawTable::ToSystrace(sqlite3_context* ctx,
+                          int argc,
+                          sqlite3_value** argv) {
+  if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_INTEGER) {
+    sqlite3_result_error(ctx, "Usage: systrace(id)", -1);
+    return;
+  }
+  RowId row_id = sqlite3_value_int64(argv[0]);
+  auto pair = TraceStorage::ParseRowId(row_id);
+  PERFETTO_DCHECK(pair.first == TableId::kRawEvents);
+  auto row = pair.second;
+
+  const auto& raw_evts = storage_->raw_events();
+
+  UniqueTid utid = raw_evts.utids()[row];
+  const auto& thread = storage_->GetThread(utid);
+  uint32_t tgid = 0;
+  if (thread.upid.has_value()) {
+    tgid = storage_->GetProcess(thread.upid.value()).pid;
+  }
+  const auto& name = storage_->GetString(thread.name_id);
+
+  char line[4096];
+  base::StringWriter writer(line, sizeof(line));
+
+  ftrace_utils::FormatSystracePrefix(raw_evts.timestamps()[row],
+                                     raw_evts.cpus()[row], thread.tid, tgid,
+                                     base::StringView(name), &writer);
+
+  const auto& event_name = storage_->GetString(raw_evts.name_ids()[row]);
+  writer.AppendChar(' ');
+  writer.AppendString(event_name.c_str(), event_name.size());
+  writer.AppendLiteral(": ");
+
+  FormatSystraceArgs(event_name, raw_evts.arg_set_ids()[row], &writer);
+  sqlite3_result_text(ctx, writer.CreateStringCopy().release(), -1, free);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/raw_table.h b/src/trace_processor/raw_table.h
index caa9aa7..e6bdbe4 100644
--- a/src/trace_processor/raw_table.h
+++ b/src/trace_processor/raw_table.h
@@ -34,6 +34,12 @@
   uint32_t RowCount() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
+ private:
+  void FormatSystraceArgs(const std::string& event_name,
+                          ArgSetId arg_set_id,
+                          base::StringWriter* writer);
+  void ToSystrace(sqlite3_context* ctx, int argc, sqlite3_value** argv);
+
   const TraceStorage* const storage_;
 };
 
diff --git a/src/trace_processor/sqlite_utils.h b/src/trace_processor/sqlite_utils.h
index 37becd0..9974e05 100644
--- a/src/trace_processor/sqlite_utils.h
+++ b/src/trace_processor/sqlite_utils.h
@@ -34,6 +34,7 @@
 namespace sqlite_utils {
 
 const auto kSqliteStatic = reinterpret_cast<sqlite3_destructor_type>(0);
+const auto kSqliteTransient = reinterpret_cast<sqlite3_destructor_type>(-1);
 
 template <typename T>
 using is_numeric =
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index d3db6af..a8e2d2d 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -29,7 +29,6 @@
 #include "src/trace_processor/counters_table.h"
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/instants_table.h"
-#include "src/trace_processor/json_trace_parser.h"
 #include "src/trace_processor/process_table.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/proto_trace_parser.h"
@@ -49,19 +48,32 @@
 
 #include "perfetto/trace_processor/raw_query.pb.h"
 
+// JSON parsing is only supported in the standalone build.
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+#include "src/trace_processor/json_trace_parser.h"
+#endif
+
+// In Android tree builds, we don't have the percentile module.
+// Just don't include it.
+#if !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
 // defined in sqlite_src/ext/misc/percentile.c
 extern "C" int sqlite3_percentile_init(sqlite3* db,
                                        char** error,
                                        const sqlite3_api_routines* api);
+#endif
 
 namespace {
 void InitializeSqliteModules(sqlite3* db) {
+// In Android tree builds, we don't have the percentile module.
+// Just don't include it.
+#if !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
   char* error = nullptr;
   sqlite3_percentile_init(db, &error, nullptr);
   if (error) {
     PERFETTO_ELOG("Error initializing: %s", error);
     sqlite3_free(error);
   }
+#endif
 }
 
 void CreateBuiltinTables(sqlite3* db) {
@@ -172,7 +184,11 @@
     switch (trace_type) {
       case kJsonTraceType:
         PERFETTO_DLOG("Legacy JSON trace detected");
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
         context_.chunk_reader.reset(new JsonTraceParser(&context_));
+#else
+        PERFETTO_FATAL("JSON traces only supported in standalone mode.");
+#endif
         break;
       case kProtoTraceType:
         context_.chunk_reader.reset(new ProtoTraceTokenizer(&context_));
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 6345d68..ef18080 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -151,6 +151,9 @@
     # libunwind is disabled on Darwin so we cannot depend on it.
     pass
 
+def enable_sqlite(module):
+    module.static_libs.append('libsqlite')
+
 # Android equivalents for third-party libraries that the upstream project
 # depends on.
 builtin_deps = {
@@ -163,6 +166,7 @@
     '//buildtools:protobuf_lite': enable_protobuf_lite,
     '//buildtools:protoc_lib': enable_protoc_lib,
     '//buildtools:libunwindstack': enable_libunwindstack,
+    '//buildtools:sqlite': enable_sqlite,
 }
 
 # ----------------------------------------------------------------------------
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index bbfe75d..d06c057 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -22,12 +22,15 @@
   public_deps = [
     "../../gn:default_deps",
     "../../include/perfetto/base",
+    "../../include/perfetto/trace_processor:trace_processor",
     "../../include/perfetto/traced:sys_stats_counters",
     "../../protos/perfetto/trace:lite",
     "../../protos/perfetto/trace/ftrace:lite",
     "../../protos/perfetto/trace/profiling:lite",
+    "../../protos/perfetto/trace_processor:lite",
     "../../protos/third_party/pprof:lite",
     "../../src/base",
+    "../../src/trace_processor:lib",
   ]
   sources = [
     "ftrace_event_formatter.cc",
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 5ef76fd..1f8c9d2 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -76,6 +76,9 @@
   if (format == "systrace")
     return perfetto::trace_to_text::TraceToSystrace(input_stream, output_stream,
                                                     /*wrap_in_json=*/false);
+  if (format == "experimental_systrace")
+    return perfetto::trace_to_text::TraceToExperimentalSystrace(input_stream,
+                                                                output_stream);
   if (format == "text")
     return perfetto::trace_to_text::TraceToText(input_stream, output_stream);
 
diff --git a/tools/trace_to_text/trace_to_systrace.cc b/tools/trace_to_text/trace_to_systrace.cc
index 6737777..292ec44 100644
--- a/tools/trace_to_text/trace_to_systrace.cc
+++ b/tools/trace_to_text/trace_to_systrace.cc
@@ -29,6 +29,7 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/trace_processor.h"
 #include "perfetto/traced/sys_stats_counters.h"
 #include "tools/trace_to_text/ftrace_event_formatter.h"
 #include "tools/trace_to_text/process_formatter.h"
@@ -36,6 +37,7 @@
 
 #include "perfetto/trace/trace.pb.h"
 #include "perfetto/trace/trace_packet.pb.h"
+#include "perfetto/trace_processor/raw_query.pb.h"
 
 // When running in Web Assembly, fflush() is a no-op and the stdio buffering
 // sends progress updates to JS only when a write ends with \n.
@@ -106,6 +108,102 @@
 
 }  // namespace
 
+int TraceToExperimentalSystrace(std::istream* input, std::ostream* output) {
+  trace_processor::Config config;
+  config.optimization_mode = trace_processor::OptimizationMode::kMaxBandwidth;
+  std::unique_ptr<trace_processor::TraceProcessor> tp =
+      trace_processor::TraceProcessor::CreateInstance(config);
+
+  // 1MB chunk size seems the best tradeoff on a MacBook Pro 2013 - i7 2.8 GHz.
+  constexpr size_t kChunkSize = 1024 * 1024;
+
+  uint64_t file_size = 0;
+  for (int i = 0;; i++) {
+    if (i % 128 == 0)
+      fprintf(stderr, "\x1b[2K\rLoading trace: %.2f MB\r", file_size / 1E6);
+
+    std::unique_ptr<uint8_t[]> buf(new uint8_t[kChunkSize]);
+    input->read(reinterpret_cast<char*>(buf.get()), kChunkSize);
+    if (input->bad()) {
+      PERFETTO_ELOG("Failed when reading trace");
+      return 1;
+    }
+
+    auto rsize = input->gcount();
+    if (rsize <= 0)
+      break;
+    file_size += static_cast<uint64_t>(rsize);
+    tp->Parse(std::move(buf), static_cast<size_t>(rsize));
+  }
+  tp->NotifyEndOfFile();
+
+  *output << "TRACE:\n";
+  *output << kFtraceHeader;
+
+  constexpr uint32_t kNumRowsToQuery = 100000;
+  int64_t start_ts = 0;
+  bool has_more = true;
+  for (uint32_t i = 0; has_more; i++) {
+    protos::RawQueryArgs query_args;
+
+    char buffer[1024];
+    sprintf(buffer,
+            "select ts, to_ftrace(id) from raw "
+            "where ts >= %" PRId64 " limit %" PRIu32,
+            start_ts, kNumRowsToQuery);
+    query_args.set_sql_query(buffer);
+
+    // This query is not actually async so just pull the result up to the
+    // function level so we can return on error.
+    protos::RawQueryResult result;
+    tp->ExecuteQuery(query_args, [&result](const protos::RawQueryResult& res) {
+      result = res;
+    });
+
+    if (result.has_error()) {
+      PERFETTO_ELOG("Error while writing systrace %s", result.error().c_str());
+      return 1;
+    }
+
+    // The code below relies on there being at least one row so just break if
+    // we don't.
+    auto num_rows = result.num_records();
+    if (num_rows == 0) {
+      has_more = false;
+      break;
+    }
+
+    // Store the end timestamp so we can start iterating from there next time.
+    const auto& ts_col = result.columns(0).long_values();
+    start_ts = ts_col.Get(ts_col.size() - 1);
+
+    // Compute how many rows we should print out - this should be the first
+    // index with the timestamp |start_ts|. Usually this is just |size - 1| but
+    // if multiple rows have the same timestamp, this can be earlier.
+    auto rit = std::find(ts_col.rbegin(), ts_col.rend(), start_ts);
+    auto rdistance = std::distance(ts_col.rbegin(), rit);
+    auto last_row = static_cast<uint32_t>(ts_col.size() - 1 - rdistance);
+
+    // Print out everything until this row.
+    for (uint64_t row = 0; row < last_row; row++) {
+      int idx = static_cast<int>(row);
+      const std::string& line = result.columns(1).string_values(idx);
+      *output << line << "\n";
+    }
+
+    output->flush();
+
+    // Update the seen count to the number of output rows and only continue if
+    // we saw exactly the number of rows we asked for.
+    has_more = kNumRowsToQuery == result.num_records();
+
+    uint64_t printed_rows = i * kNumRowsToQuery + result.num_records();
+    fprintf(stderr, "\x1b[2K\rWritten %" PRIu64 " rows\r", printed_rows);
+  }
+
+  return 0;
+}
+
 int TraceToSystrace(std::istream* input,
                     std::ostream* output,
                     bool wrap_in_json) {
diff --git a/tools/trace_to_text/trace_to_systrace.h b/tools/trace_to_text/trace_to_systrace.h
index 32ded63..8dcaf5a 100644
--- a/tools/trace_to_text/trace_to_systrace.h
+++ b/tools/trace_to_text/trace_to_systrace.h
@@ -22,6 +22,8 @@
 namespace perfetto {
 namespace trace_to_text {
 
+int TraceToExperimentalSystrace(std::istream* input, std::ostream* output);
+
 int TraceToSystrace(std::istream* input,
                     std::ostream* output,
                     bool wrap_in_json);