perfetto: Keep a ring buffer of traces per-app

Use the property 'iorapd.perfetto.max_traces' to determine how many
maximum traces there should be per-app. Excess traces are deleted
from disk and db.

In addition, associate each new perfetto_trace.pb in the raw_traces
table with the app_launch_histories table.

Bug: 141378186
Test: manual
Change-Id: I4e9d2e01cf5ac1360c55de5bf65c554fb865fa4c
diff --git a/src/db/models.h b/src/db/models.h
index ed6c16d..f2ff651 100644
--- a/src/db/models.h
+++ b/src/db/models.h
@@ -22,6 +22,7 @@
 #include <string>
 #include <sstream>
 #include <type_traits>
+#include <vector>
 
 #include <sqlite3.h>
 
@@ -112,6 +113,7 @@
     return db_;
   }
 
+  // Successive BindAll calls *do not* start back at the 0th bind position.
   template <typename T, typename ... Args>
   void BindAll(T&& arg, Args&&... args) {
     Bind(std::forward<T>(arg));
@@ -177,10 +179,13 @@
     return true;
   }
 
+  // Successive ColumnAll calls start at the 0th column again.
   template <typename T, typename ... Args>
   void ColumnAll(T& first, Args&... rest) {
     Column(first);
     ColumnAll(rest...);
+    // Reset column counter back to 0
+    column_counter_ = 0;
   }
 
   void ColumnAll() {}
@@ -221,6 +226,15 @@
 
   void Column(std::string& value) {
     const unsigned char* text = sqlite3_column_text(stmt_, column_counter_++);
+
+    DCHECK(text != nullptr) << "Column should be marked NOT NULL, otherwise use optional<string>";
+    if (text == nullptr) {
+      LOG(ERROR) << "Got NULL back for column " << column_counter_-1
+                 << "; is this column marked NOT NULL?";
+      value = "(((null)))";  // Don't segfault, keep going.
+      return;
+    }
+
     value = std::string{reinterpret_cast<const char*>(text)};
   }
 
@@ -292,6 +306,20 @@
     return static_cast<int>(last_rowid);
   }
 
+  // Returns the row ID that was inserted last.
+  template <typename... Args>
+  static bool Delete(DbHandle db, const std::string& sql, Args&&... args) {
+    ScopedLockDb lock{db};
+
+    DbStatement stmt = DbStatement::Prepare(db, sql, std::forward<Args>(args)...);
+
+    if (!stmt.Step(SQLITE_DONE)) {
+      return false;
+    }
+
+    return true;
+  }
+
   template <typename... Args>
   static bool SelectOnce(DbStatement& stmt, Args&... args) {
     int rc = stmt.Step();
@@ -755,6 +783,76 @@
   return os;
 }
 
+class RawTraceModel : public Model {
+ protected:
+  RawTraceModel(DbHandle db) : Model{db} {
+  }
+
+ public:
+
+  // Return raw_traces, sorted ascending by the id.
+  static std::vector<RawTraceModel> SelectByPackageNameActivityName(DbHandle db,
+                                                                    std::string package_name,
+                                                                    std::string activity_name) {
+    ScopedLockDb lock{db};
+
+    const char* sql =
+      "SELECT raw_traces.id, raw_traces.history_id, raw_traces.file_path "
+      "FROM raw_traces "
+      "INNER JOIN app_launch_histories ON raw_traces.history_id = app_launch_histories.id "
+      "INNER JOIN activities ON activities.id = app_launch_histories.activity_id "
+      "INNER JOIN packages ON packages.id = activities.package_id "
+      "WHERE packages.name = ? AND activities.name = ? "
+      "ORDER BY raw_traces.id ASC";
+
+    DbStatement stmt = DbStatement::Prepare(db, sql, package_name, activity_name);
+
+    std::vector<RawTraceModel> results;
+
+    RawTraceModel p{db};
+    while (DbQueryBuilder::SelectOnce(stmt, p.id, p.history_id, p.file_path)) {
+      results.push_back(p);
+    }
+
+    return results;
+  }
+
+  static std::optional<RawTraceModel> Insert(DbHandle db,
+                                             int history_id,
+                                             std::string file_path) {
+    const char* sql = "INSERT INTO raw_traces (history_id, file_path) VALUES (?1, ?2);";
+
+    std::optional<int> inserted_row_id =
+        DbQueryBuilder::Insert(db, sql, history_id, file_path);
+    if (!inserted_row_id) {
+      return std::nullopt;
+    }
+
+    RawTraceModel p{db};
+    p.id = *inserted_row_id;
+    p.history_id = history_id;
+    p.file_path = file_path;
+
+    return p;
+  }
+
+  bool Delete() {
+    const char* sql = "DELETE FROM raw_traces WHERE id = ?";
+
+    return DbQueryBuilder::Delete(db(), sql, id);
+  }
+
+  int id;
+  int history_id;
+  std::string file_path;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const RawTraceModel& p) {
+  os << "RawTraceModel{id=" << p.id << ",history_id=" << p.history_id << ",";
+  os << "file_path=" << p.file_path << "}";
+  return os;
+}
+
 }  // namespace iorap::db
 
 #endif  // IORAP_SRC_DB_MODELS_H_