Merge "gn: enable libc++ assertions in debug mode" into main
diff --git a/Android.bp b/Android.bp
index 328fc3d..cebcd7b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10974,6 +10974,7 @@
     name: "perfetto_src_trace_processor_db_storage_storage",
     srcs: [
         "src/trace_processor/db/storage/arrangement_storage.cc",
+        "src/trace_processor/db/storage/dense_null_storage.cc",
         "src/trace_processor/db/storage/dummy_storage.cc",
         "src/trace_processor/db/storage/id_storage.cc",
         "src/trace_processor/db/storage/null_storage.cc",
@@ -10982,6 +10983,7 @@
         "src/trace_processor/db/storage/set_id_storage.cc",
         "src/trace_processor/db/storage/storage.cc",
         "src/trace_processor/db/storage/string_storage.cc",
+        "src/trace_processor/db/storage/utils.cc",
     ],
 }
 
@@ -10990,6 +10992,7 @@
     name: "perfetto_src_trace_processor_db_storage_unittests",
     srcs: [
         "src/trace_processor/db/storage/arrangement_storage_unittest.cc",
+        "src/trace_processor/db/storage/dense_null_storage_unittest.cc",
         "src/trace_processor/db/storage/id_storage_unittest.cc",
         "src/trace_processor/db/storage/null_storage_unittest.cc",
         "src/trace_processor/db/storage/numeric_storage_unittest.cc",
@@ -15481,3 +15484,29 @@
         "LICENSE",
     ],
 }
+
+// TODO(b/315118713): use list of proto file sources instead of merged proto
+gensrcs {
+    name: "perfetto_trace_javastream_protos",
+    srcs: [
+        "protos/perfetto/trace/perfetto_trace.proto",
+    ],
+    tools: [
+        "aprotoc",
+        "protoc-gen-javastream",
+        "soong_zip",
+    ],
+    cmd: "mkdir -p $(genDir)/$(in) " +
+      "&& $(location aprotoc) " +
+        "--plugin=$(location protoc-gen-javastream) " +
+        "--javastream_out=$(genDir)/$(in) " +
+        "-Iexternal/protobuf/src " +
+        "-Iexternal/perfetto " +
+        "-I . $(in) " +
+      "&& $(location soong_zip) " +
+        "-jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+    output_extension: "srcjar",
+}
diff --git a/Android.bp.extras b/Android.bp.extras
index 3292ad9..9c7b0b0 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -171,3 +171,29 @@
         "LICENSE",
     ],
 }
+
+// TODO(b/315118713): use list of proto file sources instead of merged proto
+gensrcs {
+    name: "perfetto_trace_javastream_protos",
+    srcs: [
+        "protos/perfetto/trace/perfetto_trace.proto",
+    ],
+    tools: [
+        "aprotoc",
+        "protoc-gen-javastream",
+        "soong_zip",
+    ],
+    cmd: "mkdir -p $(genDir)/$(in) " +
+      "&& $(location aprotoc) " +
+        "--plugin=$(location protoc-gen-javastream) " +
+        "--javastream_out=$(genDir)/$(in) " +
+        "-Iexternal/protobuf/src " +
+        "-Iexternal/perfetto " +
+        "-I . $(in) " +
+      "&& $(location soong_zip) " +
+        "-jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+    output_extension: "srcjar",
+}
diff --git a/BUILD b/BUILD
index cddab6b..f3d4584 100644
--- a/BUILD
+++ b/BUILD
@@ -1302,6 +1302,8 @@
     srcs = [
         "src/trace_processor/db/storage/arrangement_storage.cc",
         "src/trace_processor/db/storage/arrangement_storage.h",
+        "src/trace_processor/db/storage/dense_null_storage.cc",
+        "src/trace_processor/db/storage/dense_null_storage.h",
         "src/trace_processor/db/storage/dummy_storage.cc",
         "src/trace_processor/db/storage/dummy_storage.h",
         "src/trace_processor/db/storage/id_storage.cc",
@@ -1319,6 +1321,7 @@
         "src/trace_processor/db/storage/string_storage.cc",
         "src/trace_processor/db/storage/string_storage.h",
         "src/trace_processor/db/storage/types.h",
+        "src/trace_processor/db/storage/utils.cc",
         "src/trace_processor/db/storage/utils.h",
     ],
 )
diff --git a/include/perfetto/ext/tracing/core/shared_memory_abi.h b/include/perfetto/ext/tracing/core/shared_memory_abi.h
index 4e05599..dd146ea 100644
--- a/include/perfetto/ext/tracing/core/shared_memory_abi.h
+++ b/include/perfetto/ext/tracing/core/shared_memory_abi.h
@@ -153,8 +153,10 @@
   // See PageLayout below.
   static constexpr size_t kMaxChunksPerPage = 14;
 
-  // Each TracePacket in the Chunk is prefixed by a 4 bytes redundant VarInt
-  // (see proto_utils.h) stating its size.
+  // Each TracePacket fragment in the Chunk is prefixed by a VarInt stating its
+  // size that is up to 4 bytes long. Since the size is often known after the
+  // fragment has been filled, the VarInt is often redundantly encoded (see
+  // proto_utils.h) to be exactly 4 bytes.
   static constexpr size_t kPacketHeaderSize = 4;
 
   // TraceWriter specifies this invalid packet/fragment size to signal to the
@@ -416,19 +418,6 @@
       return packets.count;
     }
 
-    // Increases |packets.count| to the given |packet_count|, but only if
-    // |packet_count| is larger than the current value of |packets.count|.
-    // Returns the new packet count. Same atomicity guarantees as
-    // IncrementPacketCount().
-    uint16_t IncreasePacketCountTo(uint16_t packet_count) {
-      ChunkHeader* chunk_header = header();
-      auto packets = chunk_header->packets.load(std::memory_order_relaxed);
-      if (packets.count < packet_count)
-        packets.count = packet_count & ChunkHeader::Packets::kMaxCount;
-      chunk_header->packets.store(packets, std::memory_order_release);
-      return packets.count;
-    }
-
     // Flags are cleared by TryAcquireChunk(), by passing the new header for
     // the chunk, or through ClearNeedsPatchingFlag.
     void SetFlag(ChunkHeader::Flags flag) {
diff --git a/protos/perfetto/trace_processor/serialization.proto b/protos/perfetto/trace_processor/serialization.proto
index e79b7c9..f559bd9 100644
--- a/protos/perfetto/trace_processor/serialization.proto
+++ b/protos/perfetto/trace_processor/serialization.proto
@@ -92,6 +92,12 @@
       optional Storage storage = 2;
     }
 
+    // A schema for serialization of |storage::DenseNullStorage|.
+    message DenseNullStorage {
+      optional BitVector bit_vector = 1;
+      optional Storage storage = 2;
+    }
+
     oneof data {
       DummyStorage dummy_storage = 1;
       IdStorage id_storage = 2;
@@ -101,6 +107,7 @@
       NullStorage null_storage = 6;
       ArrangementStorage arrangement_storage = 7;
       SelectorStorage selector_storage = 8;
+      DenseNullStorage dense_null_storage = 9;
     }
   }
 
diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py
index 981316d..564679a 100644
--- a/python/generators/sql_processing/docs_parse.py
+++ b/python/generators/sql_processing/docs_parse.py
@@ -20,7 +20,7 @@
 from typing import Any, Dict, List, Optional, Set, Tuple, NamedTuple
 
 from python.generators.sql_processing.docs_extractor import DocsExtractor
-from python.generators.sql_processing.utils import ANY_PATTERN, ARG_DEFINITION_PATTERN, ObjKind
+from python.generators.sql_processing.utils import ALLOWED_PREFIXES, ANY_PATTERN, ARG_DEFINITION_PATTERN, ObjKind
 from python.generators.sql_processing.utils import ARG_ANNOTATION_PATTERN
 from python.generators.sql_processing.utils import NAME_AND_TYPE_PATTERN
 from python.generators.sql_processing.utils import FUNCTION_RETURN_PATTERN
@@ -49,6 +49,28 @@
   description: str
 
 
+# Returns: error message if the name is not correct, None otherwise.
+def get_module_prefix_error(name: str, path: str, module: str) -> Optional[str]:
+  prefix = name.lower().split('_')[0]
+  if module == "common" or module == "prelude":
+    if prefix == module:
+      return (f'Names of tables/views/functions in the "{module}" module '
+              f'should not start with {module}')
+    return None
+  if prefix == module:
+    # Module prefix is always allowed.
+    return None
+  allowed_prefixes = [module]
+  for (path_prefix, allowed_name_prefix) in ALLOWED_PREFIXES.items():
+    if path.startswith(path_prefix):
+      if prefix == allowed_name_prefix:
+        return None
+      allowed_prefixes.append(allowed_name_prefix)
+  return (
+      f'Names of tables/views/functions at path "{path}" should be prefixed '
+      f'with one of following names: {", ".join(allowed_prefixes)}')
+
+
 class AbstractDocParser(ABC):
 
   @dataclass
@@ -64,19 +86,10 @@
   def _parse_name(self, upper: bool = False):
     assert self.name
     assert isinstance(self.name, str)
-    module_pattern = f"^{self.module}_.*"
-    if upper:
-      module_pattern = module_pattern.upper()
-    starts_with_module_name = re.match(module_pattern, self.name, re.IGNORECASE)
-    if self.module == "common" or self.module == "prelude":
-      if starts_with_module_name:
-        self._error(
-            'Names of tables/views/functions in the "{self.module}" module '
-            f'should not start with {module_pattern}')
-      return self.name
-    if not starts_with_module_name:
-      self._error('Names of tables/views/functions should be prefixed with the '
-                  f'module name (i.e. should start with {module_pattern})')
+    module_prefix_error = get_module_prefix_error(self.name, self.path,
+                                                  self.module)
+    if module_prefix_error is not None:
+      self._error(module_prefix_error)
     return self.name.strip()
 
   def _parse_desc_not_empty(self, desc: str):
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index 0a05a71..c7133b3 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -105,6 +105,9 @@
     ObjKind.table_function: CREATE_TABLE_FUNCTION_PATTERN,
 }
 
+ALLOWED_PREFIXES = {
+    'chrome/util': 'cr',
+}
 
 # Given a regex pattern and a string to match against, returns all the
 # matching positions. Specifically, it returns a dictionary from the line
diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
index 0196c4b..6fe447b 100755
--- a/python/perfetto/prebuilts/manifests/trace_processor_shell.py
+++ b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9978200,
+        10142120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/trace_processor_shell',
     'sha256':
-        'f3e21eb29fb51cb2ea9b81b69132c5ae93ce3276c57ccd27fcf7c675306b4e41',
+        '44585789d420d0bc38edc3dd6fbade4c4a718dc535fb68ac7a2449bf1a251f30',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8493976,
+        8659128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/trace_processor_shell',
     'sha256':
-        '84f35765141374b8d883813ac533e0c004cf72d1c6f05aef0c973364ff541eb9',
+        'bccca60b99fb2c587503a6430e0b15204ebeddb97607006d54f203e64ac923af',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9830856,
+        9987880,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/trace_processor_shell',
     'sha256':
-        'b3dc0a9c641b84a57fa5d59637921ae2237e4f05b1778341a691df220faf0cd7',
+        '3510cfc89e627a95ede2d06e77d1ddd70d31ef99d0d213d1fbaba4f438030e4c',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7231096,
+        7389416,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/trace_processor_shell',
     'sha256':
-        'a21252830fb1bbb7b3fd9665ce6e70920cffa6b1e72c16589c90896c002c3348',
+        '2a87d3587e9a756ea486746ba08e7535dacf8872b1d2081e9381a612668d154d',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9238056,
+        9396856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/trace_processor_shell',
     'sha256':
-        'f77519ec19743ec2c22ed78fe3a20106a482a28d77c4154378af108c5f7bdd4a',
+        '42738a5aa187fe7644d87361fcf9854459f3d46a301026c9ac76ac4bf0ec9d3e',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6870968,
+        7018688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/trace_processor_shell',
     'sha256':
-        '2c7055fb44085ec60ad8bb970d495c9c88070fce08902f11fcd44e0ae3369876'
+        '9d8247c09b82835dc5019fed2f41e8844162a8cbf520e4243111852d6398911b'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8414568,
+        8578936,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/trace_processor_shell',
     'sha256':
-        'd8ca0dc2bab7ea604a6721f0ac0e2b433b43261f247c6c98c510dc17aafe5a72'
+        '91405004e1a47b1170eb32315f3d0a5b287bc71cb856e7403fad0f02e8b4dfd1'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9328508,
+        9492612,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/trace_processor_shell',
     'sha256':
-        'de6a6ea45769888e59a1678d37b6e355b27b834d34a0b9e4980a942d333b88cc'
+        '01364fc6fb485b20d838326462239d14c8f2daf1e7dde524b2cba4fd5acb8a73'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9577896,
+        9742264,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/trace_processor_shell',
     'sha256':
-        'cd4b16c5f78a060934204737ba8b312e824ff7cc28f3732daf7d64e733a727f9'
+        '43b456dcd0238f52f5730c6f5b9f8249f096df718d3fefa443c3246d66df4bd6'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        9248256,
+        9408000,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '26584b4bbab40f8b0ad991a869e7483f92d7223e1473b879a6ceafa49b76390a',
+        'b4dc6a7968673373265344e8e5249ccc31f7d12bb9df527370065dff71b1e74a',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py
index 21698c1..a694d8a 100755
--- a/python/perfetto/prebuilts/manifests/tracebox.py
+++ b/python/perfetto/prebuilts/manifests/tracebox.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498816,
+        1515200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/tracebox',
     'sha256':
-        '185014447d35357edbd20e7ce9924842a0d5c6576bd2257abae2ed48b65fd3b8',
+        'b451e873b1f6c8bd2fc3f1e12adc381c77a5b6dce9ec28ad8788e6f7f5efd348',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -21,9 +21,9 @@
     'file_size':
         1392776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/tracebox',
     'sha256':
-        '082bb50e64df5e232673eebb1cd8b0dd752a394105f600cb0262730833f6b7f3',
+        '8247045bd78e467aa010674277048658cdf04a95bd15b25dabc973fce011d6db',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2229096,
+        2236584,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/tracebox',
     'sha256':
-        'c99120caedb845e1c3fad4428263a683b44c357c76d65848dd8e437250066e38',
+        'adf6f9ebb5686a7d2e056c8f059adf4d95e867ffa39d27b041bef16d3cb7e1c7',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1339796,
+        1344188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/tracebox',
     'sha256':
-        '6732165916b74f0b820991d1aaed2086a6b56e91f6c604291efe6636f0bdda71',
+        'e2feeeaf38c3cd9efbf992dbe09f40fca81651d9fd9159e0f9a95868e2c4e07e',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2157312,
+        2164560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/tracebox',
     'sha256':
-        '7d09865a6d7118e67d2acd0c56b2a94ce8bd5f614869d29a72fe633515ab1fbd',
+        '5d89a6c16819a74be44bf731f2d07bfb83924a2e560554a373feeb2bb9940ef1',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -77,31 +77,31 @@
     'file_size':
         1247188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/tracebox',
     'sha256':
-        '4ecc192172ac2bca49557cbdbb1f7d660718d4fb4a7314fd19b2b2e52be8bc0c'
+        '5837e88a92b8bc00d5575f9dc02dd314f16b2e0a1bb174efb6c092a8f639e7c4'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1854120,
+        1870504,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/tracebox',
     'sha256':
-        '1ca89113279d5c6a9ae273bde03b4d84373efe6923dc637cb840908f13b9639e'
+        '75431d11aec11f59b87a76fa31cf92a1f8e534ad4118357b3654a458cf547081'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1853356,
+        1869740,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/tracebox',
     'sha256':
-        'cf689a191c1252734ebbfda3106600da324610f761515cfbffbeac2ebdfee715'
+        '388da3a4248f105bc56685db4835ce487633035334efa543b7190ac4a9e26bde'
 }, {
     'arch':
         'android-x64',
@@ -110,7 +110,7 @@
     'file_size':
         2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/tracebox',
     'sha256':
-        '99e9ebdb5b5308d95551a4ad060d615d7defb6877c4061d21c783c45a71d372f'
+        '1c3d50a4066f9b3478ad65431532c3503a2ad73ee89d346e6ee12f7fb0c93aaa'
 }]
diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py
index f01ef0a..92ad14a 100755
--- a/python/perfetto/prebuilts/manifests/traceconv.py
+++ b/python/perfetto/prebuilts/manifests/traceconv.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9184800,
+        9348712,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/traceconv',
     'sha256':
-        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
+        '466110b5d92cfc7951ae3223147156dc3ddfad055f8c7a93fea91b2f1844d013',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7761896,
+        7927048,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/traceconv',
     'sha256':
-        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
+        '46663d0eaa88bc821ad71872fd9789c340f9100e22a78494006b94225e2cfe9b',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8928296,
+        9091432,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/traceconv',
     'sha256':
-        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
+        'bb0eabefb6cb22623368d1b51cd95a6d9485781f102a9293443659fdd826fc8f',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6770204,
+        6934532,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/traceconv',
     'sha256':
-        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
+        'ce90e026c71a006b01e4d3c247772cfeeeb7581cd2ce1b003de35ddd87e9d349',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8393944,
+        8558824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/traceconv',
     'sha256':
-        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
+        'f26446b306b0025c54d39dac5b62ae015e0d1771ff5ec461ccbbcc93f1dcb335',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6378744,
+        6542848,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/traceconv',
     'sha256':
-        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
+        '901512a8243b4015aeba59f547d40736fcf032edeedcd1ecb59c259bba0122cf'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7692488,
+        7856856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/traceconv',
     'sha256':
-        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
+        '0eda603b5bc4925b98e0d9656dc18e3e7c4db4685c49f902fd3e1fad1be75efd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8557756,
+        8721860,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/traceconv',
     'sha256':
-        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
+        'd9bc2acf1b280198402a81019d6d5c9f75849d0254fd929b62671af6095d6f57'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8708352,
+        8872720,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/traceconv',
     'sha256':
-        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
+        '3c33c2f79a4fb9760cdf2348a09d6fb6fbb8b5098e014e83cd14d3f3dfb8661d'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8204288,
+        8369664,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/windows-amd64/traceconv.exe',
     'sha256':
-        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
+        '900f931d89af74b84229e1c396898a9c492d49acd15977692040c95a50499936',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/test/stdlib_unittest.py b/python/test/stdlib_unittest.py
index 3fb6713..f7963fb 100644
--- a/python/test/stdlib_unittest.py
+++ b/python/test/stdlib_unittest.py
@@ -112,6 +112,79 @@
     # Expecting an error: function prefix (bar) not matching module name (foo).
     self.assertEqual(len(res.errors), 1)
 
+  # Checks that custom prefixes (cr for chrome/util) are allowed.
+  def test_custom_module_prefix(self):
+    res = parse_file(
+        'chrome/util/test.sql', f'''
+-- Comment
+CREATE PERFETTO TABLE cr_table(
+    -- Column.
+    x INT
+) AS
+SELECT 1;
+    '''.strip())
+    self.assertListEqual(res.errors, [])
+
+    fn = res.table_views[0]
+    self.assertEqual(fn.name, 'cr_table')
+    self.assertEqual(fn.desc, 'Comment')
+    self.assertEqual(fn.cols, {
+        'x': Arg('INT', 'Column.'),
+    })
+
+  # Checks that when custom prefixes (cr for chrome/util) are present,
+  # the full module name (chrome) is still accepted.
+  def test_custom_module_prefix_full_module_name(self):
+    res = parse_file(
+        'chrome/util/test.sql', f'''
+-- Comment
+CREATE PERFETTO TABLE chrome_table(
+    -- Column.
+    x INT
+) AS
+SELECT 1;
+    '''.strip())
+    self.assertListEqual(res.errors, [])
+
+    fn = res.table_views[0]
+    self.assertEqual(fn.name, 'chrome_table')
+    self.assertEqual(fn.desc, 'Comment')
+    self.assertEqual(fn.cols, {
+        'x': Arg('INT', 'Column.'),
+    })
+
+  # Checks that when custom prefixes (cr for chrome/util) are present,
+  # the incorrect prefixes (foo) are not accepted.
+  def test_custom_module_prefix_incorrect(self):
+    res = parse_file(
+        'chrome/util/test.sql', f'''
+-- Comment
+CREATE PERFETTO TABLE foo_table(
+    -- Column.
+    x INT
+) AS
+SELECT 1;
+    '''.strip())
+    # Expecting an error: table prefix (foo) is not allowed for a given path
+    # (allowed: chrome, cr).
+    self.assertEqual(len(res.errors), 1)
+
+  # Checks that when custom prefixes (cr for chrome/util) are present,
+  # they do not apply outside of the path scope.
+  def test_custom_module_prefix_does_not_apply_outside(self):
+    res = parse_file(
+        'foo/bar.sql', f'''
+-- Comment
+CREATE PERFETTO TABLE cr_table(
+    -- Column.
+    x INT
+) AS
+SELECT 1;
+    '''.strip())
+    # Expecting an error: table prefix (foo) is not allowed for a given path
+    # (allowed: foo).
+    self.assertEqual(len(res.errors), 1)
+
   def test_common_does_not_include_module_name(self):
     res = parse_file(
         'common/bar.sql', f'''
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index 0e5e72b..243651c 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -351,8 +351,8 @@
     return bv;
   }
 
-  // Creates a BitVector of size |end| bit the bits between |start| and |end|
-  // filled with corresponding bits |this| BitVector.
+  // Creates a BitVector of size `min(range_end, size())` with bits between
+  // |start| and |end| filled with corresponding bits from |this| BitVector.
   BitVector IntersectRange(uint32_t range_start, uint32_t range_end) const;
 
   // Requests the removal of unused capacity.
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index c77cb38..cb9a2cf 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -28,12 +28,14 @@
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/query_executor.h"
 #include "src/trace_processor/db/storage/arrangement_storage.h"
+#include "src/trace_processor/db/storage/dense_null_storage.h"
 #include "src/trace_processor/db/storage/dummy_storage.h"
 #include "src/trace_processor/db/storage/id_storage.h"
 #include "src/trace_processor/db/storage/null_storage.h"
 #include "src/trace_processor/db/storage/numeric_storage.h"
 #include "src/trace_processor/db/storage/selector_storage.h"
 #include "src/trace_processor/db/storage/set_id_storage.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/string_storage.h"
 #include "src/trace_processor/db/storage/types.h"
 #include "src/trace_processor/db/table.h"
@@ -63,6 +65,16 @@
     return;
   }
 
+  switch (storage.ValidateSearchConstraints(c.value, c.op)) {
+    case SearchValidationResult::kAllData:
+      return;
+    case SearchValidationResult::kNoData:
+      rm->Clear();
+      return;
+    case SearchValidationResult::kOk:
+      break;
+  }
+
   uint32_t rm_size = rm->size();
   uint32_t rm_first = rm->Get(0);
   uint32_t rm_last = rm->Get(rm_size - 1);
@@ -166,12 +178,11 @@
         col.type() == SqlValue::kLong && c.value.type == SqlValue::kDouble;
     bool double_with_int =
         col.type() == SqlValue::kDouble && c.value.type == SqlValue::kLong;
-    use_legacy = use_legacy ||
-                 (c.op != FilterOp::kIsNull && c.op != FilterOp::kIsNotNull &&
-                  (int_with_double || double_with_int));
-
-    // Dense columns.
-    use_legacy = use_legacy || col.IsDense();
+    bool double_int_enabled_col_type = col.IsId() || col.IsSetId();
+    use_legacy =
+        use_legacy ||
+        (!double_int_enabled_col_type && c.op != FilterOp::kIsNull &&
+         c.op != FilterOp::kIsNotNull && (int_with_double || double_with_int));
 
     // Extrinsically sorted columns.
     use_legacy = use_legacy ||
@@ -255,8 +266,13 @@
       // String columns are inherently nullable: null values are signified
       // with Id::Null().
       PERFETTO_CHECK(col.col_type() != ColumnType::kString);
-      storage = std::make_unique<storage::NullStorage>(std::move(storage),
-                                                       col.storage_base().bv());
+      if (col.IsDense()) {
+        storage = std::make_unique<storage::DenseNullStorage>(
+            std::move(storage), col.storage_base().bv());
+      } else {
+        storage = std::make_unique<storage::NullStorage>(
+            std::move(storage), col.storage_base().bv());
+      }
     }
     if (col.overlay().row_map().IsIndexVector()) {
       storage = std::make_unique<storage::ArrangementStorage>(
@@ -273,5 +289,37 @@
   return rm;
 }
 
+void QueryExecutor::BoundedColumnFilterForTesting(const Constraint& c,
+                                                  const storage::Storage& col,
+                                                  RowMap* rm) {
+  switch (col.ValidateSearchConstraints(c.value, c.op)) {
+    case SearchValidationResult::kAllData:
+      return;
+    case SearchValidationResult::kNoData:
+      rm->Clear();
+      return;
+    case SearchValidationResult::kOk:
+      break;
+  }
+
+  LinearSearch(c, col, rm);
+}
+
+void QueryExecutor::IndexedColumnFilterForTesting(const Constraint& c,
+                                                  const storage::Storage& col,
+                                                  RowMap* rm) {
+  switch (col.ValidateSearchConstraints(c.value, c.op)) {
+    case SearchValidationResult::kAllData:
+      return;
+    case SearchValidationResult::kNoData:
+      rm->Clear();
+      return;
+    case SearchValidationResult::kOk:
+      break;
+  }
+
+  IndexSearch(c, col, rm);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/query_executor.h b/src/trace_processor/db/query_executor.h
index 013dbf2..0b171ca 100644
--- a/src/trace_processor/db/query_executor.h
+++ b/src/trace_processor/db/query_executor.h
@@ -63,18 +63,14 @@
   }
 
   // Used only in unittests. Exposes private function.
-  static void BoundedColumnFilterForTesting(const Constraint& c,
-                                            const storage::Storage& col,
-                                            RowMap* rm) {
-    LinearSearch(c, col, rm);
-  }
+  static void BoundedColumnFilterForTesting(const Constraint&,
+                                            const storage::Storage&,
+                                            RowMap*);
 
   // Used only in unittests. Exposes private function.
-  static void IndexedColumnFilterForTesting(const Constraint& c,
-                                            const storage::Storage& col,
-                                            RowMap* rm) {
-    IndexSearch(c, col, rm);
-  }
+  static void IndexedColumnFilterForTesting(const Constraint&,
+                                            const storage::Storage&,
+                                            RowMap*);
 
  private:
   // Updates RowMap with result of filtering single column using the Constraint.
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index eb23903..38feabd 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -403,6 +403,24 @@
 }
 BENCHMARK(BM_QEDenseNullFilterIsNull)->ArgsProduct({{DB::V1, DB::V2}});
 
+static void BM_QEIdColumnWithIntAsDouble(benchmark::State& state) {
+  SliceTableForBenchmark table(state);
+  Constraint c{table.table_.track_id().index_in_table(), FilterOp::kEq,
+               SqlValue::Double(100)};
+  BenchmarkSliceTable(state, table, {c});
+}
+
+BENCHMARK(BM_QEIdColumnWithIntAsDouble)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QEIdColumnWithDouble(benchmark::State& state) {
+  SliceTableForBenchmark table(state);
+  Constraint c{table.table_.track_id().index_in_table(), FilterOp::kEq,
+               SqlValue::Double(100.5)};
+  BenchmarkSliceTable(state, table, {c});
+}
+
+BENCHMARK(BM_QEIdColumnWithDouble)->ArgsProduct({{DB::V1, DB::V2}});
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
index a2c05d8..7f2dfcc 100644
--- a/src/trace_processor/db/query_executor_unittest.cc
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -46,7 +46,7 @@
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
-  RowMap rm(0, 5);
+  RowMap rm(0, storage.size());
   QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
   ASSERT_EQ(rm.size(), 3u);
@@ -116,11 +116,12 @@
   std::iota(storage_data.begin(), storage_data.end(), 0);
   auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
       &storage_data, ColumnType::kInt64);
+
   BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
   storage::NullStorage storage(std::move(numeric), &bv);
 
   Constraint c{0, FilterOp::kIsNull, SqlValue()};
-  RowMap rm(0, 10);
+  RowMap rm(0, storage.size());
   QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
   ASSERT_EQ(rm.size(), 5u);
@@ -559,6 +560,29 @@
   ASSERT_EQ(res.size(), 0u);
 }
 
+TEST(QueryExecutor, MismatchedTypeIdWithDouble) {
+  IdStorage storage(5);
+
+  // Filter.
+  Constraint c{0, FilterOp::kGe, SqlValue::Double(1.5)};
+  QueryExecutor exec({&storage}, 5);
+  RowMap res = exec.Filter({c});
+
+  ASSERT_EQ(res.size(), 3u);
+}
+
+TEST(QueryExecutor, MismatchedTypeSetIdWithDouble) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+
+  // Filter.
+  Constraint c{0, FilterOp::kGe, SqlValue::Double(1.5)};
+  QueryExecutor exec({&storage}, storage.size());
+  RowMap res = exec.Filter({c});
+
+  ASSERT_EQ(res.size(), 9u);
+}
+
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 TEST(QueryExecutor, StringBinarySearchRegex) {
   StringPool pool;
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
index 55b7eb8..d13b530 100644
--- a/src/trace_processor/db/storage/BUILD.gn
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -18,6 +18,8 @@
   sources = [
     "arrangement_storage.cc",
     "arrangement_storage.h",
+    "dense_null_storage.cc",
+    "dense_null_storage.h",
     "dummy_storage.cc",
     "dummy_storage.h",
     "id_storage.cc",
@@ -35,6 +37,7 @@
     "string_storage.cc",
     "string_storage.h",
     "types.h",
+    "utils.cc",
     "utils.h",
   ]
   deps = [
@@ -67,6 +70,7 @@
   testonly = true
   sources = [
     "arrangement_storage_unittest.cc",
+    "dense_null_storage_unittest.cc",
     "id_storage_unittest.cc",
     "null_storage_unittest.cc",
     "numeric_storage_unittest.cc",
@@ -79,6 +83,7 @@
     ":storage",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
+    "../../../../include/perfetto/trace_processor:basic_types",
     "../../containers",
   ]
 }
diff --git a/src/trace_processor/db/storage/arrangement_storage.cc b/src/trace_processor/db/storage/arrangement_storage.cc
index fac7f68..703e4b7 100644
--- a/src/trace_processor/db/storage/arrangement_storage.cc
+++ b/src/trace_processor/db/storage/arrangement_storage.cc
@@ -40,7 +40,7 @@
                   inner_->size());
 }
 
-Storage::SearchValidationResult ArrangementStorage::ValidateSearchConstraints(
+SearchValidationResult ArrangementStorage::ValidateSearchConstraints(
     SqlValue sql_val,
     FilterOp op) const {
   return inner_->ValidateSearchConstraints(sql_val, op);
diff --git a/src/trace_processor/db/storage/arrangement_storage.h b/src/trace_processor/db/storage/arrangement_storage.h
index 97a944e..2f0ac10 100644
--- a/src/trace_processor/db/storage/arrangement_storage.h
+++ b/src/trace_processor/db/storage/arrangement_storage.h
@@ -32,8 +32,8 @@
   explicit ArrangementStorage(std::unique_ptr<Storage> inner,
                               const std::vector<uint32_t>* arrangement);
 
-  Storage::SearchValidationResult ValidateSearchConstraints(SqlValue, FilterOp)
-      const override;
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
 
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
diff --git a/src/trace_processor/db/storage/dense_null_storage.cc b/src/trace_processor/db/storage/dense_null_storage.cc
new file mode 100644
index 0000000..7a59d09
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.cc
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 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/db/storage/dense_null_storage.h"
+
+#include <cstdint>
+#include <variant>
+
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+DenseNullStorage::DenseNullStorage(std::unique_ptr<Storage> inner,
+                                   const BitVector* non_null)
+    : inner_(std::move(inner)), non_null_(non_null) {}
+
+SearchValidationResult DenseNullStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  if (op == FilterOp::kIsNull) {
+    return SearchValidationResult::kOk;
+  }
+
+  return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
+RangeOrBitVector DenseNullStorage::Search(FilterOp op,
+                                          SqlValue sql_val,
+                                          RowMap::Range in) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::Search");
+
+  if (op == FilterOp::kIsNull) {
+    switch (inner_->ValidateSearchConstraints(sql_val, op)) {
+      case SearchValidationResult::kNoData: {
+        // There is no need to search in underlying storage. It's enough to
+        // intersect the |non_null_|.
+        BitVector res = non_null_->IntersectRange(in.start, in.end);
+        res.Not();
+        res.Resize(in.end, false);
+        return RangeOrBitVector(std::move(res));
+      }
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(in);
+      case SearchValidationResult::kOk:
+        break;
+    }
+  }
+
+  RangeOrBitVector inner_res = inner_->Search(op, sql_val, in);
+  BitVector res;
+  if (inner_res.IsRange()) {
+    // If the inner storage returns a range, mask out the appropriate values in
+    // |non_null_| which matches the range. Then, resize to |in.end| as this
+    // is mandated by the API contract of |Storage::Search|.
+    RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+    PERFETTO_DCHECK(inner_range.end <= in.end);
+    PERFETTO_DCHECK(inner_range.start >= in.start);
+    res = non_null_->IntersectRange(inner_range.start, inner_range.end);
+    res.Resize(in.end, false);
+  } else {
+    res = std::move(inner_res).TakeIfBitVector();
+  }
+
+  if (op == FilterOp::kIsNull) {
+    // For IS NULL, we need to add any rows in |non_null_| which are zeros: we
+    // do this by taking the appropriate number of rows, inverting it and then
+    // bitwise or-ing the result with it.
+    BitVector non_null_copy = non_null_->Copy();
+    non_null_copy.Resize(in.end);
+    non_null_copy.Not();
+    res.Or(non_null_copy);
+  } else {
+    // For anything else, we just need to ensure that any rows which are null
+    // are removed as they would not match.
+    res.And(*non_null_);
+  }
+
+  PERFETTO_DCHECK(res.size() == in.end);
+  return RangeOrBitVector(std::move(res));
+}
+
+RangeOrBitVector DenseNullStorage::IndexSearch(FilterOp op,
+                                               SqlValue sql_val,
+                                               uint32_t* indices,
+                                               uint32_t indices_size,
+                                               bool sorted) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::IndexSearch");
+
+  RangeOrBitVector inner_res =
+      inner_->IndexSearch(op, sql_val, indices, indices_size, sorted);
+  if (inner_res.IsRange()) {
+    RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+    BitVector::Builder builder(indices_size, inner_range.start);
+    for (uint32_t i = inner_range.start; i < inner_range.end; ++i) {
+      builder.Append(non_null_->IsSet(indices[i]));
+    }
+    return RangeOrBitVector(std::move(builder).Build());
+  }
+
+  BitVector::Builder builder(indices_size);
+  for (uint32_t i = 0; i < indices_size; ++i) {
+    builder.Append(non_null_->IsSet(indices[i]));
+  }
+  BitVector non_null = std::move(builder).Build();
+
+  BitVector res = std::move(inner_res).TakeIfBitVector();
+
+  if (op == FilterOp::kIsNull) {
+    BitVector null = std::move(non_null);
+    null.Not();
+    res.Or(null);
+  } else {
+    res.And(non_null);
+  }
+
+  PERFETTO_DCHECK(res.size() == indices_size);
+  return RangeOrBitVector(std::move(res));
+}
+
+void DenseNullStorage::StableSort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Serialize(StorageProto* storage) const {
+  auto* null_storage = storage->set_dense_null_storage();
+  non_null_->Serialize(null_storage->set_bit_vector());
+  inner_->Serialize(null_storage->set_storage());
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/dense_null_storage.h b/src/trace_processor/db/storage/dense_null_storage.h
new file mode 100644
index 0000000..ec7b6e9
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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_DB_STORAGE_DENSE_NULL_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
+
+#include <memory>
+#include <variant>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage which introduces the layer of nullability but without changing the
+// "spacing" of the underlying storage i.e. this storage simply "masks" out
+// rows in the underlying storage with nulls.
+class DenseNullStorage : public Storage {
+ public:
+  DenseNullStorage(std::unique_ptr<Storage> inner, const BitVector* non_null);
+
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
+  RangeOrBitVector Search(FilterOp op,
+                          SqlValue value,
+                          RowMap::Range range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp op,
+                               SqlValue value,
+                               uint32_t* indices,
+                               uint32_t indices_count,
+                               bool sorted) const override;
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  uint32_t size() const override { return non_null_->size(); }
+
+ private:
+  std::unique_ptr<Storage> inner_;
+  const BitVector* non_null_ = nullptr;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
diff --git a/src/trace_processor/db/storage/dense_null_storage_unittest.cc b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
new file mode 100644
index 0000000..d8ec93c
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 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/db/storage/dense_null_storage.h"
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/fake_storage.h"
+#include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
+TEST(DenseNullStorage, NoFilteringSearch) {
+  std::vector<uint32_t> data{0, 1, 0, 1, 0};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3));
+}
+
+TEST(DenseNullStorage, RestrictInputSearch) {
+  std::vector<uint32_t> data{0, 1, 0, 1, 0};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(1, 3));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, RangeFilterSearch) {
+  auto fake = FakeStorage::SearchSubset(5, Range(1, 3));
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, BitvectorFilterSearch) {
+  auto fake = FakeStorage::SearchSubset(5, BitVector({0, 1, 1, 0, 0}));
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, IsNullSearch) {
+  auto fake = FakeStorage::SearchSubset(5, BitVector({1, 1, 0, 0, 1}));
+
+  BitVector bv{1, 0, 0, 1, 1};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kIsNull, SqlValue(), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 4));
+}
+
+TEST(DenseNullStorage, IndexSearch) {
+  std::vector<uint32_t> data{1, 0, 0, 1, 1, 1};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{1, 0, 0, 1, 1, 1};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  std::vector<uint32_t> index({5, 2, 3, 4, 1});
+  auto res = storage.IndexSearch(FilterOp::kGe, SqlValue::Long(0), index.data(),
+                                 static_cast<uint32_t>(index.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 2, 3));
+}
+
+TEST(DenseNullStorage, IsNullIndexSearch) {
+  auto fake = FakeStorage::SearchSubset(6, BitVector({0, 0, 0, 1, 1, 1}));
+
+  BitVector bv{0, 1, 0, 1, 1, 1};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  std::vector<uint32_t> index({5, 2, 3, 4, 1});
+  auto res = storage.IndexSearch(FilterOp::kIsNull, SqlValue(), index.data(),
+                                 static_cast<uint32_t>(index.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 3));
+}
+
+}  // namespace
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/dummy_storage.cc b/src/trace_processor/db/storage/dummy_storage.cc
index b4f0be5..ed30e8d 100644
--- a/src/trace_processor/db/storage/dummy_storage.cc
+++ b/src/trace_processor/db/storage/dummy_storage.cc
@@ -21,9 +21,8 @@
 namespace trace_processor {
 namespace storage {
 
-DummyStorage::SearchValidationResult DummyStorage::ValidateSearchConstraints(
-    SqlValue,
-    FilterOp) const {
+SearchValidationResult DummyStorage::ValidateSearchConstraints(SqlValue,
+                                                               FilterOp) const {
   PERFETTO_FATAL("Shouldn't be called");
 }
 
diff --git a/src/trace_processor/db/storage/fake_storage.cc b/src/trace_processor/db/storage/fake_storage.cc
index fd9ac2c..9d2316a 100644
--- a/src/trace_processor/db/storage/fake_storage.cc
+++ b/src/trace_processor/db/storage/fake_storage.cc
@@ -27,9 +27,8 @@
 FakeStorage::FakeStorage(uint32_t size, SearchStrategy strategy)
     : size_(size), strategy_(strategy) {}
 
-FakeStorage::SearchValidationResult FakeStorage::ValidateSearchConstraints(
-    SqlValue,
-    FilterOp) const {
+SearchValidationResult FakeStorage::ValidateSearchConstraints(SqlValue,
+                                                              FilterOp) const {
   return SearchValidationResult::kOk;
 }
 
diff --git a/src/trace_processor/db/storage/id_storage.cc b/src/trace_processor/db/storage/id_storage.cc
index bec9b041..0b9adee 100644
--- a/src/trace_processor/db/storage/id_storage.cc
+++ b/src/trace_processor/db/storage/id_storage.cc
@@ -15,7 +15,6 @@
  */
 
 #include "src/trace_processor/db/storage/id_storage.h"
-
 #include <optional>
 
 #include "perfetto/base/logging.h"
@@ -74,9 +73,8 @@
 
 }  // namespace
 
-IdStorage::SearchValidationResult IdStorage::ValidateSearchConstraints(
-    SqlValue val,
-    FilterOp op) const {
+SearchValidationResult IdStorage::ValidateSearchConstraints(SqlValue val,
+                                                            FilterOp op) const {
   // NULL checks.
   if (PERFETTO_UNLIKELY(val.is_null())) {
     if (op == FilterOp::kIsNotNull) {
@@ -118,24 +116,25 @@
     case SqlValue::kString:
       // Any string is always more than any numeric.
       if (op == FilterOp::kLt || op == FilterOp::kLe) {
-        return Storage::SearchValidationResult::kAllData;
+        return SearchValidationResult::kAllData;
       }
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
     case SqlValue::kBytes:
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
   }
 
-  // TODO(b/307482437): Remove after adding support for double
-  PERFETTO_CHECK(val.type != SqlValue::kDouble);
-
   // Bounds of the value.
-  if (PERFETTO_UNLIKELY(val.AsLong() > std::numeric_limits<uint32_t>::max())) {
+  double_t num_val = val.type == SqlValue::kLong
+                         ? static_cast<double_t>(val.AsLong())
+                         : val.AsDouble();
+
+  if (PERFETTO_UNLIKELY(num_val > std::numeric_limits<uint32_t>::max())) {
     if (op == FilterOp::kLe || op == FilterOp::kLt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
     return SearchValidationResult::kNoData;
   }
-  if (PERFETTO_UNLIKELY(val.AsLong() < std::numeric_limits<uint32_t>::min())) {
+  if (PERFETTO_UNLIKELY(num_val < std::numeric_limits<uint32_t>::min())) {
     if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
@@ -158,17 +157,21 @@
 
   PERFETTO_DCHECK(search_range.end <= size_);
 
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(search_range);
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, search_range.end));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
   }
 
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
+
   if (op == FilterOp::kNe) {
     BitVector ret(search_range.start, false);
     ret.Resize(search_range.end, true);
@@ -191,14 +194,17 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(Range(0, indices_size));
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, indices_size));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
   }
 
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
diff --git a/src/trace_processor/db/storage/id_storage_unittest.cc b/src/trace_processor/db/storage/id_storage_unittest.cc
index 0db3159..ab04d86 100644
--- a/src/trace_processor/db/storage/id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/id_storage_unittest.cc
@@ -16,6 +16,8 @@
 #include "src/trace_processor/db/storage/id_storage.h"
 #include <limits>
 
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 #include "test/gtest_and_gmock.h"
 
@@ -26,86 +28,88 @@
   return std::tie(a.start, a.end) == std::tie(b.start, b.end);
 }
 
+inline bool operator==(const BitVector& a, const BitVector& b) {
+  return a.size() == b.size() && a.CountSetBits() == b.CountSetBits();
+}
+
 namespace storage {
 namespace {
 
+using testing::ElementsAre;
+using testing::IsEmpty;
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
 using Range = RowMap::Range;
 
 TEST(IdStorageUnittest, InvalidSearchConstraints) {
   IdStorage storage(100);
-  Range test_range(10, 20);
-  Range empty_range;
 
   // NULL checks
-  SqlValue val;
-  val.type = SqlValue::kNull;
-  Range search_result =
-      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, test_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue(), FilterOp::kIsNull),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue(), FilterOp::kIsNotNull),
+            SearchValidationResult::kAllData);
 
   // FilterOp checks
-  search_result =
-      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Long(15), FilterOp::kGlob),
+      SearchValidationResult::kNoData);
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Long(15), FilterOp::kRegex),
+      SearchValidationResult::kNoData);
 
   // Type checks
-  search_result =
-      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue::String("cheese"),
+                                              FilterOp::kGe),
+            SearchValidationResult::kNoData);
+
+  // With double
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Double(-1), FilterOp::kGe),
+      SearchValidationResult::kAllData);
 
   // Value bounds
   SqlValue max_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
-  search_result =
-      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, test_range);
-  search_result =
-      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, test_range);
-  search_result =
-      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, test_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
-  SqlValue min_val = SqlValue::Long(
-      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
-  search_result =
-      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, test_range);
-  search_result =
-      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, test_range);
-  search_result =
-      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, test_range);
+  SqlValue min_val = SqlValue::Long(-1);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 }
 
 TEST(IdStorageUnittest, SearchEqSimple) {
@@ -179,13 +183,6 @@
   ASSERT_EQ(bv.CountSetBits(), 39u);
 }
 
-TEST(IdStorageUnittest, SearchNeInvalidNum) {
-  IdStorage storage(100);
-  Range r = storage.Search(FilterOp::kNe, SqlValue::Long(-1), Range(30, 70))
-                .TakeIfRange();
-  ASSERT_EQ(r.size(), 40u);
-}
-
 TEST(IdStorageUnittest, IndexSearchEqSimple) {
   IdStorage storage(12);
   std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
@@ -285,6 +282,61 @@
   ASSERT_TRUE(bv.IsSet(5));
 }
 
+TEST(IdStorageUnittest, SearchWithIdAsDoubleSimple) {
+  IdStorage storage(100);
+  SqlValue double_val = SqlValue::Double(15.0);
+  SqlValue long_val = SqlValue::Long(15);
+  Range range(10, 20);
+
+  auto res_double = storage.Search(FilterOp::kEq, double_val, range);
+  auto res_long = storage.Search(FilterOp::kEq, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kNe, double_val, range);
+  res_long = storage.Search(FilterOp::kNe, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kLe, double_val, range);
+  res_long = storage.Search(FilterOp::kLe, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kLt, double_val, range);
+  res_long = storage.Search(FilterOp::kLt, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kGe, double_val, range);
+  res_long = storage.Search(FilterOp::kGe, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kGt, double_val, range);
+  res_long = storage.Search(FilterOp::kGt, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+}
+
+TEST(IdStorageUnittest, SearchWithIdAsDouble) {
+  IdStorage storage(100);
+  Range range(10, 20);
+  SqlValue val = SqlValue::Double(15.5);
+
+  auto res = storage.Search(FilterOp::kEq, val, range);
+  ASSERT_THAT(ToIndexVector(res), IsEmpty());
+
+  res = storage.Search(FilterOp::kNe, val, range);
+  ASSERT_EQ(ToIndexVector(res).size(), 20u);
+
+  res = storage.Search(FilterOp::kLe, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(10, 11, 12, 13, 14, 15));
+
+  res = storage.Search(FilterOp::kLt, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(10, 11, 12, 13, 14, 15));
+
+  res = storage.Search(FilterOp::kGe, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(16, 17, 18, 19));
+
+  res = storage.Search(FilterOp::kGt, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(16, 17, 18, 19));
+}
+
 TEST(IdStorageUnittest, Sort) {
   std::vector<uint32_t> order{4, 3, 6, 1, 5};
   IdStorage storage(10);
diff --git a/src/trace_processor/db/storage/null_storage.cc b/src/trace_processor/db/storage/null_storage.cc
index 6de2759..0ded1af 100644
--- a/src/trace_processor/db/storage/null_storage.cc
+++ b/src/trace_processor/db/storage/null_storage.cc
@@ -17,11 +17,11 @@
 #include "src/trace_processor/db/storage/null_storage.h"
 
 #include <cstdint>
-#include <variant>
 
 #include "protos/perfetto/trace_processor/serialization.pbzero.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 #include "src/trace_processor/tp_metatrace.h"
 
@@ -32,10 +32,10 @@
 
 using Range = RowMap::Range;
 
-RangeOrBitVector ReconcileStorageResult(FilterOp op,
-                                        const BitVector& non_null,
-                                        RangeOrBitVector storage_result,
-                                        Range in_range) {
+BitVector ReconcileStorageResult(FilterOp op,
+                                 const BitVector& non_null,
+                                 RangeOrBitVector storage_result,
+                                 Range in_range) {
   PERFETTO_CHECK(in_range.end <= non_null.size());
 
   // Reconcile the results of the Search operation with the non-null indices
@@ -68,14 +68,18 @@
     null.Not();
     res.Or(null);
   }
-  return RangeOrBitVector(std::move(res));
+  return res;
 }
 
 }  // namespace
 
-Storage::SearchValidationResult NullStorage::ValidateSearchConstraints(
+SearchValidationResult NullStorage::ValidateSearchConstraints(
     SqlValue sql_val,
     FilterOp op) const {
+  if (op == FilterOp::kIsNull) {
+    return SearchValidationResult::kOk;
+  }
+
   return storage_->ValidateSearchConstraints(sql_val, op);
 }
 
@@ -90,13 +94,33 @@
                                      RowMap::Range in) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "NullStorage::Search");
 
+  if (op == FilterOp::kIsNull) {
+    switch (storage_->ValidateSearchConstraints(sql_val, op)) {
+      case SearchValidationResult::kNoData: {
+        // There is no need to search in underlying storage. It's enough to
+        // intersect the |non_null_|.
+        BitVector res = non_null_->IntersectRange(in.start, in.end);
+        res.Not();
+        res.Resize(in.end, false);
+        return RangeOrBitVector(std::move(res));
+      }
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(in);
+      case SearchValidationResult::kOk:
+        break;
+    }
+  }
+
   // Figure out the bounds of the indices in the underlying storage and search
   // it.
   uint32_t start = non_null_->CountSetBits(in.start);
   uint32_t end = non_null_->CountSetBits(in.end);
-  return ReconcileStorageResult(
+  BitVector res = ReconcileStorageResult(
       op, *non_null_, storage_->Search(op, sql_val, RowMap::Range(start, end)),
       in);
+
+  PERFETTO_DCHECK(res.size() == in.end);
+  return RangeOrBitVector(std::move(res));
 }
 
 RangeOrBitVector NullStorage::IndexSearch(FilterOp op,
@@ -106,6 +130,24 @@
                                           bool sorted) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "NullStorage::IndexSearch");
 
+  if (op == FilterOp::kIsNull) {
+    switch (storage_->ValidateSearchConstraints(sql_val, op)) {
+      case SearchValidationResult::kNoData: {
+        BitVector::Builder null_indices(indices_size);
+        for (uint32_t* it = indices; it != indices + indices_size; it++) {
+          null_indices.Append(!non_null_->IsSet(*it));
+        }
+        // There is no need to search in underlying storage. We should just
+        // check if the index is set in |non_null_|.
+        return RangeOrBitVector(std::move(null_indices).Build());
+      }
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, indices_size));
+      case SearchValidationResult::kOk:
+        break;
+    }
+  }
+
   BitVector::Builder storage_non_null(indices_size);
   std::vector<uint32_t> storage_iv;
   storage_iv.reserve(indices_size);
@@ -119,8 +161,12 @@
   RangeOrBitVector range_or_bv =
       storage_->IndexSearch(op, sql_val, storage_iv.data(),
                             static_cast<uint32_t>(storage_iv.size()), sorted);
-  return ReconcileStorageResult(op, std::move(storage_non_null).Build(),
-                                std::move(range_or_bv), Range(0, indices_size));
+  BitVector res =
+      ReconcileStorageResult(op, std::move(storage_non_null).Build(),
+                             std::move(range_or_bv), Range(0, indices_size));
+
+  PERFETTO_DCHECK(res.size() == indices_size);
+  return RangeOrBitVector(std::move(res));
 }
 
 void NullStorage::StableSort(uint32_t*, uint32_t) const {
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
index 0b792de..b090cb8 100644
--- a/src/trace_processor/db/storage/numeric_storage.cc
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -195,8 +195,9 @@
 
 }  // namespace
 
-NumericStorageBase::SearchValidationResult
-NumericStorageBase::ValidateSearchConstraints(SqlValue val, FilterOp op) const {
+SearchValidationResult NumericStorageBase::ValidateSearchConstraints(
+    SqlValue val,
+    FilterOp op) const {
   // NULL checks.
   if (PERFETTO_UNLIKELY(val.is_null())) {
     if (op == FilterOp::kIsNotNull) {
@@ -237,11 +238,11 @@
     case SqlValue::kString:
       // Any string is always more than any numeric.
       if (op == FilterOp::kLt || op == FilterOp::kLe) {
-        return Storage::SearchValidationResult::kAllData;
+        return SearchValidationResult::kAllData;
       }
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
     case SqlValue::kBytes:
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
   }
 
   // TODO(b/307482437): There is currently no support for comparison with double
@@ -289,7 +290,7 @@
 
   switch (extreme_validator) {
     case kOk:
-      return Storage::SearchValidationResult::kOk;
+      return SearchValidationResult::kOk;
     case kTooBig:
       if (op == FilterOp::kLt || op == FilterOp::kLe || op == FilterOp::kNe) {
         return SearchValidationResult::kAllData;
@@ -316,16 +317,6 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(Range(0, search_range.end));
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
-  }
-
   NumericValue val = GetNumericTypeVariant(type_, sql_val);
 
   if (is_sorted_) {
@@ -356,15 +347,6 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(Range(0, indices_size));
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
-  }
   NumericValue val = GetNumericTypeVariant(type_, sql_val);
   if (sorted) {
     return RangeOrBitVector(
diff --git a/src/trace_processor/db/storage/numeric_storage_unittest.cc b/src/trace_processor/db/storage/numeric_storage_unittest.cc
index 36fa51a..ffcc389 100644
--- a/src/trace_processor/db/storage/numeric_storage_unittest.cc
+++ b/src/trace_processor/db/storage/numeric_storage_unittest.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 #include "src/trace_processor/db/storage/numeric_storage.h"
+#include <cstdint>
 
 #include "src/trace_processor/db/storage/types.h"
 #include "test/gtest_and_gmock.h"
@@ -28,8 +29,21 @@
 namespace storage {
 namespace {
 
+using testing::ElementsAre;
+using testing::IsEmpty;
 using Range = RowMap::Range;
 
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
 TEST(NumericStorageUnittest, InvalidSearchConstraintsGeneralChecks) {
   std::vector<uint32_t> data_vec(128);
   std::iota(data_vec.begin(), data_vec.end(), 0);
@@ -40,30 +54,23 @@
   Range empty_range;
 
   // NULL checks
-  SqlValue val;
-  val.type = SqlValue::kNull;
-  Range search_result =
-      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue(), FilterOp::kIsNull),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue(), FilterOp::kIsNotNull),
+            SearchValidationResult::kAllData);
 
   // FilterOp checks
-  search_result =
-      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Long(15), FilterOp::kGlob),
+      SearchValidationResult::kNoData);
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Long(15), FilterOp::kRegex),
+      SearchValidationResult::kNoData);
 
   // Type checks
-  search_result =
-      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue::String("cheese"),
+                                              FilterOp::kGe),
+            SearchValidationResult::kNoData);
 }
 
 TEST(NumericStorageUnittest, InvalidValueBoundsUint32) {
@@ -71,53 +78,37 @@
   std::iota(data_vec.begin(), data_vec.end(), 0);
   NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32);
 
-  Range test_range(20, 100);
-  Range full_range(0, 100);
-  Range empty_range;
-
   SqlValue max_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
-  Range search_result =
-      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
   SqlValue min_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
-  search_result =
-      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 }
 
 TEST(NumericStorageUnittest, InvalidValueBoundsInt32) {
@@ -125,53 +116,37 @@
   std::iota(data_vec.begin(), data_vec.end(), 0);
   NumericStorage<int32_t> storage(&data_vec, ColumnType::kInt32);
 
-  Range test_range(20, 100);
-  Range full_range(0, 100);
-  Range empty_range;
-
   SqlValue max_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 10);
-  Range search_result =
-      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
   SqlValue min_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<int32_t>::min()) - 1);
-  search_result =
-      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 }
 
 TEST(NumericStorageUnittest, StableSortTrivial) {
@@ -198,7 +173,111 @@
   ASSERT_EQ(out, stable_out);
 }
 
-TEST(NumericStorageUnittest, CompareFast) {
+TEST(NumericStorageUnittest, Search) {
+  std::vector<int32_t> data_vec{-5, 5, -4, 4, -3, 3, 0};
+  NumericStorage<int32_t> storage(&data_vec, ColumnType::kInt32);
+  Range test_range(1, 5);
+  SqlValue val = SqlValue::Long(4);
+
+  auto res = storage.Search(FilterOp::kEq, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(3));
+
+  res = storage.Search(FilterOp::kNe, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 2, 4));
+
+  res = storage.Search(FilterOp::kLt, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(2, 4));
+
+  res = storage.Search(FilterOp::kLe, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(2, 3, 4));
+
+  res = storage.Search(FilterOp::kGt, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+
+  res = storage.Search(FilterOp::kGe, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3));
+}
+
+TEST(NumericStorageUnittest, SearchCompareWithNegative) {
+  std::vector<int32_t> data_vec{-5, 5, -4, 4, -3, 3, 0};
+  NumericStorage<int32_t> storage(&data_vec, ColumnType::kInt32);
+  Range test_range(1, 5);
+  SqlValue val = SqlValue::Long(-3);
+
+  auto res = storage.Search(FilterOp::kEq, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(4));
+
+  res = storage.Search(FilterOp::kNe, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 2, 3));
+
+  res = storage.Search(FilterOp::kLt, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(2));
+
+  res = storage.Search(FilterOp::kLe, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(2, 4));
+
+  res = storage.Search(FilterOp::kGt, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3));
+
+  res = storage.Search(FilterOp::kGe, val, test_range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3, 4));
+}
+
+TEST(NumericStorageUnittest, IndexSearch) {
+  std::vector<int32_t> data_vec{-5, 5, -4, 4, -3, 3, 0};
+  NumericStorage<int32_t> storage(&data_vec, ColumnType::kInt32);
+
+  // -5, -3, -3, 3, 5, 0
+  std::vector<uint32_t> indices{0, 4, 4, 5, 1, 6};
+  SqlValue val = SqlValue::Long(3);
+
+  auto res = storage.IndexSearch(FilterOp::kEq, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(3));
+
+  res = storage.IndexSearch(FilterOp::kNe, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 4, 5));
+
+  res = storage.IndexSearch(FilterOp::kLt, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 5));
+
+  res = storage.IndexSearch(FilterOp::kLe, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 3, 5));
+
+  res = storage.IndexSearch(FilterOp::kGt, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(4));
+
+  res = storage.IndexSearch(FilterOp::kGe, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(3, 4));
+}
+
+TEST(NumericStorageUnittest, IndexSearchCompareWithNegative) {
+  std::vector<int32_t> data_vec{-5, 5, -4, 4, -3, 3, 0};
+  NumericStorage<int32_t> storage(&data_vec, ColumnType::kInt32);
+
+  // -5, -3, -3, 3, 5, 0
+  std::vector<uint32_t> indices{0, 4, 4, 5, 1, 6};
+  SqlValue val = SqlValue::Long(-3);
+
+  auto res = storage.IndexSearch(FilterOp::kEq, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 2));
+
+  res = storage.IndexSearch(FilterOp::kNe, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 3, 4, 5));
+
+  res = storage.IndexSearch(FilterOp::kLt, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0));
+
+  res = storage.IndexSearch(FilterOp::kLe, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2));
+
+  res = storage.IndexSearch(FilterOp::kGt, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(3, 4, 5));
+
+  res = storage.IndexSearch(FilterOp::kGe, val, indices.data(), 6, false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 2, 3, 4, 5));
+}
+
+TEST(NumericStorageUnittest, SearchFast) {
   std::vector<uint32_t> data_vec(128);
   std::iota(data_vec.begin(), data_vec.end(), 0);
   NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32);
@@ -210,7 +289,7 @@
   ASSERT_EQ(bv.IndexOfNthSet(0), 100u);
 }
 
-TEST(NumericStorageUnittest, CompareSorted) {
+TEST(NumericStorageUnittest, SearchSorted) {
   std::vector<uint32_t> data_vec(128);
   std::iota(data_vec.begin(), data_vec.end(), 0);
   NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32, true);
@@ -222,7 +301,7 @@
   ASSERT_EQ(range.end, 128u);
 }
 
-TEST(NumericStorageUnittest, CompareSortedNe) {
+TEST(NumericStorageUnittest, SearchSortedNe) {
   std::vector<uint32_t> data_vec(128);
   std::iota(data_vec.begin(), data_vec.end(), 0);
   NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32, true);
@@ -232,7 +311,7 @@
   ASSERT_EQ(bv.CountSetBits(), 127u);
 }
 
-TEST(NumericStorageUnittest, CompareSortedSubset) {
+TEST(NumericStorageUnittest, SearchSortedSubset) {
   std::vector<uint32_t> data_vec(128);
   std::iota(data_vec.begin(), data_vec.end(), 0);
   NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32, true);
@@ -244,7 +323,7 @@
   ASSERT_EQ(range.end, 104u);
 }
 
-TEST(NumericStorageUnittest, CompareSortedIndexesGreaterEqual) {
+TEST(NumericStorageUnittest, IndexSearcgExtrinsicGe) {
   std::vector<uint32_t> data_vec{30, 40, 50, 60, 90, 80, 70, 0, 10, 20};
   std::vector<uint32_t> sorted_order{7, 8, 9, 0, 1, 2, 3, 6, 5, 4};
 
@@ -260,7 +339,7 @@
   ASSERT_EQ(range.end, 10u);
 }
 
-TEST(NumericStorageUnittest, CompareSortedIndexesLess) {
+TEST(NumericStorageUnittest, IndexSearchExtrinsicLt) {
   std::vector<uint32_t> data_vec{30, 40, 50, 60, 90, 80, 70, 0, 10, 20};
   std::vector<uint32_t> sorted_order{7, 8, 9, 0, 1, 2, 3, 6, 5, 4};
 
@@ -276,7 +355,7 @@
   ASSERT_EQ(range.end, 6u);
 }
 
-TEST(NumericStorageUnittest, CompareSortedIndexesEqual) {
+TEST(NumericStorageUnittest, IndexSearchExtrinsicEq) {
   std::vector<uint32_t> data_vec{30, 40, 50, 60, 90, 80, 70, 0, 10, 20};
   std::vector<uint32_t> sorted_order{7, 8, 9, 0, 1, 2, 3, 6, 5, 4};
 
diff --git a/src/trace_processor/db/storage/selector_storage.cc b/src/trace_processor/db/storage/selector_storage.cc
index 934b900..d4865e7 100644
--- a/src/trace_processor/db/storage/selector_storage.cc
+++ b/src/trace_processor/db/storage/selector_storage.cc
@@ -31,7 +31,7 @@
                                  const BitVector* selector)
     : inner_(std::move(inner)), selector_(selector) {}
 
-Storage::SearchValidationResult SelectorStorage::ValidateSearchConstraints(
+SearchValidationResult SelectorStorage::ValidateSearchConstraints(
     SqlValue sql_val,
     FilterOp op) const {
   return inner_->ValidateSearchConstraints(sql_val, op);
diff --git a/src/trace_processor/db/storage/selector_storage.h b/src/trace_processor/db/storage/selector_storage.h
index 9d368e2..518e22b 100644
--- a/src/trace_processor/db/storage/selector_storage.h
+++ b/src/trace_processor/db/storage/selector_storage.h
@@ -31,8 +31,8 @@
  public:
   SelectorStorage(std::unique_ptr<Storage> storage, const BitVector* non_null);
 
-  Storage::SearchValidationResult ValidateSearchConstraints(SqlValue, FilterOp)
-      const override;
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
 
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
diff --git a/src/trace_processor/db/storage/set_id_storage.cc b/src/trace_processor/db/storage/set_id_storage.cc
index 94b94dc..dd0af25 100644
--- a/src/trace_processor/db/storage/set_id_storage.cc
+++ b/src/trace_processor/db/storage/set_id_storage.cc
@@ -58,7 +58,7 @@
 
 }  // namespace
 
-SetIdStorage::SearchValidationResult SetIdStorage::ValidateSearchConstraints(
+SearchValidationResult SetIdStorage::ValidateSearchConstraints(
     SqlValue val,
     FilterOp op) const {
   // NULL checks.
@@ -101,24 +101,25 @@
     case SqlValue::kString:
       // Any string is always more than any numeric.
       if (op == FilterOp::kLt || op == FilterOp::kLe) {
-        return Storage::SearchValidationResult::kAllData;
+        return SearchValidationResult::kAllData;
       }
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
     case SqlValue::kBytes:
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
   }
 
-  // TODO(b/307482437): Remove after adding support for double
-  PERFETTO_CHECK(val.type != SqlValue::kDouble);
-
   // Bounds of the value.
-  if (PERFETTO_UNLIKELY(val.AsLong() > std::numeric_limits<uint32_t>::max())) {
+  double_t num_val = val.type == SqlValue::kLong
+                         ? static_cast<double_t>(val.AsLong())
+                         : val.AsDouble();
+
+  if (PERFETTO_UNLIKELY(num_val > std::numeric_limits<uint32_t>::max())) {
     if (op == FilterOp::kLe || op == FilterOp::kLt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
     return SearchValidationResult::kNoData;
   }
-  if (PERFETTO_UNLIKELY(val.AsLong() < std::numeric_limits<uint32_t>::min())) {
+  if (PERFETTO_UNLIKELY(num_val < std::numeric_limits<uint32_t>::min())) {
     if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
@@ -131,6 +132,8 @@
 RangeOrBitVector SetIdStorage::Search(FilterOp op,
                                       SqlValue sql_val,
                                       RowMap::Range search_range) const {
+  PERFETTO_DCHECK(search_range.end <= size());
+
   PERFETTO_TP_TRACE(metatrace::Category::DB, "SetIdStorage::Search",
                     [&search_range, op](metatrace::Record* r) {
                       r->AddArg("Start", std::to_string(search_range.start));
@@ -139,16 +142,17 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  PERFETTO_DCHECK(search_range.end <= size());
-
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(Range(0, search_range.end));
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, search_range.end));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
   }
 
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
@@ -179,14 +183,17 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(Range(0, indices_size));
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, indices_size));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
   }
 
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
diff --git a/src/trace_processor/db/storage/set_id_storage_unittest.cc b/src/trace_processor/db/storage/set_id_storage_unittest.cc
index 5023345..b7cbfc7 100644
--- a/src/trace_processor/db/storage/set_id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/set_id_storage_unittest.cc
@@ -24,89 +24,80 @@
   return std::tie(a.start, a.end) == std::tie(b.start, b.end);
 }
 
+inline bool operator==(const BitVector& a, const BitVector& b) {
+  return a.size() == b.size() && a.CountSetBits() == b.CountSetBits();
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
 TEST(SetIdStorageUnittest, InvalidSearchConstraints) {
   std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
   SetIdStorage storage(&storage_data);
-
-  Range test_range(3, 9);
-  Range full_range(0, 9);
-  Range empty_range;
-
   // NULL checks
-  SqlValue val;
-  val.type = SqlValue::kNull;
-  Range search_result =
-      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue(), FilterOp::kIsNull),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue(), FilterOp::kIsNotNull),
+            SearchValidationResult::kAllData);
 
   // FilterOp checks
-  search_result =
-      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Long(15), FilterOp::kGlob),
+      SearchValidationResult::kNoData);
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Long(15), FilterOp::kRegex),
+      SearchValidationResult::kNoData);
 
   // Type checks
-  search_result =
-      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
-          .TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(SqlValue::String("cheese"),
+                                              FilterOp::kGe),
+            SearchValidationResult::kNoData);
 
   // Value bounds
   SqlValue max_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
-  search_result =
-      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kGt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kLt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
   SqlValue min_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
-  search_result =
-      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
-  search_result =
-      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, full_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGe),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGt),
+            SearchValidationResult::kAllData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kNe),
+            SearchValidationResult::kAllData);
 
-  search_result =
-      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
-  search_result =
-      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
-  ASSERT_EQ(search_result, empty_range);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLe),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kLt),
+            SearchValidationResult::kNoData);
+  ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kEq),
+            SearchValidationResult::kNoData);
 }
 
 TEST(SetIdStorageUnittest, SearchEqSimple) {
@@ -305,6 +296,52 @@
   ASSERT_EQ(bv.CountSetBits(), 2u);
 }
 
+TEST(SetIdStorageUnittest, SearchWithIdAsDoubleSimple) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+  SqlValue double_val = SqlValue::Double(7.0);
+  SqlValue long_val = SqlValue::Long(7);
+  Range range(1, 9);
+
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kEq, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kEq, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kNe, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kNe, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kLe, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kLe, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kLt, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kLt, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kGe, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kGe, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kGt, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kGt, long_val, range)));
+}
+
+TEST(SetIdStorageUnittest, SearchWithIdAsDouble) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+  SqlValue val = SqlValue::Double(7.5);
+  Range range(5, 10);
+
+  Range res = storage.Search(FilterOp::kEq, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range());
+
+  res = storage.Search(FilterOp::kNe, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(0, 10));
+
+  res = storage.Search(FilterOp::kLe, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(5, 9));
+
+  res = storage.Search(FilterOp::kLt, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(5, 9));
+
+  res = storage.Search(FilterOp::kGe, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(9, 10));
+
+  res = storage.Search(FilterOp::kGt, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(9, 10));
+}
+
 }  // namespace
 }  // namespace storage
 }  // namespace trace_processor
diff --git a/src/trace_processor/db/storage/storage.h b/src/trace_processor/db/storage/storage.h
index 2a9c9fb..eb0c52a 100644
--- a/src/trace_processor/db/storage/storage.h
+++ b/src/trace_processor/db/storage/storage.h
@@ -33,8 +33,6 @@
  public:
   using StorageProto = protos::pbzero::SerializedColumn_Storage;
 
-  enum class SearchValidationResult { kOk = 0, kAllData = 1, kNoData = 2 };
-
   virtual ~Storage();
 
   // Verifies whether any further filtering is needed and if not, whether the
@@ -42,8 +40,6 @@
   // the |Search| and |IndexSearch| in special cases.
   //
   // Notes for callers:
-  // * This function is being called by Search and IndexSearch and there is no
-  //   need to call it before searches.
   // * The SqlValue and FilterOp have to be valid in Sqlite: it will crash if
   //   either: value is NULL and operation is different than "IS NULL" and "IS
   //   NOT NULL" or the operation is "IS NULL" and "IS NOT NULL" and value is
@@ -58,6 +54,11 @@
   // which match the constraint. If a BitVector is returned, it will be
   // *precisely* as large as |range.end|.
   //
+  // Notes for callers:
+  //  * Should only be called if ValidateSearchContraints returned kOk.
+  //  * Callers should note that the return value of this function corresponds
+  //    to positions in the storage.
+  //
   // Notes for implementors:
   //  * Implementations should ensure that the return value *only* includes
   //    positions in |range| as callers will expect this to be true and can
@@ -76,6 +77,7 @@
   // be *precisely* as large as |indices_count|.
   //
   // Notes for callers:
+  //  * Should only be called if ValidateSearchContraints returned kOk.
   //  * Callers should note that the return value of this function corresponds
   //    to positions in |indices| *not* positions in the storage.
   //
diff --git a/src/trace_processor/db/storage/string_storage.cc b/src/trace_processor/db/storage/string_storage.cc
index d9c9b98..7d2bc64 100644
--- a/src/trace_processor/db/storage/string_storage.cc
+++ b/src/trace_processor/db/storage/string_storage.cc
@@ -163,7 +163,7 @@
 
 }  // namespace
 
-StringStorage::SearchValidationResult StringStorage::ValidateSearchConstraints(
+SearchValidationResult StringStorage::ValidateSearchConstraints(
     SqlValue val,
     FilterOp op) const {
   // Type checks.
@@ -175,11 +175,11 @@
     case SqlValue::kDouble:
       // Any string is always more than any numeric.
       if (op == FilterOp::kGt || op == FilterOp::kGe) {
-        return Storage::SearchValidationResult::kAllData;
+        return SearchValidationResult::kAllData;
       }
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
     case SqlValue::kBytes:
-      return Storage::SearchValidationResult::kNoData;
+      return SearchValidationResult::kNoData;
   }
 
   return SearchValidationResult::kOk;
@@ -196,16 +196,6 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(Range(0, search_range.end));
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
-  }
-
   if (is_sorted_) {
     if (op != FilterOp::kNe) {
       return RangeOrBitVector(BinarySearchIntrinsic(op, sql_val, search_range));
@@ -233,16 +223,6 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // After this switch we assume the search is valid.
-  switch (ValidateSearchConstraints(sql_val, op)) {
-    case SearchValidationResult::kOk:
-      break;
-    case SearchValidationResult::kAllData:
-      return RangeOrBitVector(Range(0, indices_size));
-    case SearchValidationResult::kNoData:
-      return RangeOrBitVector(Range());
-  }
-
   if (indices_sorted) {
     return RangeOrBitVector(
         BinarySearchExtrinsic(op, sql_val, indices, indices_size));
diff --git a/src/trace_processor/db/storage/types.h b/src/trace_processor/db/storage/types.h
index 6884988..83c5d01 100644
--- a/src/trace_processor/db/storage/types.h
+++ b/src/trace_processor/db/storage/types.h
@@ -23,6 +23,13 @@
 namespace perfetto {
 namespace trace_processor {
 
+// Result of calling Storage::ValidateSearchResult function.
+enum class SearchValidationResult {
+  kOk = 0,       // It makes sense to run search
+  kAllData = 1,  // Don't run search, all data passes the constraint.
+  kNoData = 2    // Don't run search, no data passes the constraint.
+};
+
 // Used for result of filtering, which is sometimes (for more optimised
 // operations) a Range and BitVector otherwise. Stores a variant of Range and
 // BitVector.
diff --git a/src/trace_processor/db/storage/utils.cc b/src/trace_processor/db/storage/utils.cc
new file mode 100644
index 0000000..ba12e40
--- /dev/null
+++ b/src/trace_processor/db/storage/utils.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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/db/storage/utils.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace utils {
+
+SearchValidationResult CompareIntColumnWithDouble(SqlValue* sql_val,
+                                                  FilterOp op) {
+  double double_val = sql_val->AsDouble();
+  if (std::equal_to<double>()(
+          double_val, static_cast<double>(static_cast<uint32_t>(double_val)))) {
+    // If double is the same as uint32_t, we should just "cast" the |sql_val|
+    // to be treated as long.
+    *sql_val = SqlValue::Long(static_cast<int64_t>(double_val));
+    return SearchValidationResult::kOk;
+  }
+  // Logic for when the value is a real double.
+  switch (op) {
+    case FilterOp::kEq:
+      return SearchValidationResult::kNoData;
+    case FilterOp::kNe:
+      return SearchValidationResult::kAllData;
+
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+      *sql_val = SqlValue::Long(static_cast<int64_t>(std::floor(double_val)));
+      return SearchValidationResult::kOk;
+
+    case FilterOp::kLt:
+    case FilterOp::kGe:
+      *sql_val = SqlValue::Long(static_cast<int64_t>(std::ceil(double_val)));
+      return SearchValidationResult::kOk;
+
+    case FilterOp::kIsNotNull:
+    case FilterOp::kIsNull:
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      PERFETTO_FATAL("Invalid filter operation");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+}  // namespace utils
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/utils.h b/src/trace_processor/db/storage/utils.h
index 55e18f8..3d38545 100644
--- a/src/trace_processor/db/storage/utils.h
+++ b/src/trace_processor/db/storage/utils.h
@@ -16,7 +16,10 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_UTILS_H_
 #define SRC_TRACE_PROCESSOR_DB_STORAGE_UTILS_H_
 
+#include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/types.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -83,6 +86,12 @@
   }
 }
 
+// Used for comparing the integer column ({u|}int{32|64}) with a double value.
+// If further search is required it would return kOk and change the SqlValue to
+// a `SqlLong` which would return real results.
+SearchValidationResult CompareIntColumnWithDouble(SqlValue* sql_val,
+                                                  FilterOp op);
+
 }  // namespace utils
 
 }  // namespace storage
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.cc b/src/trace_processor/perfetto_sql/engine/created_function.cc
index 162dbc6..9c32a97 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/created_function.cc
@@ -587,6 +587,10 @@
                                   SqlValue& out,
                                   Destructors&) {
   State* state = static_cast<State*>(ctx);
+
+  // Enter the function and ensure that we have a statement allocated.
+  RETURN_IF_ERROR(state->PushStackEntry());
+
   if (argc != state->prototype().arguments.size()) {
     return base::ErrStatus(
         "%s: invalid number of args; expected %zu, received %zu",
@@ -608,9 +612,6 @@
     }
   }
 
-  // Enter the function and ensure that we have a statement allocated.
-  RETURN_IF_ERROR(state->PushStackEntry());
-
   std::optional<Memoizer::MemoizedArgs> memoized_args =
       Memoizer::AsMemoizedArgs(argc, argv);
 
diff --git a/src/websocket_bridge/websocket_bridge.cc b/src/websocket_bridge/websocket_bridge.cc
index eb26506..875e971 100644
--- a/src/websocket_bridge/websocket_bridge.cc
+++ b/src/websocket_bridge/websocket_bridge.cc
@@ -18,10 +18,12 @@
 
 #include <stdint.h>
 
+#include <cstdlib>
 #include <map>
 #include <memory>
 #include <vector>
 
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/http/http_server.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/unix_task_runner.h"
@@ -71,8 +73,22 @@
 #else
   const auto kTracedFamily = base::SockFamily::kUnix;
 #endif
+  // The ADB_SERVER_SOCKET environment variable is sourced from
+  // the commandline.cpp file in the ADB module of the Android platform.
+  // Examples: tcp:localhost:5037 or tcp:10.52.8.53:5037.
+  std::string adb_socket_endpoint;
+  if (const char* adb_ss = getenv("ADB_SERVER_SOCKET"); adb_ss) {
+    base::StringView adb_ss_sv(adb_ss);
+    adb_socket_endpoint = base::StripPrefix(adb_ss, "tcp:");
+
+    // Ensure that ADB_SERVER_SOCKET actually starts with tcp:
+    PERFETTO_CHECK(adb_socket_endpoint.size() != adb_ss_sv.size());
+  } else {
+    adb_socket_endpoint = "127.0.0.1:5037";
+  }
+  PERFETTO_LOG("[WSBridge] adb server socket is:%s.", adb_socket_endpoint.c_str());
   endpoints_.push_back({"/traced", GetConsumerSocket(), kTracedFamily});
-  endpoints_.push_back({"/adb", "127.0.0.1:5037", base::SockFamily::kInet});
+  endpoints_.push_back({"/adb", adb_socket_endpoint.c_str(), base::SockFamily::kInet});
 
   base::HttpServer srv(&task_runner_, this);
   srv.AddAllowedOrigin("http://localhost:10000");
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index 9ed3863..f99f37b 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-2d29987562fb3b106e0f1794d2cb341c72a4a51fb5d94bf347048bbe198b7302
\ No newline at end of file
+f358a8b059b98a0445674d5e720230047f85829a6366439559ffc5908508ba11
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
index ea7e6de..518b07a 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
@@ -1 +1 @@
-d9f429e9adad0ce321ab12dbdd062a9a8661c6dc3b6357646bcad5910a215407
\ No newline at end of file
+109a9e36d3659acc0f72275f63a3162718d90d62bfd6251f2d3e3fed2cbac8f0
\ No newline at end of file
diff --git a/tools/cpu_profile b/tools/cpu_profile
index 9654a2a..c33235f 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -37,18 +37,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9184800,
+        9348712,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/traceconv',
     'sha256':
-        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
+        '466110b5d92cfc7951ae3223147156dc3ddfad055f8c7a93fea91b2f1844d013',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -58,11 +58,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7761896,
+        7927048,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/traceconv',
     'sha256':
-        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
+        '46663d0eaa88bc821ad71872fd9789c340f9100e22a78494006b94225e2cfe9b',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -72,11 +72,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8928296,
+        9091432,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/traceconv',
     'sha256':
-        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
+        'bb0eabefb6cb22623368d1b51cd95a6d9485781f102a9293443659fdd826fc8f',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -86,11 +86,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6770204,
+        6934532,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/traceconv',
     'sha256':
-        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
+        'ce90e026c71a006b01e4d3c247772cfeeeb7581cd2ce1b003de35ddd87e9d349',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8393944,
+        8558824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/traceconv',
     'sha256':
-        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
+        'f26446b306b0025c54d39dac5b62ae015e0d1771ff5ec461ccbbcc93f1dcb335',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -114,55 +114,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6378744,
+        6542848,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/traceconv',
     'sha256':
-        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
+        '901512a8243b4015aeba59f547d40736fcf032edeedcd1ecb59c259bba0122cf'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7692488,
+        7856856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/traceconv',
     'sha256':
-        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
+        '0eda603b5bc4925b98e0d9656dc18e3e7c4db4685c49f902fd3e1fad1be75efd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8557756,
+        8721860,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/traceconv',
     'sha256':
-        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
+        'd9bc2acf1b280198402a81019d6d5c9f75849d0254fd929b62671af6095d6f57'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8708352,
+        8872720,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/traceconv',
     'sha256':
-        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
+        '3c33c2f79a4fb9760cdf2348a09d6fb6fbb8b5098e014e83cd14d3f3dfb8661d'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8204288,
+        8369664,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/windows-amd64/traceconv.exe',
     'sha256':
-        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
+        '900f931d89af74b84229e1c396898a9c492d49acd15977692040c95a50499936',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 6d00144..c1c1ea1 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -536,6 +536,7 @@
     self.apex_available = set()
     self.min_sdk_version = None
     self.proto = dict()
+    self.output_extension: Optional[str] = None
     # The genrule_XXX below are properties that must to be propagated back
     # on the module(s) that depend on the genrule.
     self.genrule_headers = set()
@@ -587,6 +588,7 @@
     self._output_field(output, 'stubs')
     self._output_field(output, 'proto')
     self._output_field(output, 'main')
+    self._output_field(output, 'output_extension')
 
     target_out = []
     self._output_field(target_out, 'android')
diff --git a/tools/heap_profile b/tools/heap_profile
index 77885fc..e002051 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -34,18 +34,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9184800,
+        9348712,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/traceconv',
     'sha256':
-        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
+        '466110b5d92cfc7951ae3223147156dc3ddfad055f8c7a93fea91b2f1844d013',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -55,11 +55,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7761896,
+        7927048,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/traceconv',
     'sha256':
-        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
+        '46663d0eaa88bc821ad71872fd9789c340f9100e22a78494006b94225e2cfe9b',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -69,11 +69,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8928296,
+        9091432,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/traceconv',
     'sha256':
-        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
+        'bb0eabefb6cb22623368d1b51cd95a6d9485781f102a9293443659fdd826fc8f',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -83,11 +83,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6770204,
+        6934532,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/traceconv',
     'sha256':
-        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
+        'ce90e026c71a006b01e4d3c247772cfeeeb7581cd2ce1b003de35ddd87e9d349',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -97,11 +97,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8393944,
+        8558824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/traceconv',
     'sha256':
-        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
+        'f26446b306b0025c54d39dac5b62ae015e0d1771ff5ec461ccbbcc93f1dcb335',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -111,55 +111,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6378744,
+        6542848,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/traceconv',
     'sha256':
-        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
+        '901512a8243b4015aeba59f547d40736fcf032edeedcd1ecb59c259bba0122cf'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7692488,
+        7856856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/traceconv',
     'sha256':
-        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
+        '0eda603b5bc4925b98e0d9656dc18e3e7c4db4685c49f902fd3e1fad1be75efd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8557756,
+        8721860,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/traceconv',
     'sha256':
-        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
+        'd9bc2acf1b280198402a81019d6d5c9f75849d0254fd929b62671af6095d6f57'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8708352,
+        8872720,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/traceconv',
     'sha256':
-        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
+        '3c33c2f79a4fb9760cdf2348a09d6fb6fbb8b5098e014e83cd14d3f3dfb8661d'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8204288,
+        8369664,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/windows-amd64/traceconv.exe',
     'sha256':
-        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
+        '900f931d89af74b84229e1c396898a9c492d49acd15977692040c95a50499936',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/install-build-deps b/tools/install-build-deps
index c3822f6..fd17b81 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -63,7 +63,7 @@
     'buildtools/test_data',  # Moved to test/data by r.android.com/1539381 .
     'buildtools/d8',  # Removed by r.android.com/1424334 .
 
-    # Build toools moved to third_party/ by r.android.com/2327602 .
+    # Build tools moved to third_party/ by r.android.com/2327602 .
     'buildtools/mac/clang-format',
     'buildtools/mac/gn',
     'buildtools/mac/ninja',
@@ -95,6 +95,11 @@
         'f706aaa0676e3e22f5fc9ca482295d7caee8535d1869f99efa2358177b64f5cd',
         'linux', 'x64'),
     Dependency(
+        'third_party/gn/gn',
+        'https://storage.googleapis.com/perfetto/gn-linux-arm64-1968-0725d782',
+        'c2a372cd4f911028d8bc351fbf24835c9b1194fcc92beadf6c5a2b3addae973c',
+        'linux', 'arm64'),
+    Dependency(
         'third_party/gn/gn.exe',
         'https://storage.googleapis.com/perfetto/gn-win-1968-0725d782',
         '001f777f023c7a6959c778fb3a6b6cfc63f6baef953410ecdeaec350fb12285b',
@@ -150,6 +155,11 @@
         'https://storage.googleapis.com/perfetto/ninja-win-182',
         '09ced0fcd1a4dec7d1b798a2cf9ce5d20e5d2fbc2337343827f192ce47d0f491',
         'windows', 'x64'),
+    Dependency(
+        'third_party/ninja/ninja',
+        'https://storage.googleapis.com/perfetto/ninja-linux-arm64-1111',
+        '05031a734ec4310a51b2cfe9f0096b26fce25ab4ff19e5b51abe6371de066cc5',
+        'linux', 'arm64'),
 
     # Keep the revision in sync with Chrome's PACKAGE_VERSION in
     # tools/clang/scripts/update.py.
@@ -463,6 +473,8 @@
   arch = machine()
   if arch == 'arm64':
     return 'arm64'
+  elif arch == 'aarch64':
+    return 'arm64'
   else:
     # Assume everything else is x64 matching previous behaviour.
     return 'x64'
diff --git a/tools/record_android_trace b/tools/record_android_trace
index d4a740a..59bde1c 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -33,18 +33,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498816,
+        1515200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/tracebox',
     'sha256':
-        '185014447d35357edbd20e7ce9924842a0d5c6576bd2257abae2ed48b65fd3b8',
+        'b451e873b1f6c8bd2fc3f1e12adc381c77a5b6dce9ec28ad8788e6f7f5efd348',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -56,9 +56,9 @@
     'file_size':
         1392776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/tracebox',
     'sha256':
-        '082bb50e64df5e232673eebb1cd8b0dd752a394105f600cb0262730833f6b7f3',
+        '8247045bd78e467aa010674277048658cdf04a95bd15b25dabc973fce011d6db',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -68,11 +68,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2229096,
+        2236584,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/tracebox',
     'sha256':
-        'c99120caedb845e1c3fad4428263a683b44c357c76d65848dd8e437250066e38',
+        'adf6f9ebb5686a7d2e056c8f059adf4d95e867ffa39d27b041bef16d3cb7e1c7',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -82,11 +82,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1339796,
+        1344188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/tracebox',
     'sha256':
-        '6732165916b74f0b820991d1aaed2086a6b56e91f6c604291efe6636f0bdda71',
+        'e2feeeaf38c3cd9efbf992dbe09f40fca81651d9fd9159e0f9a95868e2c4e07e',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -96,11 +96,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2157312,
+        2164560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/tracebox',
     'sha256':
-        '7d09865a6d7118e67d2acd0c56b2a94ce8bd5f614869d29a72fe633515ab1fbd',
+        '5d89a6c16819a74be44bf731f2d07bfb83924a2e560554a373feeb2bb9940ef1',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -112,31 +112,31 @@
     'file_size':
         1247188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/tracebox',
     'sha256':
-        '4ecc192172ac2bca49557cbdbb1f7d660718d4fb4a7314fd19b2b2e52be8bc0c'
+        '5837e88a92b8bc00d5575f9dc02dd314f16b2e0a1bb174efb6c092a8f639e7c4'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1854120,
+        1870504,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/tracebox',
     'sha256':
-        '1ca89113279d5c6a9ae273bde03b4d84373efe6923dc637cb840908f13b9639e'
+        '75431d11aec11f59b87a76fa31cf92a1f8e534ad4118357b3654a458cf547081'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1853356,
+        1869740,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/tracebox',
     'sha256':
-        'cf689a191c1252734ebbfda3106600da324610f761515cfbffbeac2ebdfee715'
+        '388da3a4248f105bc56685db4835ce487633035334efa543b7190ac4a9e26bde'
 }, {
     'arch':
         'android-x64',
@@ -145,9 +145,9 @@
     'file_size':
         2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/tracebox',
     'sha256':
-        '99e9ebdb5b5308d95551a4ad060d615d7defb6877c4061d21c783c45a71d372f'
+        '1c3d50a4066f9b3478ad65431532c3503a2ad73ee89d346e6ee12f7fb0c93aaa'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/trace_processor b/tools/trace_processor
index 60de377..4a839a3 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/trace_processor_shell.py
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9978200,
+        10142120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/trace_processor_shell',
     'sha256':
-        'f3e21eb29fb51cb2ea9b81b69132c5ae93ce3276c57ccd27fcf7c675306b4e41',
+        '44585789d420d0bc38edc3dd6fbade4c4a718dc535fb68ac7a2449bf1a251f30',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8493976,
+        8659128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/trace_processor_shell',
     'sha256':
-        '84f35765141374b8d883813ac533e0c004cf72d1c6f05aef0c973364ff541eb9',
+        'bccca60b99fb2c587503a6430e0b15204ebeddb97607006d54f203e64ac923af',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9830856,
+        9987880,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/trace_processor_shell',
     'sha256':
-        'b3dc0a9c641b84a57fa5d59637921ae2237e4f05b1778341a691df220faf0cd7',
+        '3510cfc89e627a95ede2d06e77d1ddd70d31ef99d0d213d1fbaba4f438030e4c',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7231096,
+        7389416,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/trace_processor_shell',
     'sha256':
-        'a21252830fb1bbb7b3fd9665ce6e70920cffa6b1e72c16589c90896c002c3348',
+        '2a87d3587e9a756ea486746ba08e7535dacf8872b1d2081e9381a612668d154d',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9238056,
+        9396856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/trace_processor_shell',
     'sha256':
-        'f77519ec19743ec2c22ed78fe3a20106a482a28d77c4154378af108c5f7bdd4a',
+        '42738a5aa187fe7644d87361fcf9854459f3d46a301026c9ac76ac4bf0ec9d3e',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6870968,
+        7018688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/trace_processor_shell',
     'sha256':
-        '2c7055fb44085ec60ad8bb970d495c9c88070fce08902f11fcd44e0ae3369876'
+        '9d8247c09b82835dc5019fed2f41e8844162a8cbf520e4243111852d6398911b'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8414568,
+        8578936,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/trace_processor_shell',
     'sha256':
-        'd8ca0dc2bab7ea604a6721f0ac0e2b433b43261f247c6c98c510dc17aafe5a72'
+        '91405004e1a47b1170eb32315f3d0a5b287bc71cb856e7403fad0f02e8b4dfd1'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9328508,
+        9492612,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/trace_processor_shell',
     'sha256':
-        'de6a6ea45769888e59a1678d37b6e355b27b834d34a0b9e4980a942d333b88cc'
+        '01364fc6fb485b20d838326462239d14c8f2daf1e7dde524b2cba4fd5acb8a73'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9577896,
+        9742264,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/trace_processor_shell',
     'sha256':
-        'cd4b16c5f78a060934204737ba8b312e824ff7cc28f3732daf7d64e733a727f9'
+        '43b456dcd0238f52f5730c6f5b9f8249f096df718d3fefa443c3246d66df4bd6'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        9248256,
+        9408000,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '26584b4bbab40f8b0ad991a869e7483f92d7223e1473b879a6ceafa49b76390a',
+        'b4dc6a7968673373265344e8e5249ccc31f7d12bb9df527370065dff71b1e74a',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/tracebox b/tools/tracebox
index a4c278b..402a52d 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498816,
+        1515200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/tracebox',
     'sha256':
-        '185014447d35357edbd20e7ce9924842a0d5c6576bd2257abae2ed48b65fd3b8',
+        'b451e873b1f6c8bd2fc3f1e12adc381c77a5b6dce9ec28ad8788e6f7f5efd348',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -53,9 +53,9 @@
     'file_size':
         1392776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/tracebox',
     'sha256':
-        '082bb50e64df5e232673eebb1cd8b0dd752a394105f600cb0262730833f6b7f3',
+        '8247045bd78e467aa010674277048658cdf04a95bd15b25dabc973fce011d6db',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2229096,
+        2236584,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/tracebox',
     'sha256':
-        'c99120caedb845e1c3fad4428263a683b44c357c76d65848dd8e437250066e38',
+        'adf6f9ebb5686a7d2e056c8f059adf4d95e867ffa39d27b041bef16d3cb7e1c7',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1339796,
+        1344188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/tracebox',
     'sha256':
-        '6732165916b74f0b820991d1aaed2086a6b56e91f6c604291efe6636f0bdda71',
+        'e2feeeaf38c3cd9efbf992dbe09f40fca81651d9fd9159e0f9a95868e2c4e07e',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2157312,
+        2164560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/tracebox',
     'sha256':
-        '7d09865a6d7118e67d2acd0c56b2a94ce8bd5f614869d29a72fe633515ab1fbd',
+        '5d89a6c16819a74be44bf731f2d07bfb83924a2e560554a373feeb2bb9940ef1',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -109,31 +109,31 @@
     'file_size':
         1247188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/tracebox',
     'sha256':
-        '4ecc192172ac2bca49557cbdbb1f7d660718d4fb4a7314fd19b2b2e52be8bc0c'
+        '5837e88a92b8bc00d5575f9dc02dd314f16b2e0a1bb174efb6c092a8f639e7c4'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1854120,
+        1870504,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/tracebox',
     'sha256':
-        '1ca89113279d5c6a9ae273bde03b4d84373efe6923dc637cb840908f13b9639e'
+        '75431d11aec11f59b87a76fa31cf92a1f8e534ad4118357b3654a458cf547081'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1853356,
+        1869740,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/tracebox',
     'sha256':
-        'cf689a191c1252734ebbfda3106600da324610f761515cfbffbeac2ebdfee715'
+        '388da3a4248f105bc56685db4835ce487633035334efa543b7190ac4a9e26bde'
 }, {
     'arch':
         'android-x64',
@@ -142,9 +142,9 @@
     'file_size':
         2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/tracebox',
     'sha256':
-        '99e9ebdb5b5308d95551a4ad060d615d7defb6877c4061d21c783c45a71d372f'
+        '1c3d50a4066f9b3478ad65431532c3503a2ad73ee89d346e6ee12f7fb0c93aaa'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/traceconv b/tools/traceconv
index 0136e37..63997fb 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v40.0
+# This file has been generated by: tools/roll-prebuilts 3e53e144bee271ec558363df2e561a77d7e0b789
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9184800,
+        9348712,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-amd64/traceconv',
     'sha256':
-        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
+        '466110b5d92cfc7951ae3223147156dc3ddfad055f8c7a93fea91b2f1844d013',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7761896,
+        7927048,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/mac-arm64/traceconv',
     'sha256':
-        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
+        '46663d0eaa88bc821ad71872fd9789c340f9100e22a78494006b94225e2cfe9b',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8928296,
+        9091432,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-amd64/traceconv',
     'sha256':
-        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
+        'bb0eabefb6cb22623368d1b51cd95a6d9485781f102a9293443659fdd826fc8f',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6770204,
+        6934532,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/traceconv',
     'sha256':
-        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
+        'ce90e026c71a006b01e4d3c247772cfeeeb7581cd2ce1b003de35ddd87e9d349',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8393944,
+        8558824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/traceconv',
     'sha256':
-        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
+        'f26446b306b0025c54d39dac5b62ae015e0d1771ff5ec461ccbbcc93f1dcb335',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6378744,
+        6542848,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm/traceconv',
     'sha256':
-        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
+        '901512a8243b4015aeba59f547d40736fcf032edeedcd1ecb59c259bba0122cf'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7692488,
+        7856856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-arm64/traceconv',
     'sha256':
-        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
+        '0eda603b5bc4925b98e0d9656dc18e3e7c4db4685c49f902fd3e1fad1be75efd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8557756,
+        8721860,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x86/traceconv',
     'sha256':
-        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
+        'd9bc2acf1b280198402a81019d6d5c9f75849d0254fd929b62671af6095d6f57'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8708352,
+        8872720,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/android-x64/traceconv',
     'sha256':
-        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
+        '3c33c2f79a4fb9760cdf2348a09d6fb6fbb8b5098e014e83cd14d3f3dfb8661d'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8204288,
+        8369664,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/windows-amd64/traceconv.exe',
     'sha256':
-        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
+        '900f931d89af74b84229e1c396898a9c492d49acd15977692040c95a50499936',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 9ade987..cce73f1 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
     },
     {
       "name": "canary",
-      "rev": "9ca89e30931314dec4af1131d516e07e39d8657d"
+      "rev": "46dae5655847f65acd49cad6a24ef37c6a85383d"
     },
     {
       "name": "autopush",
diff --git a/ui/src/base/static_initializers.ts b/ui/src/base/static_initializers.ts
index 5abfcd9..f927471 100644
--- a/ui/src/base/static_initializers.ts
+++ b/ui/src/base/static_initializers.ts
@@ -38,7 +38,7 @@
   // from the global state (which is frozen) and later try to update the copies.
   // By doing so, we  accidentally the local copy of global state, which is
   // supposed to be immutable.
-  setAutoFreeze(false);
+  setAutoFreeze(true);
 }
 
 function initializeProtobuf() {
diff --git a/ui/src/common/colorizer.ts b/ui/src/common/colorizer.ts
index 584deb5..14f2140 100644
--- a/ui/src/common/colorizer.ts
+++ b/ui/src/common/colorizer.ts
@@ -19,10 +19,10 @@
 
 import {Color, HSLColor, HSLuvColor} from './color';
 
-// 128 would provide equal weighting between dark and light text, but we want to
-// slightly prefer light text for stylistic reasons.
-// 140 means we must be brighter on average before switching to dark text.
-const PERCEIVED_BRIGHTNESS_LIMIT = 140;
+// 128 would provide equal weighting between dark and light text.
+// However, we want to prefer light text for stylistic reasons.
+// A higher value means color must be brighter before switching to dark text.
+const PERCEIVED_BRIGHTNESS_LIMIT = 180;
 
 // This file defines some opinionated colors and provides functions to access
 // random but predictable colors based on a seed, as well as standardized ways
diff --git a/ui/src/common/track_adapter.ts b/ui/src/common/track_adapter.ts
index acc142d..35fe5db 100644
--- a/ui/src/common/track_adapter.ts
+++ b/ui/src/common/track_adapter.ts
@@ -16,8 +16,8 @@
 import {v4 as uuidv4} from 'uuid';
 
 import {assertExists} from '../base/logging';
-import {duration, Span, time} from '../base/time';
-import {PxSpan, TimeScale} from '../frontend/time_scale';
+import {duration, time} from '../base/time';
+import {PanelSize} from '../frontend/panel';
 import {NewTrackArgs} from '../frontend/track';
 import {SliceRect} from '../public';
 import {EngineProxy} from '../trace_processor/engine';
@@ -64,12 +64,8 @@
     super.onDestroy();
   }
 
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, bigint>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined {
-    return this.track.getSliceRect(
-        visibleTimeScale, visibleWindow, windowSpan, tStart, tEnd, depth);
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined {
+    return this.track.getSliceRect(tStart, tEnd, depth);
   }
 
   getHeight(): number {
@@ -105,8 +101,8 @@
     return await this.controller.onBoundsChange(start, end, resolution);
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
-    this.track.renderCanvas(ctx);
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
+    this.track.renderCanvas(ctx, size);
   }
 }
 
@@ -138,12 +134,10 @@
     this.trackKey = args.trackKey;
   }
 
-  abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
+  abstract renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void;
 
-  getSliceRect(
-      _visibleTimeScale: TimeScale, _visibleWindow: Span<time, bigint>,
-      _windowSpan: PxSpan, _tStart: time, _tEnd: time,
-      _depth: number): SliceRect|undefined {
+  getSliceRect(_tStart: time, _tEnd: time, _depth: number): SliceRect
+      |undefined {
     return undefined;
   }
 
diff --git a/ui/src/common/track_helper.ts b/ui/src/common/track_helper.ts
index 114e2c9..8990a44 100644
--- a/ui/src/common/track_helper.ts
+++ b/ui/src/common/track_helper.ts
@@ -14,10 +14,10 @@
 
 import m from 'mithril';
 
-import {duration, Span, Time, time} from '../base/time';
+import {duration, Time, time} from '../base/time';
 import {raf} from '../core/raf_scheduler';
 import {globals} from '../frontend/globals';
-import {PxSpan, TimeScale} from '../frontend/time_scale';
+import {PanelSize} from '../frontend/panel';
 import {SliceRect, Track, TrackContext} from '../public';
 
 import {TrackData} from './track_data';
@@ -66,10 +66,8 @@
   // only for track types that support slices e.g. chrome_slice, async_slices
   // tStart - slice start time in seconds, tEnd - slice end time in seconds,
   // depth - slice depth
-  getSliceRect(
-      _visibleTimeScale: TimeScale, _visibleWindow: Span<time, duration>,
-      _windowSpan: PxSpan, _tStart: time, _tEnd: time,
-      _depth: number): SliceRect|undefined {
+  getSliceRect(_tStart: time, _tEnd: time, _depth: number): SliceRect
+      |undefined {
     return undefined;
   }
 
@@ -92,14 +90,14 @@
   abstract onBoundsChange(start: time, end: time, resolution: duration):
       Promise<Data>;
 
-  abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
+  abstract renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void;
 
-  render(ctx: CanvasRenderingContext2D): void {
+  render(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     if (this.shouldLoadNewData()) {
       this.loadData();
     }
 
-    this.renderCanvas(ctx);
+    this.renderCanvas(ctx, size);
   }
 
   private loadData(): void {
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index ae7145d..5081cb7 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -29,6 +29,7 @@
 
 import {checkerboardExcept} from './checkerboard';
 import {globals} from './globals';
+import {PanelSize} from './panel';
 import {constraintsToQuerySuffix} from './sql_utils';
 import {NewTrackArgs, TrackBase} from './track';
 import {CacheKey, TrackCache} from './track_cache';
@@ -175,11 +176,10 @@
     ];
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D) {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
     const {
       visibleTimeScale: timeScale,
       visibleWindowTime: vizTime,
-      windowSpan,
     } = globals.frontendLocalState;
 
     {
@@ -237,7 +237,7 @@
     }
 
     const effectiveHeight = this.getHeight() - MARGIN_TOP;
-    const endPx = windowSpan.end;
+    const endPx = size.width;
     const zeroY = MARGIN_TOP + effectiveHeight / (minimumValue < 0 ? 2 : 1);
 
     // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
@@ -373,8 +373,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        windowSpan.start,
-        windowSpan.end,
+        0,
+        size.width,
         timeScale.timeToPx(this.countersKey.start),
         timeScale.timeToPx(this.countersKey.end));
   }
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 2dbe0f2..95855cf 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -17,7 +17,6 @@
 import {clamp, floatEqual} from '../base/math_utils';
 import {
   duration,
-  Span,
   Time,
   time,
 } from '../base/time';
@@ -30,18 +29,18 @@
 import {colorCompare} from '../common/color';
 import {UNEXPECTED_PINK} from '../common/colorizer';
 import {Selection, SelectionKind} from '../common/state';
+import {featureFlags} from '../core/feature_flags';
 import {raf} from '../core/raf_scheduler';
 import {Slice, SliceRect} from '../public';
 import {LONG, NUM} from '../trace_processor/query_result';
 
 import {checkerboardExcept} from './checkerboard';
 import {globals} from './globals';
+import {PanelSize} from './panel';
 import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout';
 import {constraintsToQuerySuffix} from './sql_utils';
-import {PxSpan, TimeScale} from './time_scale';
 import {NewTrackArgs, TrackBase} from './track';
 import {BUCKETS_PER_PIXEL, CacheKey, TrackCache} from './track_cache';
-import {featureFlags} from '../core/feature_flags';
 
 // The common class that underpins all tracks drawing slices.
 
@@ -327,7 +326,7 @@
     return `${size}px Roboto Condensed`;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     // TODO(hjd): fonts and colors should come from the CSS and not hardcoded
     // here.
     const {
@@ -547,8 +546,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.hpTimeToPx(vizTime.start),
-        timeScale.hpTimeToPx(vizTime.end),
+        0,
+        size.width,
         timeScale.timeToPx(this.slicesKey.start),
         timeScale.timeToPx(this.slicesKey.end));
 
@@ -967,17 +966,20 @@
     return this.computedTrackHeight;
   }
 
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined {
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined {
     this.updateSliceAndTrackHeight();
 
+    const {
+      windowSpan,
+      visibleTimeScale,
+      visibleTimeSpan,
+    } = globals.frontendLocalState;
+
     const pxEnd = windowSpan.end;
     const left = Math.max(visibleTimeScale.timeToPx(tStart), 0);
     const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd);
 
-    const visible = visibleWindow.intersects(tStart, tEnd);
+    const visible = visibleTimeSpan.intersects(tStart, tEnd);
 
     const totalSliceHeight = this.computedRowSpacing + this.computedSliceHeight;
 
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index fd73ca4..12ce93c 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -140,19 +140,12 @@
 
   private getSliceRect(args: FlowEventsRendererArgs, point: FlowPoint):
       SliceRect|undefined {
-    const {visibleTimeScale, visibleTimeSpan, windowSpan} =
-        globals.frontendLocalState;
     const trackPanel = args.trackIdToTrackPanel.get(point.trackId) ?.panel;
     if (!trackPanel) {
       return undefined;
     }
     return trackPanel.getSliceRect(
-        visibleTimeScale,
-        visibleTimeSpan,
-        windowSpan,
-        point.sliceStartTs,
-        point.sliceEndTs,
-        point.depth);
+        point.sliceStartTs, point.sliceEndTs, point.depth);
   }
 
   render(ctx: CanvasRenderingContext2D, args: FlowEventsRendererArgs) {
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index 1904f32..1d7589c 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -215,7 +215,7 @@
     ctx.strokeStyle = color;
     const topOffset = 10;
     // Don't draw in the track shell section.
-    if (x >= globals.frontendLocalState.windowSpan.start + TRACK_SHELL_WIDTH) {
+    if (x >= TRACK_SHELL_WIDTH) {
       // Draw left triangle.
       ctx.beginPath();
       ctx.moveTo(x, topOffset);
@@ -235,8 +235,7 @@
     ctx.stroke();
 
     // Start line after track shell section, join triangles.
-    const startDraw = Math.max(
-        x, globals.frontendLocalState.windowSpan.start + TRACK_SHELL_WIDTH);
+    const startDraw = Math.max(x, TRACK_SHELL_WIDTH);
     ctx.beginPath();
     ctx.moveTo(startDraw, topOffset);
     ctx.lineTo(xEnd, topOffset);
diff --git a/ui/src/frontend/slice_track.ts b/ui/src/frontend/slice_track.ts
index dd8756d..685ff8c 100644
--- a/ui/src/frontend/slice_track.ts
+++ b/ui/src/frontend/slice_track.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {duration, Span, Time, time} from '../base/time';
+import {Time, time} from '../base/time';
 import {Actions} from '../common/actions';
 import {cropText, drawIncompleteSlice} from '../common/canvas_utils';
 import {getColorForSlice} from '../common/colorizer';
@@ -24,7 +24,7 @@
 import {CROP_INCOMPLETE_SLICE_FLAG} from './base_slice_track';
 import {checkerboardExcept} from './checkerboard';
 import {globals} from './globals';
-import {PxSpan, TimeScale} from './time_scale';
+import {PanelSize} from './panel';
 
 export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
 const SLICE_HEIGHT = 18;
@@ -77,21 +77,20 @@
     return '12px Roboto Condensed';
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
     const data = this.data;
     if (data === undefined) return;  // Can't possibly draw anything.
 
-    const {visibleTimeSpan, visibleWindowTime, visibleTimeScale, windowSpan} =
-        globals.frontendLocalState;
+    const {visibleTimeSpan, visibleTimeScale} = globals.frontendLocalState;
 
     // If the cached trace slices don't fully cover the visible time range,
     // show a gray rectangle with a "Loading..." label.
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        visibleTimeScale.hpTimeToPx(visibleWindowTime.start),
-        visibleTimeScale.hpTimeToPx(visibleWindowTime.end),
+        0,
+        size.width,
         visibleTimeScale.timeToPx(data.start),
         visibleTimeScale.timeToPx(data.end),
     );
@@ -126,11 +125,16 @@
         continue;
       }
 
-      const rect = this.getSliceRect(
-          visibleTimeScale, visibleTimeSpan, windowSpan, tStart, tEnd, depth);
-      if (!rect || !rect.visible) {
-        continue;
-      }
+      const pxEnd = size.width;
+      const left = Math.max(visibleTimeScale.timeToPx(tStart), 0);
+      const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd);
+
+      const rect = {
+        left,
+        width: Math.max(right - left, 1),
+        top: TRACK_PADDING + depth * SLICE_HEIGHT,
+        height: SLICE_HEIGHT,
+      };
 
       const currentSelection = globals.state.currentSelection;
       const isSelected = currentSelection &&
@@ -340,15 +344,18 @@
     return SLICE_HEIGHT * (this.maxDepth + 1) + 2 * TRACK_PADDING;
   }
 
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined {
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined {
+    const {
+      windowSpan,
+      visibleTimeScale,
+      visibleTimeSpan,
+    } = globals.frontendLocalState;
+
     const pxEnd = windowSpan.end;
     const left = Math.max(visibleTimeScale.timeToPx(tStart), 0);
     const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd);
 
-    const visible = visibleWindow.intersects(tStart, tEnd);
+    const visible = visibleTimeSpan.intersects(tStart, tEnd);
 
     return {
       left,
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 88565f2..3cc79f1 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -14,12 +14,11 @@
 
 import m from 'mithril';
 
-import {duration, Span, time} from '../base/time';
+import {time} from '../base/time';
 import {SliceRect, Track, TrackContext} from '../public';
 import {EngineProxy} from '../trace_processor/engine';
 
-import {PxSpan, TimeScale} from './time_scale';
-
+import {PanelSize} from './panel';
 // Args passed to the track constructors when creating a new track.
 export interface NewTrackArgs {
   trackKey: string;
@@ -42,7 +41,8 @@
   // this object is removed.
   onDestroy() {}
 
-  protected abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
+  protected abstract renderCanvas(
+      ctx: CanvasRenderingContext2D, size: PanelSize): void;
 
   getHeight(): number {
     return 40;
@@ -64,18 +64,16 @@
 
   onFullRedraw(): void {}
 
-  render(ctx: CanvasRenderingContext2D) {
-    this.renderCanvas(ctx);
+  render(ctx: CanvasRenderingContext2D, size: PanelSize) {
+    this.renderCanvas(ctx, size);
   }
 
   // Returns a place where a given slice should be drawn. Should be implemented
   // only for track types that support slices e.g. chrome_slice, async_slices
   // tStart - slice start time in seconds, tEnd - slice end time in seconds,
   // depth - slice depth
-  getSliceRect(
-      _visibleTimeScale: TimeScale, _visibleWindow: Span<time, duration>,
-      _windowSpan: PxSpan, _tStart: time, _tEnd: time,
-      _depth: number): SliceRect|undefined {
+  getSliceRect(_tStart: time, _tEnd: time, _depth: number): SliceRect
+      |undefined {
     return undefined;
   }
 }
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index a524dfb..6b69607 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -24,7 +24,7 @@
   TrackGroupState,
   TrackState,
 } from '../common/state';
-import {Migrate, Track, TrackContext} from '../public';
+import {Migrate, Track, TrackContext, TrackTags} from '../public';
 
 import {globals} from './globals';
 import {drawGridLines} from './gridline_helper';
@@ -44,6 +44,7 @@
   private shellWidth = 0;
   private backgroundColor = '#ffffff';  // Updated from CSS later.
   private summaryTrack?: Track;
+  private summaryTrackTags?: TrackTags;
 
   constructor({attrs}: m.CVnode<Attrs>) {
     super();
@@ -70,6 +71,7 @@
     };
 
     this.summaryTrack = pluginManager.createTrack(uri, ctx);
+    this.summaryTrackTags = pluginManager.resolveTrackInfo(uri)?.tags;
   }
 
   get trackGroupState(): TrackGroupState {
@@ -148,7 +150,7 @@
                 'h1.track-title',
                 {title: name},
                 name,
-                renderChips(this.summaryTrackState),
+                renderChips(this.summaryTrackTags),
                 ),
             (this.trackGroupState.collapsed && child !== null) ?
                 m('h2.track-subtitle', child) :
@@ -235,7 +237,8 @@
     ctx.save();
     ctx.translate(this.shellWidth, 0);
     if (this.summaryTrack) {
-      this.summaryTrack.render(ctx);
+      const trackSize = {...size, width: size.width - this.shellWidth};
+      this.summaryTrack.render(ctx, trackSize);
     }
     ctx.restore();
 
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 56318d1..afd58c6 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -17,19 +17,19 @@
 
 import {currentTargetOffset} from '../base/dom_utils';
 import {Icons} from '../base/semantic_icons';
-import {duration, Span, time} from '../base/time';
+import {time} from '../base/time';
 import {Actions} from '../common/actions';
 import {pluginManager} from '../common/plugins';
 import {TrackState} from '../common/state';
 import {raf} from '../core/raf_scheduler';
-import {Migrate, SliceRect, Track, TrackContext} from '../public';
+import {Migrate, SliceRect, Track, TrackContext, TrackTags} from '../public';
 
+import {checkerboard} from './checkerboard';
 import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
 import {globals} from './globals';
 import {drawGridLines} from './gridline_helper';
 import {Panel, PanelSize} from './panel';
 import {verticalScrollToTrack} from './scroll_helper';
-import {PxSpan, TimeScale} from './time_scale';
 import {
   drawVerticalLineAtTime,
 } from './vertical_line_helper';
@@ -75,29 +75,24 @@
   }
 }
 
-export function renderChips({uri}: TrackState) {
-  const tagElements: m.Children = [];
-  const trackInfo = pluginManager.resolveTrackInfo(uri);
-  const tags = trackInfo?.tags;
-  tags?.metric && tagElements.push(m(TrackChip, {text: 'metric'}));
-  tags?.debuggable && tagElements.push(m(TrackChip, {text: 'debuggable'}));
-  return tagElements;
+export function renderChips(tags?: TrackTags) {
+  return [
+    tags?.metric && m(TrackChip, {text: 'metric'}),
+    tags?.debuggable && m(TrackChip, {text: 'debuggable'}),
+  ];
 }
 
 interface TrackShellAttrs {
-  track: Track;
-  trackState: TrackState;
+  trackKey: string;
+  title: string;
+  buttons: m.Children;
+  tags?: TrackTags;
 }
 
 class TrackShell implements m.ClassComponent<TrackShellAttrs> {
   // Set to true when we click down and drag the
   private dragging = false;
   private dropping: 'before'|'after'|undefined = undefined;
-  private attrs?: TrackShellAttrs;
-
-  oninit(vnode: m.Vnode<TrackShellAttrs>) {
-    this.attrs = vnode.attrs;
-  }
 
   view({attrs}: m.CVnode<TrackShellAttrs>) {
     // The shell should be highlighted if the current search result is inside
@@ -106,7 +101,7 @@
     const searchIndex = globals.state.searchIndex;
     if (searchIndex !== -1) {
       const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
-      if (trackKey === attrs.trackState.key) {
+      if (trackKey === attrs.trackKey) {
         highlightClass = 'flash';
       }
     }
@@ -117,34 +112,34 @@
         `.track-shell[draggable=true]`,
         {
           class: `${highlightClass} ${dragClass} ${dropClass}`,
-          ondragstart: this.ondragstart.bind(this),
+          ondragstart: (e: DragEvent) => this.ondragstart(e, attrs.trackKey),
           ondragend: this.ondragend.bind(this),
           ondragover: this.ondragover.bind(this),
           ondragleave: this.ondragleave.bind(this),
-          ondrop: this.ondrop.bind(this),
+          ondrop: (e: DragEvent) => this.ondrop(e, attrs.trackKey),
         },
         m(
             'h1',
             {
-              title: attrs.trackState.name,
+              title: attrs.title,
               style: {
-                'font-size': getTitleSize(attrs.trackState.name),
+                'font-size': getTitleSize(attrs.title),
               },
             },
-            attrs.trackState.name,
-            renderChips(attrs.trackState),
+            attrs.title,
+            renderChips(attrs.tags),
             ),
         m('.track-buttons',
-          attrs.track.getTrackShellButtons(),
+          attrs.buttons,
           m(TrackButton, {
             action: () => {
               globals.dispatch(
-                  Actions.toggleTrackPinned({trackKey: attrs.trackState.key}));
+                  Actions.toggleTrackPinned({trackKey: attrs.trackKey}));
             },
             i: Icons.Pin,
-            filledIcon: isPinned(attrs.trackState.key),
-            tooltip: isPinned(attrs.trackState.key) ? 'Unpin' : 'Pin to top',
-            showButton: isPinned(attrs.trackState.key),
+            filledIcon: isPinned(attrs.trackKey),
+            tooltip: isPinned(attrs.trackKey) ? 'Unpin' : 'Pin to top',
+            showButton: isPinned(attrs.trackKey),
             fullHeight: true,
           }),
           globals.state.currentSelection !== null &&
@@ -152,25 +147,24 @@
               m(TrackButton, {
                 action: (e: MouseEvent) => {
                   globals.dispatch(Actions.toggleTrackSelection(
-                      {id: attrs.trackState.key, isTrackGroup: false}));
+                      {id: attrs.trackKey, isTrackGroup: false}));
                   e.stopPropagation();
                 },
-                i: isSelected(attrs.trackState.key) ? Icons.Checkbox :
-                                                      Icons.BlankCheckbox,
-                tooltip: isSelected(attrs.trackState.key) ?
-                    'Remove track' :
-                    'Add track to selection',
+                i: isSelected(attrs.trackKey) ? Icons.Checkbox :
+                                                Icons.BlankCheckbox,
+                tooltip: isSelected(attrs.trackKey) ? 'Remove track' :
+                                                      'Add track to selection',
                 showButton: true,
               }) :
               ''));
   }
 
-  ondragstart(e: DragEvent) {
+  ondragstart(e: DragEvent, trackKey: string) {
     const dataTransfer = e.dataTransfer;
     if (dataTransfer === null) return;
     this.dragging = true;
     raf.scheduleFullRedraw();
-    dataTransfer.setData('perfetto/track', `${this.attrs!.trackState.key}`);
+    dataTransfer.setData('perfetto/track', `${trackKey}`);
     dataTransfer.setDragImage(new Image(), 0, 0);
   }
 
@@ -203,13 +197,13 @@
     raf.scheduleFullRedraw();
   }
 
-  ondrop(e: DragEvent) {
+  ondrop(e: DragEvent, trackKey: string) {
     if (this.dropping === undefined) return;
     const dataTransfer = e.dataTransfer;
     if (dataTransfer === null) return;
     raf.scheduleFullRedraw();
     const srcId = dataTransfer.getData('perfetto/track');
-    const dstId = this.attrs!.trackState.key;
+    const dstId = trackKey;
     globals.dispatch(Actions.moveTrack({srcId, op: this.dropping, dstId}));
     this.dropping = undefined;
   }
@@ -274,9 +268,14 @@
 }
 
 interface TrackComponentAttrs {
-  trackState: TrackState;
-  track: Track;
+  trackKey: string;
+  heightPx?: number;
+  title: string;
+  buttons?: m.Children;
+  tags?: TrackTags;
+  track?: Track;
 }
+
 class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
   view({attrs}: m.CVnode<TrackComponentAttrs>) {
     // TODO(hjd): The min height below must match the track_shell_title
@@ -286,19 +285,24 @@
         '.track',
         {
           style: {
-            height: `${Math.max(18, attrs.track.getHeight())}px`,
+            height: `${Math.max(18, attrs.heightPx ?? 0)}px`,
           },
-          id: 'track_' + attrs.trackState.key,
+          id: 'track_' + attrs.trackKey,
         },
         [
-          m(TrackShell, {track: attrs.track, trackState: attrs.trackState}),
-          m(TrackContent, {track: attrs.track}),
+          m(TrackShell, {
+            buttons: attrs.buttons,
+            title: attrs.title,
+            trackKey: attrs.trackKey,
+            tags: attrs.tags,
+          }),
+          attrs.track && m(TrackContent, {track: attrs.track}),
         ]);
   }
 
   oncreate({attrs}: m.CVnode<TrackComponentAttrs>) {
-    if (globals.scrollToTrackKey === attrs.trackState.key) {
-      verticalScrollToTrack(attrs.trackState.key);
+    if (globals.scrollToTrackKey === attrs.trackKey) {
+      verticalScrollToTrack(attrs.trackKey);
       globals.scrollToTrackKey = undefined;
     }
   }
@@ -341,6 +345,7 @@
   // has disappeared.
   private track: Track|undefined;
   private trackState: TrackState|undefined;
+  private tags: TrackTags|undefined;
 
   private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
     const trackKey = vnode.attrs.trackKey;
@@ -364,6 +369,7 @@
     };
 
     this.track = pluginManager.createTrack(uri, trackCtx);
+    this.tags = pluginManager.resolveTrackInfo(uri)?.tags;
 
     this.track?.onCreate(trackCtx);
     this.trackState = trackState;
@@ -375,9 +381,19 @@
     }
 
     if (this.track === undefined || this.trackState === undefined) {
-      return m('div', 'No such track');
+      return m(TrackComponent, {
+        trackKey: vnode.attrs.trackKey,
+        title: this.trackState?.name ?? 'Loading...',
+      });
     }
-    return m(TrackComponent, {trackState: this.trackState, track: this.track});
+    return m(TrackComponent, {
+      tags: this.tags,
+      heightPx: this.track.getHeight(),
+      title: this.trackState.name,
+      trackKey: this.trackState.key,
+      buttons: this.track.getTrackShellButtons(),
+      track: this.track,
+    });
   }
 
   oncreate() {
@@ -428,7 +444,10 @@
 
     ctx.translate(TRACK_SHELL_WIDTH, 0);
     if (this.track !== undefined) {
-      this.track.render(ctx);
+      const trackSize = {...size, width: size.width - TRACK_SHELL_WIDTH};
+      this.track.render(ctx, trackSize);
+    } else {
+      checkerboard(ctx, size.height, 0, size.width - TRACK_SHELL_WIDTH);
     }
     ctx.restore();
 
@@ -491,14 +510,10 @@
     }
   }
 
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tDur: time, depth: number): SliceRect
-      |undefined {
+  getSliceRect(tStart: time, tDur: time, depth: number): SliceRect|undefined {
     if (this.track === undefined) {
       return undefined;
     }
-    return this.track.getSliceRect(
-        visibleTimeScale, visibleWindow, windowSpan, tStart, tDur, depth);
+    return this.track.getSliceRect(tStart, tDur, depth);
   }
 }
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
index 7658fb6..a521724 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
@@ -58,6 +58,59 @@
            SELECT * FROM android_binder_graph(-1000, 1000, -1000, 1000)`,
           'all process binder graph'),
     });
+
+    ctx.registerCommand({
+      id: 'dev.perfetto.AndroidPerf#ThreadClusterDistribution',
+      name: 'Run query: runtime cluster distribution for a thread',
+      callback: async (tid) => {
+        if (tid === undefined) {
+          tid = prompt('Enter a thread tid', '');
+          if (tid === null) return;
+        }
+        ctx.tabs.openQuery(`
+          INCLUDE PERFETTO MODULE common.cpus;
+          WITH
+            total_runtime AS (
+              SELECT sum(dur) AS total_runtime
+              FROM sched s
+              LEFT JOIN thread t
+                USING (utid)
+              WHERE t.tid = ${tid}
+            )
+            SELECT
+              c.size AS cluster,
+              sum(dur)/1e6 AS total_dur_ms,
+              sum(dur) * 1.0 / (SELECT * FROM total_runtime) AS percentage
+            FROM sched s
+            LEFT JOIN thread t
+              USING (utid)
+            LEFT JOIN cpus c
+              ON s.cpu = c.cpu_index
+            WHERE t.tid = ${tid}
+            GROUP BY 1`, `runtime cluster distrubtion for tid ${tid}`);
+      },
+    });
+
+    ctx.registerCommand({
+      id: 'dev.perfetto.AndroidPerf#SchedLatency',
+      name: 'Run query: top 50 sched latency for a thread',
+      callback: async (tid) => {
+        if (tid === undefined) {
+          tid = prompt('Enter a thread tid', '');
+          if (tid === null) return;
+        }
+        ctx.tabs.openQuery(`
+          SELECT ts.*, t.tid, t.name, tt.id AS track_id
+          FROM thread_state ts
+          LEFT JOIN thread_track tt
+           USING (utid)
+          LEFT JOIN thread t
+           USING (utid)
+          WHERE ts.state IN ('R', 'R+') AND tid = ${tid}
+           ORDER BY dur DESC
+          LIMIT 50`, `top 50 sched latency slice for tid ${tid}`);
+      },
+    });
   }
 }
 
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS
new file mode 100644
index 0000000..e5632b1
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS
@@ -0,0 +1 @@
+lukechang@google.com
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
new file mode 100644
index 0000000..46fbb46
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -0,0 +1,109 @@
+// Copyright (C) 2023 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.
+
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {addDebugSliceTrack} from '../../tracks/debug/slice_track';
+
+class AndroidPerfTraceCounters implements Plugin {
+
+  onActivate(_: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerCommand({
+      id: 'dev.perfetto.AndroidPerfTraceCounters#ThreadRuntimeIPC',
+      name: 'Add a track to show a thread runtime ipc',
+      callback: async (tid) => {
+        if (tid === undefined) {
+          tid = prompt('Enter a thread tid', '');
+          if (tid === null) return;
+        }
+        const sql_prefix = `
+WITH
+  sched_switch_ipc AS (
+    SELECT
+      ts,
+      EXTRACT_ARG(arg_set_id, 'prev_pid') AS tid,
+      EXTRACT_ARG(arg_set_id, 'prev_comm') AS thread_name,
+      EXTRACT_ARG(arg_set_id, 'inst') / (EXTRACT_ARG(arg_set_id, 'cyc') * 1.0) AS ipc,
+      EXTRACT_ARG(arg_set_id, 'inst') AS instruction,
+      EXTRACT_ARG(arg_set_id, 'cyc') AS cycle,
+      EXTRACT_ARG(arg_set_id, 'stallbm') AS stall_backend_mem,
+      EXTRACT_ARG(arg_set_id, 'l3dm') AS l3_cache_miss
+    FROM ftrace_event
+    WHERE name = 'sched_switch_with_ctrs' AND tid = ${tid}
+  ),
+  target_thread_sched_slice AS (
+    SELECT s.*, t.tid, t.name FROM sched s LEFT JOIN thread t USING (utid) WHERE t.tid = ${tid}
+  ),
+  target_thread_ipc_slice AS (
+    SELECT
+      (
+        SELECT
+          ts
+        FROM target_thread_sched_slice ts
+        WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+        ORDER BY ts.ts DESC
+        LIMIT 1
+      ) AS ts,
+      (
+        SELECT
+          dur
+        FROM target_thread_sched_slice ts
+        WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+        ORDER BY ts.ts DESC
+        LIMIT 1
+      ) AS dur,
+      ssi.ipc,
+      ssi.instruction,
+      ssi.cycle,
+      ssi.stall_backend_mem,
+      ssi.l3_cache_miss
+    FROM sched_switch_ipc ssi
+  )
+`
+
+        await addDebugSliceTrack(
+          ctx.engine,
+          {
+            sqlSource: sql_prefix + `
+SELECT * FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+          },
+          'Rutime IPC:' + tid,
+          {ts: 'ts', dur: 'dur', name: 'ipc'},
+          ['instruction', 'cycle', 'stall_backend_mem', 'l3_cache_miss' ],
+        );
+        ctx.tabs.openQuery(sql_prefix + `
+SELECT
+  (sum(instruction) * 1.0 / sum(cycle)*1.0) AS avg_ipc,
+  sum(dur)/1e6 as total_runtime_ms,
+  sum(instruction) AS total_instructions,
+  sum(cycle) AS total_cycles,
+  sum(stall_backend_mem) as total_stall_backend_mem,
+  sum(l3_cache_miss) as total_l3_cache_miss
+FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+          'target thread ipc statistic');
+      },
+    });
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'dev.perfetto.AndroidPerfTraceCounters',
+  plugin: AndroidPerfTraceCounters,
+};
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 480676c..7a2aaf2 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -15,10 +15,10 @@
 import m from 'mithril';
 
 import {Hotkey} from '../base/hotkeys';
-import {duration, Span, time} from '../base/time';
+import {duration, time} from '../base/time';
 import {ColorScheme} from '../common/colorizer';
+import {PanelSize} from '../frontend/panel';
 import {Store} from '../frontend/store';
-import {PxSpan, TimeScale} from '../frontend/time_scale';
 import {EngineProxy} from '../trace_processor/engine';
 
 export {createStore, Store} from '../frontend/store';
@@ -173,12 +173,9 @@
 
 export interface Track {
   onCreate(ctx: TrackContext): void;
-  render(ctx: CanvasRenderingContext2D): void;
+  render(ctx: CanvasRenderingContext2D, size: PanelSize): void;
   onFullRedraw(): void;
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined;
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined;
   getHeight(): number;
   getTrackShellButtons(): m.Children;
   onMouseMove(position: {x: number, y: number}): void;
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index 2f78a57..04237b6 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -21,6 +21,7 @@
 import {LIMIT, TrackData} from '../../common/track_data';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {NewTrackArgs} from '../../frontend/track';
 import {
   Plugin,
@@ -107,8 +108,8 @@
     super(args);
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
-    const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
+    const {visibleTimeScale} = globals.frontendLocalState;
 
     const data = this.data();
 
@@ -116,16 +117,9 @@
 
     const dataStartPx = visibleTimeScale.timeToPx(data.start);
     const dataEndPx = visibleTimeScale.timeToPx(data.end);
-    const visibleStartPx = windowSpan.start;
-    const visibleEndPx = windowSpan.end;
 
     checkerboardExcept(
-        ctx,
-        this.getHeight(),
-        visibleStartPx,
-        visibleEndPx,
-        dataStartPx,
-        dataEndPx);
+        ctx, this.getHeight(), 0, size.width, dataStartPx, dataEndPx);
 
     const quantWidth =
         Math.max(EVT_PX, visibleTimeScale.durationToPx(data.resolution));
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index 4f2b7f4..f9b61a5 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -29,6 +29,7 @@
 } from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {
   EngineProxy,
   LONG,
@@ -348,11 +349,10 @@
     );
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
     const {
       visibleTimeScale: timeScale,
-      windowSpan,
     } = globals.frontendLocalState;
     const data = this.data;
 
@@ -389,7 +389,7 @@
       minimumValue = data.minimumRate;
     }
 
-    const endPx = windowSpan.end;
+    const endPx = size.width;
     const zeroY = MARGIN_TOP + RECT_HEIGHT / (minimumValue < 0 ? 2 : 1);
 
     // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
@@ -550,8 +550,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        windowSpan.start,
-        windowSpan.end,
+        0,
+        size.width,
         timeScale.timeToPx(data.start),
         timeScale.timeToPx(data.end));
   }
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index f07f263..06f3087 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -27,6 +27,7 @@
 import {TrackData} from '../../common/track_data';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {NewTrackArgs} from '../../frontend/track';
 import {
   Plugin,
@@ -288,12 +289,11 @@
     return MARGIN_TOP + RECT_HEIGHT;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
     const {
       visibleTimeScale,
       visibleWindowTime,
-      windowSpan,
     } = globals.frontendLocalState;
     const data = this.data();
 
@@ -307,7 +307,7 @@
     assertTrue(data.timestamps.length === data.maxFreqKHz.length);
     assertTrue(data.timestamps.length === data.lastIdleValues.length);
 
-    const endPx = windowSpan.end;
+    const endPx = size.width;
     const zeroY = MARGIN_TOP + RECT_HEIGHT;
 
     // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
@@ -383,7 +383,7 @@
     // Draw CPU idle rectangles that overlay the CPU freq graph.
     ctx.fillStyle = `rgba(240, 240, 240, 1)`;
 
-    for (let i = 0; i < data.lastIdleValues.length; i++) {
+    for (let i = startIdx; i < endIdx; i++) {
       if (data.lastIdleValues[i] < 0) {
         continue;
       }
@@ -456,8 +456,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        windowSpan.start,
-        windowSpan.end,
+        0,
+        size.width,
         visibleTimeScale.timeToPx(data.start),
         visibleTimeScale.timeToPx(data.end));
   }
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index 59ad373..df0916b 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -24,6 +24,7 @@
 } from '../../common/track_adapter';
 import {TrackData} from '../../common/track_data';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {TimeScale} from '../../frontend/time_scale';
 import {NewTrackArgs} from '../../frontend/track';
 import {
@@ -102,7 +103,7 @@
     return MARGIN_TOP + RECT_HEIGHT - 1;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, _size: PanelSize): void {
     const {
       visibleTimeScale: timeScale,
     } = globals.frontendLocalState;
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 0e9c470..f4b2830 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -34,6 +34,7 @@
 import {TrackData} from '../../common/track_data';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {NewTrackArgs} from '../../frontend/track';
 import {
   EngineProxy,
@@ -217,9 +218,9 @@
     return TRACK_HEIGHT;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
-    const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
+    const {visibleTimeScale} = globals.frontendLocalState;
     const data = this.data();
 
     if (data === undefined) return;  // Can't possibly draw anything.
@@ -229,8 +230,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        windowSpan.start,
-        windowSpan.end,
+        0,
+        size.width,
         visibleTimeScale.timeToPx(data.start),
         visibleTimeScale.timeToPx(data.end));
 
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index 75c741e..6ae8474 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -18,6 +18,7 @@
 import {TrackHelperLEGACY} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {
   EngineProxy,
   Plugin,
@@ -89,10 +90,9 @@
     return result;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     const {
       visibleTimeScale,
-      windowSpan,
     } = globals.frontendLocalState;
 
     const data = this.data;
@@ -101,16 +101,9 @@
 
     const dataStartPx = visibleTimeScale.timeToPx(data.start);
     const dataEndPx = visibleTimeScale.timeToPx(data.end);
-    const visibleStartPx = windowSpan.start;
-    const visibleEndPx = windowSpan.end;
 
     checkerboardExcept(
-        ctx,
-        this.getHeight(),
-        visibleStartPx,
-        visibleEndPx,
-        dataStartPx,
-        dataEndPx);
+        ctx, this.getHeight(), 0, size.width, dataStartPx, dataEndPx);
 
     const diamondSideLen = RECT_HEIGHT / Math.sqrt(2);
 
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index dd30045..a78e36d 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -24,6 +24,7 @@
 import {TrackData} from '../../common/track_data';
 import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {TimeScale} from '../../frontend/time_scale';
 import {NewTrackArgs} from '../../frontend/track';
 import {
@@ -99,7 +100,7 @@
     return MARGIN_TOP + RECT_HEIGHT - 1;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, _size: PanelSize): void {
     const {
       visibleTimeScale,
     } = globals.frontendLocalState;
diff --git a/ui/src/tracks/process_summary/process_scheduling_track.ts b/ui/src/tracks/process_summary/process_scheduling_track.ts
index a955278..455cac5 100644
--- a/ui/src/tracks/process_summary/process_scheduling_track.ts
+++ b/ui/src/tracks/process_summary/process_scheduling_track.ts
@@ -28,6 +28,7 @@
 import {TrackData} from '../../common/track_data';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {NewTrackArgs} from '../../frontend/track';
 import {
   LONG,
@@ -200,11 +201,10 @@
     return TRACK_HEIGHT;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
     const {
       visibleTimeScale,
-      visibleWindowTime,
       visibleTimeSpan,
     } = globals.frontendLocalState;
     const data = this.data();
@@ -216,8 +216,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        visibleTimeScale.hpTimeToPx(visibleWindowTime.start),
-        visibleTimeScale.hpTimeToPx(visibleWindowTime.end),
+        0,
+        size.width,
         visibleTimeScale.timeToPx(data.start),
         visibleTimeScale.timeToPx(data.end));
 
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
index ee0bcc8..383dcb3 100644
--- a/ui/src/tracks/process_summary/process_summary_track.ts
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -20,6 +20,7 @@
 import {LIMIT, TrackData} from '../../common/track_data';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {NewTrackArgs} from '../../frontend/track';
 import {NUM} from '../../trace_processor/query_result';
 
@@ -150,10 +151,9 @@
     return TRACK_HEIGHT;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     const {
       visibleTimeScale,
-      windowSpan,
     } = globals.frontendLocalState;
     const data = this.data();
     if (data === undefined) return;  // Can't possibly draw anything.
@@ -161,8 +161,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        windowSpan.start,
-        windowSpan.end,
+        0,
+        size.width,
         visibleTimeScale.timeToPx(data.start),
         visibleTimeScale.timeToPx(data.end));
 
@@ -171,8 +171,8 @@
 
   // TODO(dproy): Dedup with CPU slices.
   renderSummary(ctx: CanvasRenderingContext2D, data: Data): void {
-    const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
-    const startPx = windowSpan.start;
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const startPx = 0;
     const bottomY = TRACK_HEIGHT;
 
     let lastX = startPx;
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index 7544ddb..074025e 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -28,6 +28,7 @@
 import {TrackData} from '../../common/track_data';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
 import {NewTrackArgs} from '../../frontend/track';
 import {
   Plugin,
@@ -187,11 +188,10 @@
     return 2 * MARGIN_TOP + RECT_HEIGHT;
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
+  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void {
     const {
       visibleTimeScale: timeScale,
       visibleTimeSpan,
-      windowSpan,
     } = globals.frontendLocalState;
     const data = this.data();
     const charWidth = ctx.measureText('dbpqaouk').width / 8;
@@ -205,8 +205,8 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        windowSpan.start,
-        windowSpan.end,
+        0,
+        size.width,
         timeScale.timeToPx(data.start),
         timeScale.timeToPx(data.end),
     );
@@ -257,7 +257,7 @@
           const rectStart =
               Math.max(0 - EXCESS_WIDTH, timeScale.timeToPx(tStart));
           const rectEnd =
-              Math.min(windowSpan.end + EXCESS_WIDTH, timeScale.timeToPx(tEnd));
+              Math.min(size.width + EXCESS_WIDTH, timeScale.timeToPx(tEnd));
           ctx.strokeStyle = colorScheme.base.cssString;
           ctx.beginPath();
           ctx.lineWidth = 3;