Merge "CI: remove android-arm-asan bot" into main
diff --git a/infra/ui.perfetto.dev/appengine/main.py b/infra/ui.perfetto.dev/appengine/main.py
index 0fac701..acca00d 100644
--- a/infra/ui.perfetto.dev/appengine/main.py
+++ b/infra/ui.perfetto.dev/appengine/main.py
@@ -19,8 +19,9 @@
 
 REQ_HEADERS = [
     'Accept',
-    'Accept-Encoding',
-    'Cache-Control',
+    # TODO(primiano): re-enable once the gzip handling outage fixed.
+    # 'Accept-Encoding',
+    # 'Cache-Control',
 ]
 
 RESP_HEADERS = [
diff --git a/infra/ui.perfetto.dev/appengine/requirements.txt b/infra/ui.perfetto.dev/appengine/requirements.txt
index 1faca3d..9aa5a27 100644
--- a/infra/ui.perfetto.dev/appengine/requirements.txt
+++ b/infra/ui.perfetto.dev/appengine/requirements.txt
@@ -1,2 +1,3 @@
-Flask==1.1.2
+Flask==2.2.5
+Jinja2==3.0.3
 requests
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 262b346..d6e21c5 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -207,7 +207,7 @@
 
   // Creates a Column which returns the index as the value of the row.
   static ColumnLegacy IdColumn(uint32_t col_idx_in_table,
-                               uint32_t row_map_idx,
+                               uint32_t overlay_idx,
                                const char* name = "id",
                                uint32_t flags = kIdFlags);
 
diff --git a/src/trace_processor/db/runtime_table.cc b/src/trace_processor/db/runtime_table.cc
index 38938b8..0de15c3 100644
--- a/src/trace_processor/db/runtime_table.cc
+++ b/src/trace_processor/db/runtime_table.cc
@@ -19,25 +19,34 @@
 #include <algorithm>
 #include <cinttypes>
 #include <cstdint>
+#include <functional>
+#include <limits>
 #include <memory>
 #include <optional>
+#include <set>
 #include <string>
 #include <utility>
 #include <variant>
 #include <vector>
 
+#include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/iterator.h"
 #include "perfetto/trace_processor/ref_counted.h"
+#include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/column.h"
 #include "src/trace_processor/db/column/data_layer.h"
 #include "src/trace_processor/db/column/id_storage.h"
 #include "src/trace_processor/db/column/null_overlay.h"
 #include "src/trace_processor/db/column/numeric_storage.h"
+#include "src/trace_processor/db/column/range_overlay.h"
+#include "src/trace_processor/db/column/selector_overlay.h"
 #include "src/trace_processor/db/column/string_storage.h"
 #include "src/trace_processor/db/column/types.h"
+#include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/db/column_storage_overlay.h"
 
 namespace perfetto::trace_processor {
@@ -62,6 +71,60 @@
          std::get_if<RuntimeTable::DoubleStorage>(&col) == nullptr;
 }
 
+void CreateNonNullableIntsColumn(
+    uint32_t col_idx,
+    const char* col_name,
+    ColumnStorage<int64_t>* ints_storage,
+    std::vector<RefPtr<column::DataLayer>>& storage_layers,
+    std::vector<RefPtr<column::DataLayer>>& overlay_layers,
+    std::vector<ColumnLegacy>& legacy_columns,
+    std::vector<ColumnStorageOverlay>& legacy_overlays) {
+  const std::vector<int64_t>& values = ints_storage->vector();
+
+  // Looking for the iterator to the first value that is less or equal to the
+  // previous value. The values before are therefore strictly monotonic - each
+  // is greater than the previous one.
+  bool is_monotonic = true;
+  bool is_sorted = true;
+  for (uint32_t i = 1; i < values.size() && is_sorted; i++) {
+    is_monotonic = is_monotonic && values[i - 1] < values[i];
+    is_sorted = values[i - 1] <= values[i];
+  }
+
+  // The special treatement for Id columns makes no sense for empty or
+  // single element indices. Those should be treated as standard int
+  // column.
+  // We are checking if the first value is not too big - we will later
+  // create a BitVector which will skip all of the bits until the front
+  // of the index vector, so we are creating a cutoff to prevent
+  // unreasonable memory usage.
+  bool is_id = is_monotonic && values.size() > 1 && values.front() < 1 << 20 &&
+               values.front() >= std::numeric_limits<uint32_t>::min() &&
+               values.back() < std::numeric_limits<uint32_t>::max();
+
+  if (is_id) {
+    // The column is an Id column.
+    storage_layers[col_idx].reset(new column::IdStorage());
+
+    legacy_overlays.emplace_back(BitVector::FromSortedIndexVector(values));
+    overlay_layers.emplace_back().reset(new column::SelectorOverlay(
+        legacy_overlays.back().row_map().GetIfBitVector()));
+
+    legacy_columns.push_back(ColumnLegacy::IdColumn(
+        col_idx, static_cast<uint32_t>(legacy_overlays.size() - 1), col_name,
+        ColumnLegacy::kIdFlags));
+    return;
+  }
+
+  uint32_t flags =
+      is_sorted ? ColumnLegacy::Flag::kNonNull | ColumnLegacy::Flag::kSorted
+                : ColumnLegacy::Flag::kNonNull;
+
+  legacy_columns.emplace_back(col_name, ints_storage, flags, col_idx, 0);
+  storage_layers[col_idx].reset(new column::NumericStorage<int64_t>(
+      &values, ColumnType::kInt64, is_sorted));
+}
+
 }  // namespace
 
 RuntimeTable::RuntimeTable(
@@ -179,7 +242,19 @@
     uint32_t rows) && {
   std::vector<RefPtr<column::DataLayer>> storage_layers(col_names_.size() + 1);
   std::vector<RefPtr<column::DataLayer>> null_layers(col_names_.size() + 1);
-  std::vector<ColumnLegacy> columns;
+
+  std::vector<ColumnLegacy> legacy_columns;
+  std::vector<ColumnStorageOverlay> legacy_overlays;
+
+  // |overlay_layers| might use the RowMaps used by |legacy_overlays| and access
+  // them by fetching the pointer to the RowMap inside overlay. We need to make
+  // sure that those pointers will not change, hence we need to make sure that
+  // the vector will not resize. In the current implementation there is at most
+  // one overlay per column.
+  legacy_overlays.reserve(col_names_.size() + 1);
+  legacy_overlays.emplace_back(rows);
+  std::vector<RefPtr<column::DataLayer>> overlay_layers(1);
+
   for (uint32_t i = 0; i < col_names_.size(); ++i) {
     auto* col = storage_[i].get();
     std::unique_ptr<column::DataLayerChain> chain;
@@ -188,38 +263,34 @@
       PERFETTO_CHECK(*leading_nulls == rows);
       *col = Fill<NullIntStorage>(*leading_nulls, std::nullopt);
     }
+
     if (auto* ints = std::get_if<NullIntStorage>(col)) {
+      // The `ints` column
       PERFETTO_CHECK(ints->size() == rows);
-      // Check if the column is nullable.
+
       if (ints->non_null_size() == ints->size()) {
+        // The column doesn't have any nulls so we construct a new nonnullable
+        // column.
         *col = IntStorage::CreateFromAssertNonNull(std::move(*ints));
-        auto* non_null_ints = std::get_if<IntStorage>(col);
-        bool is_sorted = std::is_sorted(non_null_ints->vector().begin(),
-                                        non_null_ints->vector().end());
-        uint32_t flags = is_sorted ? ColumnLegacy::Flag::kNonNull |
-                                         ColumnLegacy::Flag::kSorted
-                                   : ColumnLegacy::Flag::kNonNull;
-        columns.emplace_back(col_names_[i].c_str(), non_null_ints, flags, i, 0);
-        storage_layers[i].reset(new column::NumericStorage<int64_t>(
-            &non_null_ints->vector(), ColumnType::kInt64, is_sorted));
+        CreateNonNullableIntsColumn(
+            i, col_names_[i].c_str(), std::get_if<IntStorage>(col),
+            storage_layers, overlay_layers, legacy_columns, legacy_overlays);
       } else {
-        columns.emplace_back(col_names_[i].c_str(), ints,
-                             ColumnLegacy::Flag::kNoFlag, i, 0);
+        // Nullable ints column.
+        legacy_columns.emplace_back(col_names_[i].c_str(), ints,
+                                    ColumnLegacy::Flag::kNoFlag, i, 0);
         storage_layers[i].reset(new column::NumericStorage<int64_t>(
             &ints->non_null_vector(), ColumnType::kInt64, false));
         null_layers[i].reset(
             new column::NullOverlay(&ints->non_null_bit_vector()));
       }
-    } else if (auto* strings = std::get_if<StringStorage>(col)) {
-      PERFETTO_CHECK(strings->size() == rows);
-      columns.emplace_back(col_names_[i].c_str(), strings,
-                           ColumnLegacy::Flag::kNonNull, i, 0);
-      storage_layers[i].reset(
-          new column::StringStorage(string_pool_, &strings->vector()));
+
+      // The doubles column.
     } else if (auto* doubles = std::get_if<NullDoubleStorage>(col)) {
       PERFETTO_CHECK(doubles->size() == rows);
-      // Check if the column is nullable.
+
       if (doubles->non_null_size() == doubles->size()) {
+        // The column is not nullable.
         *col = DoubleStorage::CreateFromAssertNonNull(std::move(*doubles));
 
         auto* non_null_doubles = std::get_if<DoubleStorage>(col);
@@ -228,34 +299,40 @@
         uint32_t flags = is_sorted ? ColumnLegacy::Flag::kNonNull |
                                          ColumnLegacy::Flag::kSorted
                                    : ColumnLegacy::Flag::kNonNull;
-        columns.emplace_back(col_names_[i].c_str(), non_null_doubles, flags, i,
-                             0);
+        legacy_columns.emplace_back(col_names_[i].c_str(), non_null_doubles,
+                                    flags, i, 0);
         storage_layers[i].reset(new column::NumericStorage<double>(
             &non_null_doubles->vector(), ColumnType::kDouble, is_sorted));
+
       } else {
-        columns.emplace_back(col_names_[i].c_str(), doubles,
-                             ColumnLegacy::Flag::kNoFlag, i, 0);
+        // The column is nullable.
+        legacy_columns.emplace_back(col_names_[i].c_str(), doubles,
+                                    ColumnLegacy::Flag::kNoFlag, i, 0);
         storage_layers[i].reset(new column::NumericStorage<double>(
             &doubles->non_null_vector(), ColumnType::kDouble, false));
         null_layers[i].reset(
             new column::NullOverlay(&doubles->non_null_bit_vector()));
       }
+
+    } else if (auto* strings = std::get_if<StringStorage>(col)) {
+      // The `strings` column.
+      PERFETTO_CHECK(strings->size() == rows);
+      legacy_columns.emplace_back(col_names_[i].c_str(), strings,
+                                  ColumnLegacy::Flag::kNonNull, i, 0);
+      storage_layers[i].reset(
+          new column::StringStorage(string_pool_, &strings->vector()));
+
     } else {
       PERFETTO_FATAL("Unexpected column type");
     }
   }
-  columns.push_back(ColumnLegacy::IdColumn(
-      static_cast<uint32_t>(columns.size()), 0, "_auto_id",
+  legacy_columns.push_back(ColumnLegacy::IdColumn(
+      static_cast<uint32_t>(legacy_columns.size()), 0, "_auto_id",
       ColumnLegacy::kIdFlags | ColumnLegacy::Flag::kHidden));
   storage_layers.back().reset(new column::IdStorage());
 
-  std::vector<ColumnStorageOverlay> overlays;
-  overlays.emplace_back(rows);
-
-  std::vector<RefPtr<column::DataLayer>> overlay_layers(1);
-
   auto table = std::make_unique<RuntimeTable>(
-      string_pool_, rows, std::move(columns), std::move(overlays),
+      string_pool_, rows, std::move(legacy_columns), std::move(legacy_overlays),
       std::move(storage_layers), std::move(null_layers),
       std::move(overlay_layers));
   table->storage_ = std::move(storage_);
diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql
index a81f28a..1a9adba 100644
--- a/src/trace_processor/metrics/sql/android/android_boot.sql
+++ b/src/trace_processor/metrics/sql/android/android_boot.sql
@@ -56,7 +56,7 @@
     'full_trace_process_start_aggregation', (
         SELECT NULL_IF_EMPTY(AndroidBootMetric_ProcessStartAggregation(
             'total_start_sum', (SELECT SUM(total_dur) FROM android_app_process_starts),
-            'num_of_processes', (SELECT COUNT(process_name) FROM android_app_process_starts GROUP BY process_name),
+            'num_of_processes', (SELECT COUNT(*) FROM android_app_process_starts),
             'average_start_time', (SELECT AVG(total_dur) FROM android_app_process_starts)))
             FROM android_app_process_starts),
     'post_boot_process_start_aggregation', (
@@ -66,11 +66,10 @@
                 FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
                 ORDER BY ts ASC LIMIT 1 )
             ),
-            'num_of_processes', (SELECT COUNT(process_name) FROM android_app_process_starts
+            'num_of_processes', (SELECT COUNT(*) FROM android_app_process_starts
               WHERE proc_start_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
                 WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
                 ASC LIMIT 1 )
-              GROUP BY process_name
             ),
             'average_start_time', (SELECT AVG(total_dur) FROM android_app_process_starts
               WHERE proc_start_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
index 5bc962a..8e10095 100644
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -34,14 +34,12 @@
 using TracePacket = protos::pbzero::TracePacket;
 
 constexpr std::string_view kTracePath =
-    "test/data/trace_redaction_jank_high_cpu.pftrace";
+    "test/data/trace-redaction-general.pftrace";
 
-// "com.google.android.settings.intelligence" will have one package, but two
-// processes will reference it. When doing so, they will use two different
-// uids (multiples of 1,000,000).
 constexpr std::string_view kPackageName =
-    "com.google.android.settings.intelligence";
-constexpr uint64_t kPackageUid = 10118;
+    "com.Unity.com.unity.multiplayer.samples.coop";
+
+constexpr uint64_t kPackageUid = 10252;
 
 class TraceRedactorIntegrationTest : public testing::Test {
  public:
@@ -58,13 +56,30 @@
 
   const std::string& dest_trace() const { return dest_trace_->path(); }
 
+  std::vector<protozero::ConstBytes> GetPackageInfos(
+      const Trace::Decoder& trace) const {
+    std::vector<protozero::ConstBytes> infos;
+
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      TracePacket::Decoder packet_decoder(*packet_it);
+      if (packet_decoder.has_packages_list()) {
+        PackagesList::Decoder list_it(packet_decoder.packages_list());
+        for (auto info_it = list_it.packages(); info_it; ++info_it) {
+          PackageInfo::Decoder info(*info_it);
+          infos.push_back(*info_it);
+        }
+      }
+    }
+
+    return infos;
+  }
+
  private:
   std::string src_trace_;
   std::unique_ptr<base::TempFile> dest_trace_;
 };
 
-TEST_F(TraceRedactorIntegrationTest,
-       DISABLED_FindsPackageAndFiltersPackageList) {
+TEST_F(TraceRedactorIntegrationTest, FindsPackageAndFiltersPackageList) {
   TraceRedactor redaction;
   redaction.collectors()->emplace_back(new FindPackageUid());
   redaction.transformers()->emplace_back(new PrunePackageList());
@@ -79,33 +94,27 @@
   std::string redacted_buffer;
   ASSERT_TRUE(base::ReadFile(dest_trace(), &redacted_buffer));
 
-  // Collect package info from the trace.
-  std::vector<protozero::ConstBytes> infos;
+  Trace::Decoder redacted_trace(redacted_buffer);
+  std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
 
-  Trace::Decoder trace_decoder(redacted_buffer);
+  // It is possible for two packages_list to appear in the trace. The
+  // find_package_uid will stop after the first one is found. Package uids are
+  // appear as n * 1,000,000 where n is some integer. It is also possible for
+  // two packages_list to contain copies of each other - for example
+  // "com.Unity.com.unity.multiplayer.samples.coop" appears in both
+  // packages_list.
+  ASSERT_GE(infos.size(), 1u);
 
-  for (auto packet_it = trace_decoder.packet(); packet_it; ++packet_it) {
-    TracePacket::Decoder packet_decoder(*packet_it);
+  for (const auto& info_buffer : infos) {
+    PackageInfo::Decoder info(info_buffer);
 
-    if (packet_decoder.has_packages_list()) {
-      PackagesList::Decoder list_it(packet_decoder.packages_list());
+    ASSERT_TRUE(info.has_name());
+    ASSERT_EQ(info.name().ToStdString(), kPackageName);
 
-      for (auto info_it = list_it.packages(); info_it; ++info_it) {
-        infos.push_back(*info_it);
-      }
-    }
+    ASSERT_TRUE(info.has_uid());
+    ASSERT_EQ(NormalizeUid(info.uid()), NormalizeUid(kPackageUid));
   }
 
-  ASSERT_EQ(infos.size(), 1u);
-
-  PackageInfo::Decoder info(infos[0]);
-
-  ASSERT_TRUE(info.has_name());
-  ASSERT_EQ(info.name().ToStdString(), kPackageName);
-
-  ASSERT_TRUE(info.has_uid());
-  ASSERT_EQ(NormalizeUid(info.uid()), NormalizeUid(kPackageUid));
-
   ASSERT_TRUE(context.package_uid.has_value());
   ASSERT_EQ(NormalizeUid(context.package_uid.value()),
             NormalizeUid(kPackageUid));
diff --git a/test/data/trace-redaction-general.pftrace.sha256 b/test/data/trace-redaction-general.pftrace.sha256
new file mode 100644
index 0000000..9e7d405
--- /dev/null
+++ b/test/data/trace-redaction-general.pftrace.sha256
@@ -0,0 +1 @@
+dd5f14e36a23158cf740e146cbdd57816056c70ed73b6038a5f317dab28477fe
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index 4f15081..bb7dcbc 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -213,12 +213,12 @@
           }
           full_trace_process_start_aggregation {
             total_start_sum: 10678297679
-            num_of_processes: 1
+            num_of_processes: 29
             average_start_time: 368217161.3448276
           }
           post_boot_process_start_aggregation {
             total_start_sum: 6112984648
-            num_of_processes: 1
+            num_of_processes: 21
             average_start_time: 291094507.04761904
           }
         }
diff --git a/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py
index 0f56881..10ec9ab 100644
--- a/test/trace_processor/diff_tests/syntax/table_tests.py
+++ b/test/trace_processor/diff_tests/syntax/table_tests.py
@@ -130,3 +130,19 @@
         "sorted"
         1
         """))
+
+  def test_create_perfetto_table_id_column(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_boot.pftrace'),
+        query="""
+        CREATE PERFETTO TABLE foo AS
+        SELECT dur FROM slice WHERE dur != -1
+        GROUP BY 1 ORDER BY 1;
+
+        SELECT col_type FROM perfetto_table_info('foo')
+        WHERE name = 'dur';
+        """,
+        out=Csv("""
+        "col_type"
+        "id"
+        """))
diff --git a/ui/release/build_all_channels.py b/ui/release/build_all_channels.py
index 9aa9c33..5549421 100755
--- a/ui/release/build_all_channels.py
+++ b/ui/release/build_all_channels.py
@@ -26,6 +26,7 @@
 import sys
 
 from os.path import dirname
+
 pjoin = os.path.join
 
 BUCKET_NAME = 'ui.perfetto.dev'
@@ -127,10 +128,10 @@
   print('===================================================================')
   print('Uploading to gs://%s' % BUCKET_NAME)
   print('===================================================================')
-  cp_cmd = [
-      'gsutil', '-m', '-h', 'Cache-Control:public, max-age=3600', 'cp', '-z',
-      'html,js,css,wasm,map'
-  ]
+  # TODO(primiano): re-enable caching once the gzip-related outage is restored.
+  # cache_hdr = 'Cache-Control:public, max-age=3600'
+  cache_hdr = 'Cache-Control:no-cache'
+  cp_cmd = ['gsutil', '-m', '-h', cache_hdr, 'cp', '-j', 'html,js,css,wasm,map']
   for name in os.listdir(merged_dist_dir):
     path = pjoin(merged_dist_dir, name)
     if os.path.isdir(path):