trace_processor: thread table implementation

Fast filtering on Utid. Can be joined with the process table on Upid.

Also a few small changes to process table to be consistent with the
thread table.

Bug:80416541
Change-Id: Ie97f1997fef28d6af250aa8199b59aeebb5af8f1
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index bed05df..47995b6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -43,6 +43,8 @@
     "sched_slice_table.cc",
     "sched_slice_table.h",
     "scoped_db.h",
+    "thread_table.cc",
+    "thread_table.h",
     "trace_database.cc",
     "trace_database.h",
     "trace_parser.cc",
@@ -67,6 +69,7 @@
     "process_table_unittest.cc",
     "query_constraints_unittest.cc",
     "sched_slice_table_unittest.cc",
+    "thread_table_unittest.cc",
     "trace_parser_unittest.cc",
     "trace_storage_unittest.cc",
   ]
diff --git a/src/trace_processor/process_table.cc b/src/trace_processor/process_table.cc
index f8ad6c7..a4d6d48 100644
--- a/src/trace_processor/process_table.cc
+++ b/src/trace_processor/process_table.cc
@@ -147,11 +147,11 @@
 int ProcessTable::Cursor::Column(sqlite3_context* context, int N) {
   switch (N) {
     case Column::kUpid: {
-      sqlite3_result_int64(context, current_upid_);
+      sqlite3_result_int64(context, upid_filter_.current);
       break;
     }
     case Column::kName: {
-      auto process = storage_->GetProcess(current_upid_);
+      auto process = storage_->GetProcess(upid_filter_.current);
       const auto& name = storage_->GetString(process.name_id);
       sqlite3_result_text(context, name.c_str(),
                           static_cast<int>(name.length()), nullptr);
@@ -172,10 +172,10 @@
 
   PERFETTO_DCHECK(qc.constraints().size() == static_cast<size_t>(argc));
 
-  min_upid_ = 1;
-  max_upid_ = static_cast<uint32_t>(storage_->process_count());
-  desc_ = false;
-  current_upid_ = min_upid_;
+  upid_filter_.min = 1;
+  upid_filter_.max = static_cast<uint32_t>(storage_->process_count());
+  upid_filter_.desc = false;
+  upid_filter_.current = upid_filter_.min;
 
   for (size_t j = 0; j < qc.constraints().size(); j++) {
     const auto& cs = qc.constraints()[j];
@@ -186,19 +186,22 @@
       // constraints in the query. Everything between min and max (inclusive)
       // will be returned.
       if (IsOpGe(cs.op) || IsOpGt(cs.op)) {
-        min_upid_ = IsOpGt(cs.op) ? constraint_upid + 1 : constraint_upid;
+        upid_filter_.min =
+            IsOpGt(cs.op) ? constraint_upid + 1 : constraint_upid;
       } else if (IsOpLe(cs.op) || IsOpLt(cs.op)) {
-        max_upid_ = IsOpLt(cs.op) ? constraint_upid - 1 : constraint_upid;
+        upid_filter_.max =
+            IsOpLt(cs.op) ? constraint_upid - 1 : constraint_upid;
       } else if (IsOpEq(cs.op)) {
-        min_upid_ = constraint_upid;
-        max_upid_ = constraint_upid;
+        upid_filter_.min = constraint_upid;
+        upid_filter_.max = constraint_upid;
       }
     }
   }
   for (const auto& ob : qc.order_by()) {
     if (ob.iColumn == Column::kUpid) {
-      desc_ = ob.desc;
-      current_upid_ = desc_ ? max_upid_ : min_upid_;
+      upid_filter_.desc = ob.desc;
+      upid_filter_.current =
+          upid_filter_.desc ? upid_filter_.max : upid_filter_.min;
     }
   }
 
@@ -206,10 +209,10 @@
 }
 
 int ProcessTable::Cursor::Next() {
-  if (desc_) {
-    --current_upid_;
+  if (upid_filter_.desc) {
+    --upid_filter_.current;
   } else {
-    ++current_upid_;
+    ++upid_filter_.current;
   }
   return SQLITE_OK;
 }
@@ -219,7 +222,8 @@
 }
 
 int ProcessTable::Cursor::Eof() {
-  return desc_ ? current_upid_ < min_upid_ : current_upid_ > max_upid_;
+  return upid_filter_.desc ? upid_filter_.current < upid_filter_.min
+                           : upid_filter_.current > upid_filter_.max;
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/process_table.h b/src/trace_processor/process_table.h
index 56829cd..b20b25d 100644
--- a/src/trace_processor/process_table.h
+++ b/src/trace_processor/process_table.h
@@ -58,11 +58,15 @@
    private:
     sqlite3_vtab_cursor base_;  // Must be first.
 
+    struct UpidFilter {
+      TraceStorage::UniquePid min;
+      TraceStorage::UniquePid max;
+      TraceStorage::UniquePid current;
+      bool desc;
+    };
+
     const TraceStorage* const storage_;
-    TraceStorage::UniquePid min_upid_;
-    TraceStorage::UniquePid max_upid_;
-    TraceStorage::UniquePid current_upid_;
-    bool desc_ = false;
+    UpidFilter upid_filter_;
   };
 
   static inline Cursor* AsCursor(sqlite3_vtab_cursor* cursor) {
diff --git a/src/trace_processor/process_table_unittest.cc b/src/trace_processor/process_table_unittest.cc
index 75d57af..e7f24ba 100644
--- a/src/trace_processor/process_table_unittest.cc
+++ b/src/trace_processor/process_table_unittest.cc
@@ -24,8 +24,6 @@
 namespace trace_processor {
 namespace {
 
-using Column = ProcessTable::Column;
-
 class ProcessTableUnittest : public ::testing::Test {
  public:
   ProcessTableUnittest() {
diff --git a/src/trace_processor/thread_table.cc b/src/trace_processor/thread_table.cc
new file mode 100644
index 0000000..55fc769
--- /dev/null
+++ b/src/trace_processor/thread_table.cc
@@ -0,0 +1,232 @@
+/*
+ * 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 "src/trace_processor/thread_table.h"
+#include "src/trace_processor/query_constraints.h"
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+
+inline bool IsOpEq(int op) {
+  return op == SQLITE_INDEX_CONSTRAINT_EQ;
+}
+
+inline bool IsOpGe(int op) {
+  return op == SQLITE_INDEX_CONSTRAINT_GE;
+}
+
+inline bool IsOpGt(int op) {
+  return op == SQLITE_INDEX_CONSTRAINT_GT;
+}
+
+inline bool IsOpLe(int op) {
+  return op == SQLITE_INDEX_CONSTRAINT_LE;
+}
+
+inline bool IsOpLt(int op) {
+  return op == SQLITE_INDEX_CONSTRAINT_LT;
+}
+
+inline ThreadTable* AsTable(sqlite3_vtab* vtab) {
+  return reinterpret_cast<ThreadTable*>(vtab);
+}
+
+}  // namespace
+
+ThreadTable::ThreadTable(const TraceStorage* storage) : storage_(storage) {
+  static_assert(offsetof(ThreadTable, base_) == 0,
+                "SQLite base class must be first member of the table");
+  memset(&base_, 0, sizeof(base_));
+}
+
+sqlite3_module ThreadTable::CreateModule() {
+  sqlite3_module module;
+  memset(&module, 0, sizeof(module));
+  module.xConnect = [](sqlite3* db, void* raw_args, int, const char* const*,
+                       sqlite3_vtab** tab, char**) {
+    int res = sqlite3_declare_vtab(db,
+                                   "CREATE TABLE threads("
+                                   "utid UNSIGNED INT, "
+                                   "upid UNSIGNED INT, "
+                                   "name TEXT, "
+                                   "PRIMARY KEY(utid)"
+                                   ") WITHOUT ROWID;");
+    if (res != SQLITE_OK)
+      return res;
+    TraceStorage* storage = static_cast<TraceStorage*>(raw_args);
+    *tab = reinterpret_cast<sqlite3_vtab*>(new ThreadTable(storage));
+    return SQLITE_OK;
+  };
+  module.xBestIndex = [](sqlite3_vtab* t, sqlite3_index_info* i) {
+    return AsTable(t)->BestIndex(i);
+  };
+  module.xDisconnect = [](sqlite3_vtab* t) {
+    delete AsTable(t);
+    return SQLITE_OK;
+  };
+  module.xOpen = [](sqlite3_vtab* t, sqlite3_vtab_cursor** c) {
+    return AsTable(t)->Open(c);
+  };
+  module.xClose = [](sqlite3_vtab_cursor* c) {
+    delete AsCursor(c);
+    return SQLITE_OK;
+  };
+  module.xFilter = [](sqlite3_vtab_cursor* c, int i, const char* s, int a,
+                      sqlite3_value** v) {
+    return AsCursor(c)->Filter(i, s, a, v);
+  };
+  module.xNext = [](sqlite3_vtab_cursor* c) { return AsCursor(c)->Next(); };
+  module.xEof = [](sqlite3_vtab_cursor* c) { return AsCursor(c)->Eof(); };
+  module.xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
+    return AsCursor(c)->Column(a, b);
+  };
+  module.xRowid = [](sqlite3_vtab_cursor*, sqlite_int64*) {
+    return SQLITE_ERROR;
+  };
+  return module;
+}
+
+int ThreadTable::Open(sqlite3_vtab_cursor** ppCursor) {
+  *ppCursor = reinterpret_cast<sqlite3_vtab_cursor*>(new Cursor(storage_));
+  return SQLITE_OK;
+}
+
+// Called at least once but possibly many times before filtering things and is
+// the best time to keep track of constriants.
+int ThreadTable::BestIndex(sqlite3_index_info* idx) {
+  QueryConstraints qc;
+
+  for (int i = 0; i < idx->nOrderBy; i++) {
+    Column column = static_cast<Column>(idx->aOrderBy[i].iColumn);
+    unsigned char desc = idx->aOrderBy[i].desc;
+    qc.AddOrderBy(column, desc);
+  }
+  idx->orderByConsumed = true;
+
+  for (int i = 0; i < idx->nConstraint; i++) {
+    const auto& cs = idx->aConstraint[i];
+    if (!cs.usable)
+      continue;
+    qc.AddConstraint(cs.iColumn, cs.op);
+
+    idx->estimatedCost = cs.iColumn == Column::kUtid ? 10 : 100;
+
+    // argvIndex is 1-based so use the current size of the vector.
+    int argv_index = static_cast<int>(qc.constraints().size());
+    idx->aConstraintUsage[i].argvIndex = argv_index;
+  }
+
+  idx->idxStr = qc.ToNewSqlite3String().release();
+  idx->needToFreeIdxStr = true;
+
+  return SQLITE_OK;
+}
+
+ThreadTable::Cursor::Cursor(const TraceStorage* storage) : storage_(storage) {
+  static_assert(offsetof(Cursor, base_) == 0,
+                "SQLite base class must be first member of the cursor");
+  memset(&base_, 0, sizeof(base_));
+}
+
+int ThreadTable::Cursor::Column(sqlite3_context* context, int N) {
+  auto thread = storage_->GetThread(utid_filter_.current);
+  switch (N) {
+    case Column::kUtid: {
+      sqlite3_result_int64(context, utid_filter_.current);
+      break;
+    }
+    case Column::kUpid: {
+      sqlite3_result_int64(context, thread.upid);
+      break;
+    }
+    case Column::kName: {
+      const auto& name = storage_->GetString(thread.name_id);
+      sqlite3_result_text(context, name.c_str(),
+                          static_cast<int>(name.length()), nullptr);
+      break;
+    }
+    default: {
+      PERFETTO_FATAL("Unknown column %d", N);
+      break;
+    }
+  }
+  return SQLITE_OK;
+}
+
+int ThreadTable::Cursor::Filter(int /*idxNum*/,
+                                const char* idxStr,
+                                int argc,
+                                sqlite3_value** argv) {
+  QueryConstraints qc = QueryConstraints::FromString(idxStr);
+
+  PERFETTO_DCHECK(qc.constraints().size() == static_cast<size_t>(argc));
+
+  utid_filter_.min = 1;
+  utid_filter_.max = static_cast<uint32_t>(storage_->thread_count());
+  utid_filter_.desc = false;
+  utid_filter_.current = utid_filter_.min;
+
+  for (size_t j = 0; j < qc.constraints().size(); j++) {
+    const auto& cs = qc.constraints()[j];
+    if (cs.iColumn == Column::kUtid) {
+      TraceStorage::UniqueTid constraint_utid =
+          static_cast<TraceStorage::UniqueTid>(sqlite3_value_int(argv[j]));
+      // Filter the range of utids that we are interested in, based on the
+      // constraints in the query. Everything between min and max (inclusive)
+      // will be returned.
+      if (IsOpGe(cs.op) || IsOpGt(cs.op)) {
+        utid_filter_.min =
+            IsOpGt(cs.op) ? constraint_utid + 1 : constraint_utid;
+      } else if (IsOpLe(cs.op) || IsOpLt(cs.op)) {
+        utid_filter_.max =
+            IsOpLt(cs.op) ? constraint_utid - 1 : constraint_utid;
+      } else if (IsOpEq(cs.op)) {
+        utid_filter_.min = constraint_utid;
+        utid_filter_.max = constraint_utid;
+      }
+    }
+  }
+  for (const auto& ob : qc.order_by()) {
+    if (ob.iColumn == Column::kUtid) {
+      utid_filter_.desc = ob.desc;
+      utid_filter_.current =
+          utid_filter_.desc ? utid_filter_.max : utid_filter_.min;
+    }
+  }
+
+  return SQLITE_OK;
+}
+
+int ThreadTable::Cursor::Next() {
+  if (utid_filter_.desc) {
+    --utid_filter_.current;
+  } else {
+    ++utid_filter_.current;
+  }
+
+  return SQLITE_OK;
+}
+
+int ThreadTable::Cursor::Eof() {
+  return utid_filter_.desc ? utid_filter_.current < utid_filter_.min
+                           : utid_filter_.current > utid_filter_.max;
+}
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/thread_table.h b/src/trace_processor/thread_table.h
new file mode 100644
index 0000000..97c0b4e
--- /dev/null
+++ b/src/trace_processor/thread_table.h
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_THREAD_TABLE_H_
+#define SRC_TRACE_PROCESSOR_THREAD_TABLE_H_
+
+#include <limits>
+#include <memory>
+
+#include "sqlite3.h"
+#include "src/trace_processor/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// The implementation of the SQLite table containing each unique process with
+// the metadata for those processes.
+class ThreadTable {
+ public:
+  enum Column { kUtid = 0, kUpid = 1, kName = 2 };
+  struct OrderBy {
+    Column column = kUpid;
+    bool desc = false;
+  };
+
+  ThreadTable(const TraceStorage*);
+  static sqlite3_module CreateModule();
+
+  // Implementation for sqlite3_vtab.
+  int BestIndex(sqlite3_index_info*);
+  int Open(sqlite3_vtab_cursor**);
+
+ private:
+  using Constraint = sqlite3_index_info::sqlite3_index_constraint;
+
+  struct IndexInfo {
+    std::vector<OrderBy> order_by;
+    std::vector<Constraint> constraints;
+  };
+
+  class Cursor {
+   public:
+    Cursor(const TraceStorage*);
+
+    // Implementation of sqlite3_vtab_cursor.
+    int Filter(int idxNum, const char* idxStr, int argc, sqlite3_value** argv);
+    int Next();
+    int Eof();
+
+    int Column(sqlite3_context* context, int N);
+    int RowId(sqlite_int64* rowId);
+
+   private:
+    sqlite3_vtab_cursor base_;  // Must be first.
+
+    struct UtidFilter {
+      TraceStorage::UniqueTid min;
+      TraceStorage::UniqueTid max;
+      TraceStorage::UniqueTid current;
+      bool desc;
+    };
+
+    const TraceStorage* const storage_;
+    UtidFilter utid_filter_;
+  };
+
+  static inline Cursor* AsCursor(sqlite3_vtab_cursor* cursor) {
+    return reinterpret_cast<Cursor*>(cursor);
+  }
+
+  sqlite3_vtab base_;  // Must be first.
+  const TraceStorage* const storage_;
+};
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_THREAD_TABLE_H_
diff --git a/src/trace_processor/thread_table_unittest.cc b/src/trace_processor/thread_table_unittest.cc
new file mode 100644
index 0000000..5b26822
--- /dev/null
+++ b/src/trace_processor/thread_table_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * 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 "src/trace_processor/thread_table.h"
+#include "src/trace_processor/process_table.h"
+#include "src/trace_processor/scoped_db.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+class ThreadTableUnittest : public ::testing::Test {
+ public:
+  ThreadTableUnittest() {
+    sqlite3* db = nullptr;
+    PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
+    db_.reset(db);
+
+    static sqlite3_module t_module = ThreadTable::CreateModule();
+    sqlite3_create_module(*db_, "thread", &t_module,
+                          static_cast<void*>(&storage_));
+    static sqlite3_module p_module = ProcessTable::CreateModule();
+    sqlite3_create_module(*db_, "process", &p_module,
+                          static_cast<void*>(&storage_));
+  }
+
+  void PrepareValidStatement(const std::string& sql) {
+    int size = static_cast<int>(sql.size());
+    sqlite3_stmt* stmt;
+    ASSERT_EQ(sqlite3_prepare_v2(*db_, sql.c_str(), size, &stmt, nullptr),
+              SQLITE_OK);
+    stmt_.reset(stmt);
+  }
+
+  const char* GetColumnAsText(int colId) {
+    return reinterpret_cast<const char*>(sqlite3_column_text(*stmt_, colId));
+  }
+
+ protected:
+  TraceStorage storage_;
+  ScopedDb db_;
+  ScopedStmt stmt_;
+};
+
+TEST_F(ThreadTableUnittest, Select) {
+  uint32_t cpu = 3;
+  uint64_t timestamp = 100;
+  uint32_t pid_1 = 1;
+  uint32_t prev_state = 32;
+  static const char kCommProc1[] = "thread1";
+  static const char kCommProc2[] = "thread2";
+  uint32_t pid_2 = 4;
+
+  storage_.PushSchedSwitch(cpu, timestamp, pid_1, prev_state, kCommProc1,
+                           sizeof(kCommProc1) - 1, pid_2);
+  storage_.PushSchedSwitch(cpu, timestamp + 1, pid_2, prev_state, kCommProc2,
+                           sizeof(kCommProc2) - 1, pid_1);
+
+  storage_.PushProcess(2, "test", 4);
+  storage_.MatchThreadToProcess(1, 2);
+  PrepareValidStatement("SELECT utid, upid, name FROM thread");
+
+  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 0), 1 /* utid */);
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 1), 1 /* upid */);
+  ASSERT_STREQ(GetColumnAsText(2), kCommProc1);
+
+  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
+}
+
+TEST_F(ThreadTableUnittest, SelectWhere) {
+  uint32_t cpu = 3;
+  uint64_t timestamp = 100;
+  uint32_t pid_1 = 1;
+  uint32_t prev_state = 32;
+  static const char kCommProc1[] = "thread1";
+  static const char kCommProc2[] = "thread2";
+  uint32_t pid_2 = 4;
+
+  storage_.PushSchedSwitch(cpu, timestamp, pid_1, prev_state, kCommProc1,
+                           sizeof(kCommProc1) - 1, pid_2);
+  storage_.PushSchedSwitch(cpu, timestamp + 1, pid_2, prev_state, kCommProc2,
+                           sizeof(kCommProc2) - 1, pid_1);
+  storage_.PushSchedSwitch(cpu, timestamp + 2, pid_1, prev_state, kCommProc1,
+                           sizeof(kCommProc1) - 1, pid_2);
+
+  storage_.PushProcess(2, "test", 4);
+  storage_.MatchThreadToProcess(1, 2);
+  storage_.MatchThreadToProcess(2, 2);
+  PrepareValidStatement("SELECT utid, upid, name FROM thread where utid = 1");
+
+  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 0), 1 /* utid */);
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 1), 1 /* upid */);
+  ASSERT_STREQ(GetColumnAsText(2), kCommProc1);
+
+  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
+}
+
+TEST_F(ThreadTableUnittest, JoinWithProcess) {
+  uint32_t cpu = 3;
+  uint64_t timestamp = 100;
+  uint32_t pid_1 = 1;
+  uint32_t prev_state = 32;
+  static const char kCommProc1[] = "thread1";
+  static const char kCommProc2[] = "thread2";
+  uint32_t pid_2 = 4;
+
+  storage_.PushSchedSwitch(cpu, timestamp, pid_1, prev_state, kCommProc1,
+                           sizeof(kCommProc1) - 1, pid_2);
+  storage_.PushSchedSwitch(cpu, timestamp + 1, pid_2, prev_state, kCommProc2,
+                           sizeof(kCommProc2) - 1, pid_1);
+
+  storage_.PushProcess(2, "test", 4);
+  storage_.PushProcess(3, "test1", 5);
+  storage_.MatchThreadToProcess(1, 2);
+  PrepareValidStatement(
+      "SELECT utid, thread.name, process.upid, process.name FROM thread INNER "
+      "JOIN process USING (upid)");
+
+  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 0), 1 /* utid */);
+  ASSERT_STREQ(GetColumnAsText(1), kCommProc1);
+
+  ASSERT_EQ(sqlite3_column_int(*stmt_, 2), 1 /* upid */);
+  ASSERT_STREQ(GetColumnAsText(3), "test");
+
+  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/trace_database.cc b/src/trace_processor/trace_database.cc
index 9d2e3c2..a95a784 100644
--- a/src/trace_processor/trace_database.cc
+++ b/src/trace_processor/trace_database.cc
@@ -39,6 +39,11 @@
   static sqlite3_module p_module = ProcessTable::CreateModule();
   sqlite3_create_module(*db_, "process", &p_module,
                         static_cast<void*>(&storage_));
+
+  // Setup the thread table.
+  static sqlite3_module t_module = ThreadTable::CreateModule();
+  sqlite3_create_module(*db_, "thread", &t_module,
+                        static_cast<void*>(&storage_));
 }
 
 void TraceDatabase::LoadTrace(BlobReader* reader,
diff --git a/src/trace_processor/trace_database.h b/src/trace_processor/trace_database.h
index e1ef800..a0b81fb 100644
--- a/src/trace_processor/trace_database.h
+++ b/src/trace_processor/trace_database.h
@@ -26,6 +26,7 @@
 #include "src/trace_processor/process_table.h"
 #include "src/trace_processor/sched_slice_table.h"
 #include "src/trace_processor/scoped_db.h"
+#include "src/trace_processor/thread_table.h"
 #include "src/trace_processor/trace_parser.h"
 #include "src/trace_processor/trace_storage.h"