Merge "[metrics] Add pid and tids to android_monitor_contention metric"
diff --git a/Android.bp b/Android.bp
index 2196b71..91b7f78 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10038,6 +10038,7 @@
         "src/trace_processor/metrics/sql/android/java_heap_histogram.sql",
         "src/trace_processor/metrics/sql/android/java_heap_stats.sql",
         "src/trace_processor/metrics/sql/android/mem_stats_priority_breakdown.sql",
+        "src/trace_processor/metrics/sql/android/network_activity_template.sql",
         "src/trace_processor/metrics/sql/android/p_state.sql",
         "src/trace_processor/metrics/sql/android/power_drain_in_watts.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data.sql",
diff --git a/BUILD b/BUILD
index 0f0551b..195292f 100644
--- a/BUILD
+++ b/BUILD
@@ -1787,6 +1787,7 @@
         "src/trace_processor/metrics/sql/android/java_heap_histogram.sql",
         "src/trace_processor/metrics/sql/android/java_heap_stats.sql",
         "src/trace_processor/metrics/sql/android/mem_stats_priority_breakdown.sql",
+        "src/trace_processor/metrics/sql/android/network_activity_template.sql",
         "src/trace_processor/metrics/sql/android/p_state.sql",
         "src/trace_processor/metrics/sql/android/power_drain_in_watts.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data.sql",
diff --git a/docs/quickstart/chrome-tracing.md b/docs/quickstart/chrome-tracing.md
index 4e17172..6786b4d 100644
--- a/docs/quickstart/chrome-tracing.md
+++ b/docs/quickstart/chrome-tracing.md
@@ -4,6 +4,8 @@
 
 > To record traces from Chrome on Android, follow the [instructions for recording Android system traces](/docs/quickstart/android-tracing.md) and enable the Chrome probe.
 
+>> If you are using [user build of Android](https://source.android.com/docs/setup/build/building#lunch), you'll have to enable integration with system Perfetto by switching chrome://flags#enable-perfetto-system-tracing to "Enabled" and restarting Chrome.
+
 ## Recording a trace
 
 1. Navigate to [ui.perfetto.dev](https://ui.perfetto.dev/) and select **"Record new trace"** from the left menu.
diff --git a/include/perfetto/protozero/proto_decoder.h b/include/perfetto/protozero/proto_decoder.h
index 2210532..c27fcad 100644
--- a/include/perfetto/protozero/proto_decoder.h
+++ b/include/perfetto/protozero/proto_decoder.h
@@ -340,7 +340,8 @@
       uint32_t field_id,
       bool* parse_error_location) const {
     const Field& field = Get(field_id);
-    if (field.valid()) {
+    if (field.valid() &&
+        field.type() == proto_utils::ProtoWireType::kLengthDelimited) {
       return PackedRepeatedFieldIterator<wire_type, cpp_type>(
           field.data(), field.size(), parse_error_location);
     }
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index aa107e2..e886311 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -11344,7 +11344,7 @@
 // Deliberate empty message. See comment on StatsdAtom#atom below.
 message Atom {}
 
-// One or more statsd atoms. Ideally this should continue to match:
+// One or more statsd atoms. This must continue to match:
 // perfetto/protos/third_party/statsd/shell_data.proto
 // So that we can efficiently add data from statsd directly to the
 // trace.
diff --git a/protos/perfetto/trace/statsd/statsd_atom.proto b/protos/perfetto/trace/statsd/statsd_atom.proto
index 026766d..62ca960 100644
--- a/protos/perfetto/trace/statsd/statsd_atom.proto
+++ b/protos/perfetto/trace/statsd/statsd_atom.proto
@@ -20,7 +20,7 @@
 // Deliberate empty message. See comment on StatsdAtom#atom below.
 message Atom {}
 
-// One or more statsd atoms. Ideally this should continue to match:
+// One or more statsd atoms. This must continue to match:
 // perfetto/protos/third_party/statsd/shell_data.proto
 // So that we can efficiently add data from statsd directly to the
 // trace.
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 1f5a211..cce5e76 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -513,7 +513,10 @@
   optional int32 browsing_instance_id = 1;
 
   // The ID of the CoopRelatedGroup that the BrowsingContextState belongs to.
-  optional int32 coop_related_group_id = 2;
+  optional int32 coop_related_group_id = 2 [deprecated = true];
+
+  // The token of the CoopRelatedGroup that the BrowsingContextState belongs to.
+  optional string coop_related_group_token = 3;
 
   // Additional untyped debug information associated with this
   // FrameTreeNode, populated via TracedProto::AddDebugAnnotations API.
diff --git a/protos/third_party/statsd/shell_data.proto b/protos/third_party/statsd/shell_data.proto
index e4c31bd..64551ea 100644
--- a/protos/third_party/statsd/shell_data.proto
+++ b/protos/third_party/statsd/shell_data.proto
@@ -20,8 +20,8 @@
 
 // This is a manual import of ShellData:
 // https://cs.android.com/android/platform/superproject/+/master:packages/modules/StatsD/statsd/src/shell/shell_data.proto;l=27;drc=d2e51ecdf08753688fb889b657dcba60adb994f3
-
+// This must exactly match perfetto.protos.StatsdAtom.
 message ShellData {
   repeated bytes atom = 1;
-  repeated int64 timestamp_nanos = 2 [packed = true];
+  repeated int64 timestamp_nanos = 2;
 }
diff --git a/src/protozero/proto_decoder_unittest.cc b/src/protozero/proto_decoder_unittest.cc
index 0991f88..1e962d7 100644
--- a/src/protozero/proto_decoder_unittest.cc
+++ b/src/protozero/proto_decoder_unittest.cc
@@ -592,5 +592,26 @@
   ASSERT_FALSE(field.valid());
 }
 
+// Check what happens when trying to parse packed repeated field and finding a
+// mismatching wire type instead. A compliant protobuf decoder should accept it,
+// but protozero doesn't handle that. At least it shouldn't crash.
+TEST(ProtoDecoderTest, PacketRepeatedWireTypeMismatch) {
+  protozero::HeapBuffered<pbtest::PackedRepeatedFields> message;
+  // A proper packed encoding should have a length delimited wire type. Use a
+  // var int wire type instead.
+  constexpr int kFieldId = pbtest::PackedRepeatedFields::kFieldInt32FieldNumber;
+  message->AppendTinyVarInt(kFieldId, 5);
+  auto data = message.SerializeAsArray();
+
+  pbtest::PackedRepeatedFields::Decoder decoder(data.data(), data.size());
+  bool parse_error = false;
+  auto it = decoder.field_int32(&parse_error);
+  // The decoder doesn't return a parse error (maybe it should, but that has
+  // been the behavior since the beginning).
+  ASSERT_FALSE(parse_error);
+  // But the iterator returns 0 elements.
+  EXPECT_FALSE(it);
+}
+
 }  // namespace
 }  // namespace protozero
diff --git a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
index f692373..712e3d1 100644
--- a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+++ b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
@@ -191,6 +191,11 @@
     // configurations)
     if (group == "ftrace" && proto.event_name == "print" && field->name == "ip")
       continue;
+    // Ignore the "nid" field. On new kernels, this field has a type that we
+    // don't know how to parse. See b/281660544
+    if (group == "f2fs" && proto.event_name == "f2fs_truncate_partial_nodes" &&
+        field->name == "nid")
+      continue;
     s += "{";
     s += "kUnsetOffset, ";
     s += "kUnsetSize, ";
diff --git a/src/trace_processor/db/sorting_overlay.h b/src/trace_processor/db/sorting_overlay.h
index 4d490e3..7b536a1 100644
--- a/src/trace_processor/db/sorting_overlay.h
+++ b/src/trace_processor/db/sorting_overlay.h
@@ -31,8 +31,8 @@
 class SortingOverlay : public ColumnOverlay {
  public:
   explicit SortingOverlay(ColumnOverlay* ancestor);
-  void Filter(FilterOp, SqlValue, RowMap&) override;
-  void StableSort(uint32_t* rows_order, uint32_t rows_size) override;
+  void Filter(FilterOp, SqlValue, RowMap&) const override;
+  void StableSort(uint32_t* rows_order, uint32_t rows_size) const override;
 
  private:
   std::unique_ptr<ColumnOverlay> inner_;
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index bf54d44..a566661 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -82,6 +82,7 @@
     "java_heap_histogram.sql",
     "java_heap_stats.sql",
     "mem_stats_priority_breakdown.sql",
+    "network_activity_template.sql",
     "p_state.sql",
     "power_drain_in_watts.sql",
     "power_profile_data.sql",
diff --git a/src/trace_processor/metrics/sql/android/network_activity_template.sql b/src/trace_processor/metrics/sql/android/network_activity_template.sql
new file mode 100644
index 0000000..2dcfa31
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/network_activity_template.sql
@@ -0,0 +1,74 @@
+--
+-- Copyright 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
+--
+--     https://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.
+
+SELECT IMPORT('android.network_packets');
+
+-- Creates a view of aggregated network activity. It is common among networking
+-- to have the interface active for some time after network use. For example, in
+-- mobile networking, it is common to have the cellular interface active for 10
+-- or more seconds after the last packet was sent or received. This view takes
+-- raw packet timing and aggregates it into something that approximates the
+-- activity of the underlying interface.
+--
+-- @arg view_name        The name of the output view.
+-- @arg group_by         Expression to group by (set to 'null' for no grouping).
+-- @arg filter           Expression on `android_network_packets` to filter by.
+-- @arg idle_ns          The amount of time before considering the network idle.
+-- @arg quant_ns         Quantization value, to group rows before the heavy
+--                       part of the query. This should be smaller than idle_ns.
+--
+-- @column group_by      The group_by columns are all present in the output.
+-- @column ts            The timestamp indicating the start of the segment.
+-- @column dur           The duration of the current segment.
+-- @column packet_count  The total number of packets in this segment.
+-- @column packet_length The total number of bytes for packets in this segment.
+CREATE VIEW {{view_name}} AS
+WITH quantized AS (
+  SELECT
+    {{group_by}},
+    MIN(ts) AS ts,
+    MAX(ts+dur)-MIN(ts) AS dur,
+    SUM(packet_count) AS packet_count,
+    SUM(packet_length) AS packet_length
+  FROM android_network_packets
+  WHERE {{filter}}
+  GROUP BY CAST(ts / {{quant_ns}} AS INT64), {{group_by}}
+),
+with_last AS (
+  SELECT
+    *,
+    LAG(ts) OVER (
+      PARTITION BY {{group_by}}
+      ORDER BY ts
+    ) AS last_ts
+  FROM quantized
+),
+with_group AS (
+  SELECT
+    *,
+    COUNT(IIF(ts-last_ts>{{idle_ns}}, 1, null)) OVER (
+      PARTITION BY {{group_by}}
+      ORDER BY ts
+    ) AS group_id
+  FROM with_last
+)
+SELECT
+  {{group_by}},
+  MIN(ts) AS ts,
+  MAX(ts+dur)-MIN(ts)+{{idle_ns}} AS dur,
+  SUM(packet_count) AS packet_count,
+  SUM(packet_length) AS packet_length
+FROM with_group
+GROUP BY group_id, {{group_by}}
diff --git a/src/trace_processor/prelude/functions/utils.h b/src/trace_processor/prelude/functions/utils.h
index 21fed04..ac848d0 100644
--- a/src/trace_processor/prelude/functions/utils.h
+++ b/src/trace_processor/prelude/functions/utils.h
@@ -19,6 +19,7 @@
 
 #include <sqlite3.h>
 #include <unordered_map>
+
 #include "perfetto/ext/base/base64.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/trace_processor/demangle.h"
@@ -26,9 +27,9 @@
 #include "src/trace_processor/export_json.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/prelude/functions/create_function_internal.h"
-#include "src/trace_processor/util/status_macros.h"
-
 #include "src/trace_processor/prelude/functions/sql_function.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index 2ec82e0..2af8b81 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -130,11 +130,11 @@
 
  private:
   struct FnHasher {
-    uint64_t operator()(const std::pair<std::string, int>& x) const {
+    size_t operator()(const std::pair<std::string, int>& x) const {
       base::Hasher hasher;
       hasher.Update(x.first);
       hasher.Update(x.second);
-      return hasher.digest();
+      return static_cast<size_t>(hasher.digest());
     }
   };
 
diff --git a/src/trace_processor/stdlib/android/battery_stats.sql b/src/trace_processor/stdlib/android/battery_stats.sql
index 753fd61..2507cb1 100644
--- a/src/trace_processor/stdlib/android/battery_stats.sql
+++ b/src/trace_processor/stdlib/android/battery_stats.sql
@@ -21,7 +21,7 @@
 -- @arg value LONG    The counter value.
 -- @ret STRING        The human-readable name for the counter value.
 SELECT CREATE_FUNCTION(
-  'BATTERY_STATS_COUNTER_TO_STRING(track STRING, value LONG)',
+  'BATTERY_STATS_COUNTER_TO_STRING(track STRING, value FLOAT)',
   'STRING',
   '
   SELECT
@@ -125,16 +125,16 @@
 --
 -- @column ts                  Timestamp in nanoseconds.
 -- @column dur                 The duration the state was active.
--- @column name                The name of the counter track.
+-- @column track_name          The name of the counter track.
 -- @column value               The counter value as a number.
 -- @column value_name          The counter value as a human-readable string.
 CREATE VIEW android_battery_stats_state AS
 SELECT
   ts,
-  name,
-  value,
-  BATTERY_STATS_VALUE_TO_STRING(name, value) AS value_name,
-  LEAD(ts, 1, TRACE_END()) OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur
+  name AS track_name,
+  CAST(value AS INT64) AS value,
+  BATTERY_STATS_COUNTER_TO_STRING(name, value) AS value_name,
+  IFNULL(LEAD(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts, -1) AS dur
 FROM counter
 JOIN counter_track
   ON counter.track_id = counter_track.id
diff --git a/src/trace_processor/stdlib/android/process_metadata.sql b/src/trace_processor/stdlib/android/process_metadata.sql
index 20fb82e..6154f00 100644
--- a/src/trace_processor/stdlib/android/process_metadata.sql
+++ b/src/trace_processor/stdlib/android/process_metadata.sql
@@ -50,11 +50,19 @@
 LEFT JOIN internal_uid_package_count ON process.android_appid = internal_uid_package_count.uid
 LEFT JOIN package_list plist
   ON (
-    process.android_appid = plist.uid
-    AND internal_uid_package_count.uid = plist.uid
-    AND (
-      -- unique match
-      internal_uid_package_count.cnt = 1
-      -- or process name starts with the package name
-      OR process.name GLOB plist.package_name || '*')
+    (
+      process.android_appid = plist.uid
+      AND internal_uid_package_count.uid = plist.uid
+      AND (
+        -- unique match
+        internal_uid_package_count.cnt = 1
+        -- or process name starts with the package name
+        OR process.name GLOB plist.package_name || '*')
+    )
+    OR
+    (
+      -- isolated processes can only be matched based on the name prefix
+      process.android_appid >= 90000 AND process.android_appid < 100000
+      AND STR_SPLIT(process.name, ':', 0) GLOB plist.package_name || '*'
+    )
   );
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index f00c7fa..f64b1c1 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -596,7 +596,7 @@
 
   // Legacy tables.
   engine_.RegisterVirtualTableModule<SqlStatsTable>(
-      "sql_stats", storage, SqliteTable::TableType::kEponymousOnly, false);
+      "sqlstats", storage, SqliteTable::TableType::kEponymousOnly, false);
   engine_.RegisterVirtualTableModule<StatsTable>(
       "stats", storage, SqliteTable::TableType::kEponymousOnly, false);
 
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 459b640..859cc8b 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -34,6 +34,7 @@
 #include "test/gtest_and_gmock.h"
 
 #include "protos/perfetto/trace/ftrace/dpu.gen.h"
+#include "protos/perfetto/trace/ftrace/f2fs.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
@@ -62,6 +63,7 @@
 using testing::Pair;
 using testing::Property;
 using testing::Return;
+using testing::SizeIs;
 using testing::StartsWith;
 
 namespace perfetto {
@@ -3303,5 +3305,113 @@
   EXPECT_THAT(AllTracePackets(), IsEmpty());
 }
 
+// Kernel code:
+// trace_f2fs_truncate_partial_nodes(... nid = {1,2,3}, depth = 4, err = 0)
+//
+// After kernel commit 0b04d4c0542e("f2fs: Fix
+// f2fs_truncate_partial_nodes ftrace event")
+static ExamplePage g_f2fs_truncate_partial_nodes_new{
+    "b281660544_new",
+    R"(
+00000000: 1555 c3e4 cb07 0000 3c00 0000 0000 0000  .U......<.......
+00000010: 3e33 0b87 2700 0000 0c00 0000 7d02 0000  >3..'.......}...
+00000020: c638 0000 3900 e00f 0000 0000 b165 0000  .8..9........e..
+00000030: 0000 0000 0100 0000 0200 0000 0300 0000  ................
+00000040: 0400 0000 0000 0000 0000 0000 0000 0000  ................
+    )",
+};
+
+TEST_F(CpuReaderParsePagePayloadTest, F2fsTruncatePartialNodesNew) {
+  const ExamplePage* test_case = &g_f2fs_truncate_partial_nodes_new;
+
+  ProtoTranslationTable* table = GetTable(test_case->name);
+  auto page = PageFromXxd(test_case->data);
+
+  FtraceDataSourceConfig ds_config = EmptyConfig();
+  ds_config.event_filter.AddEnabledEvent(table->EventToFtraceId(
+      GroupAndName("f2fs", "f2fs_truncate_partial_nodes")));
+
+  const uint8_t* parse_pos = page.get();
+  std::optional<CpuReader::PageHeader> page_header =
+      CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+
+  const uint8_t* page_end = page.get() + base::kPageSize;
+  ASSERT_TRUE(page_header.has_value());
+  EXPECT_FALSE(page_header->lost_events);
+  EXPECT_LE(parse_pos + page_header->size, page_end);
+
+  size_t evt_bytes = CpuReader::ParsePagePayload(
+      parse_pos, &page_header.value(), table, &ds_config,
+      CreateBundler(ds_config), &metadata_);
+
+  EXPECT_LT(0u, evt_bytes);
+
+  auto bundle = GetBundle();
+  ASSERT_THAT(bundle.event(), SizeIs(1));
+  auto& event = bundle.event()[0];
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().dev(), 65081u);
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().ino(), 26033u);
+  // This field is disabled in ftrace_proto_gen.cc
+  EXPECT_FALSE(event.f2fs_truncate_partial_nodes().has_nid());
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().depth(), 4);
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().err(), 0);
+}
+
+// Kernel code:
+// trace_f2fs_truncate_partial_nodes(... nid = {1,2,3}, depth = 4, err = 0)
+//
+// Before kernel commit 0b04d4c0542e("f2fs: Fix
+// f2fs_truncate_partial_nodes ftrace event")
+static ExamplePage g_f2fs_truncate_partial_nodes_old{
+    "b281660544_old",
+    R"(
+00000000: 8f90 aa0d 9e00 0000 3c00 0000 0000 0000  ........<.......
+00000010: 3e97 0295 0e01 0000 0c00 0000 7d02 0000  >...........}...
+00000020: 8021 0000 3900 e00f 0000 0000 0d66 0000  .!..9........f..
+00000030: 0000 0000 0100 0000 0200 0000 0300 0000  ................
+00000040: 0400 0000 0000 0000 0000 0000 0000 0000  ................
+    )",
+};
+
+TEST_F(CpuReaderParsePagePayloadTest, F2fsTruncatePartialNodesOld) {
+  const ExamplePage* test_case = &g_f2fs_truncate_partial_nodes_old;
+
+  ProtoTranslationTable* table = GetTable(test_case->name);
+  auto page = PageFromXxd(test_case->data);
+
+  FtraceDataSourceConfig ds_config = EmptyConfig();
+  auto id = table->EventToFtraceId(
+      GroupAndName("f2fs", "f2fs_truncate_partial_nodes"));
+  PERFETTO_LOG("Enabling: %zu", id);
+  ds_config.event_filter.AddEnabledEvent(id);
+
+  const uint8_t* parse_pos = page.get();
+  std::optional<CpuReader::PageHeader> page_header =
+      CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+
+  const uint8_t* page_end = page.get() + base::kPageSize;
+  ASSERT_TRUE(page_header.has_value());
+  EXPECT_FALSE(page_header->lost_events);
+  EXPECT_LE(parse_pos + page_header->size, page_end);
+
+  size_t evt_bytes = CpuReader::ParsePagePayload(
+      parse_pos, &page_header.value(), table, &ds_config,
+      CreateBundler(ds_config), &metadata_);
+
+  EXPECT_LT(0u, evt_bytes);
+
+  auto bundle = GetBundle();
+  ASSERT_THAT(bundle.event(), SizeIs(1));
+  auto& event = bundle.event()[0];
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().dev(), 65081u);
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().ino(), 26125u);
+  // This field is disabled in ftrace_proto_gen.cc
+  EXPECT_FALSE(event.f2fs_truncate_partial_nodes().has_nid());
+  // Due to a kernel bug, nid[1] is parsed as depth.
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().depth(), 2);
+  // Due to a kernel bug, nid[2] is parsed as err.
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().err(), 3);
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index f17f6b6..6a8cca3 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -4266,9 +4266,6 @@
             "ino", 2, ProtoSchemaType::kUint64,
             TranslationStrategy::kInvalidTranslationStrategy},
            {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
-            "nid", 3, ProtoSchemaType::kUint32,
-            TranslationStrategy::kInvalidTranslationStrategy},
-           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
             "depth", 4, ProtoSchemaType::kInt32,
             TranslationStrategy::kInvalidTranslationStrategy},
            {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
diff --git a/src/traced/probes/ftrace/test/data/b281660544_new/available_events b/src/traced/probes/ftrace/test/data/b281660544_new/available_events
new file mode 100644
index 0000000..5588a96
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_new/available_events
@@ -0,0 +1 @@
+f2fs:f2fs_truncate_partial_nodes
diff --git a/src/traced/probes/ftrace/test/data/b281660544_new/events/f2fs/f2fs_truncate_partial_nodes/format b/src/traced/probes/ftrace/test/data/b281660544_new/events/f2fs/f2fs_truncate_partial_nodes/format
new file mode 100644
index 0000000..2f5a8ab
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_new/events/f2fs/f2fs_truncate_partial_nodes/format
@@ -0,0 +1,15 @@
+name: f2fs_truncate_partial_nodes
+ID: 637
+format:
+	field:unsigned short common_type;       offset:0;       size:2; signed:0;
+	field:unsigned char common_flags;       offset:2;       size:1; signed:0;
+	field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;
+	field:int common_pid;   offset:4;       size:4; signed:1;
+
+	field:dev_t dev;        offset:8;       size:4; signed:0;
+	field:ino_t ino;        offset:16;      size:8; signed:0;
+	field:nid_t nid[3];     offset:24;      size:12;        signed:0;
+	field:int depth;        offset:36;      size:4; signed:1;
+	field:int err;  offset:40;      size:4; signed:1;
+
+print fmt: "dev = (%d,%d), ino = %lu, nid[0] = %u, nid[1] = %u, nid[2] = %u, depth = %d, err = %d", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), (unsigned long)REC->ino, (unsigned int)REC->nid[0], (unsigned int)REC->nid[1], (unsigned int)REC->nid[2], REC->depth, REC->err
diff --git a/src/traced/probes/ftrace/test/data/b281660544_new/events/header_page b/src/traced/probes/ftrace/test/data/b281660544_new/events/header_page
new file mode 100644
index 0000000..276dce9
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_new/events/header_page
@@ -0,0 +1,4 @@
+	field: u64 timestamp;   offset:0;       size:8; signed:0;
+	field: local_t commit;  offset:8;       size:8; signed:1;
+	field: int overwrite;   offset:8;       size:1; signed:1;
+	field: char data;       offset:16;      size:4080;      signed:1;
diff --git a/src/traced/probes/ftrace/test/data/b281660544_old/available_events b/src/traced/probes/ftrace/test/data/b281660544_old/available_events
new file mode 100644
index 0000000..5588a96
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_old/available_events
@@ -0,0 +1 @@
+f2fs:f2fs_truncate_partial_nodes
diff --git a/src/traced/probes/ftrace/test/data/b281660544_old/events/f2fs/f2fs_truncate_partial_nodes/format b/src/traced/probes/ftrace/test/data/b281660544_old/events/f2fs/f2fs_truncate_partial_nodes/format
new file mode 100644
index 0000000..15f6a64
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_old/events/f2fs/f2fs_truncate_partial_nodes/format
@@ -0,0 +1,15 @@
+name: f2fs_truncate_partial_nodes
+ID: 637
+format:
+	field:unsigned short common_type;       offset:0;       size:2; signed:0;
+	field:unsigned char common_flags;       offset:2;       size:1; signed:0;
+	field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;
+	field:int common_pid;   offset:4;       size:4; signed:1;
+
+	field:dev_t dev;        offset:8;       size:4; signed:0;
+	field:ino_t ino;        offset:16;      size:8; signed:0;
+	field:nid_t nid[3];     offset:24;      size:4; signed:0;
+	field:int depth;        offset:28;      size:4; signed:1;
+	field:int err;  offset:32;      size:4; signed:1;
+
+print fmt: "dev = (%d,%d), ino = %lu, nid[0] = %u, nid[1] = %u, nid[2] = %u, depth = %d, err = %d", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), (unsigned long)REC->ino, (unsigned int)REC->nid[0], (unsigned int)REC->nid[1], (unsigned int)REC->nid[2], REC->depth, REC->err
diff --git a/src/traced/probes/ftrace/test/data/b281660544_old/events/header_page b/src/traced/probes/ftrace/test/data/b281660544_old/events/header_page
new file mode 100644
index 0000000..276dce9
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_old/events/header_page
@@ -0,0 +1,4 @@
+	field: u64 timestamp;   offset:0;       size:8; signed:0;
+	field: local_t commit;  offset:8;       size:8; signed:1;
+	field: int overwrite;   offset:8;       size:1; signed:1;
+	field: char data;       offset:16;      size:4080;      signed:1;
diff --git a/src/traced/probes/statsd_client/statsd_binder_data_source.cc b/src/traced/probes/statsd_client/statsd_binder_data_source.cc
index 627d4bc..9d3d7f7 100644
--- a/src/traced/probes/statsd_client/statsd_binder_data_source.cc
+++ b/src/traced/probes/statsd_client/statsd_binder_data_source.cc
@@ -242,30 +242,20 @@
                                     const uint8_t* data,
                                     size_t sz) {
   ShellDataDecoder message(data, sz);
+  if (message.has_atom()) {
+    TraceWriter::TracePacketHandle packet = writer_->NewTracePacket();
 
-  bool parse_error = false;
-  auto timestamps_it = message.timestamp_nanos(&parse_error);
-  std::vector<int64_t> timestamps;
-  if (!parse_error) {
-    for (; timestamps_it; ++timestamps_it) {
-      timestamps.push_back(*timestamps_it);
-    }
+    // The root packet gets the timestamp of *now* to aid in
+    // a) Packet sorting in trace_processor
+    // b) So we have some useful record of timestamp in case the statsd
+    //    one gets broken in some exciting way.
+    packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
 
-    TraceWriter::TracePacketHandle packet;
-    size_t i = 0;
-    for (auto it = message.atom(); it; ++it) {
-      packet = writer_->NewTracePacket();
-      if (i < timestamps.size()) {
-        packet->set_timestamp(static_cast<uint64_t>(timestamps[i++]));
-      } else {
-        packet->set_timestamp(
-            static_cast<uint64_t>(base::GetBootTimeNs().count()));
-      }
-      auto* statsd_atom = packet->set_statsd_atom();
-      auto* atom = statsd_atom->add_atom();
-      atom->AppendRawProtoBytes(it->data(), it->size());
-      packet->Finalize();
-    }
+    // Now put all the data. We rely on ShellData and StatsdAtom
+    // matching format exactly.
+    packet->AppendBytes(protos::pbzero::TracePacket::kStatsdAtomFieldNumber,
+                        message.begin(),
+                        static_cast<size_t>(message.end() - message.begin()));
   }
 
   // If we have the pending flush in progress resolve that:
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index f04f67f..b32a658 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -3637,6 +3637,7 @@
   cloned_session->flushes_requested = src->flushes_requested;
   cloned_session->flushes_succeeded = src->flushes_succeeded;
   cloned_session->flushes_failed = src->flushes_failed;
+  cloned_session->compress_deflate = src->compress_deflate;
   if (src->trace_filter) {
     // Copy the trace filter.
     cloned_session->trace_filter.reset(
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index c6d7384..fb175e9 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -1922,6 +1922,74 @@
                   &protos::gen::TracePacket::for_testing,
                   Property(&protos::gen::TestEvent::str, Eq("payload-2")))));
 }
+
+TEST_F(TracingServiceImplTest, CloneSessionWithCompression) {
+  TracingService::InitOpts init_opts;
+  init_opts.compressor_fn = ZlibCompressFn;
+  InitializeSvcWithOpts(init_opts);
+
+  // The consumer the creates the initial tracing session.
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  // The consumer that clones it and reads back the data.
+  std::unique_ptr<MockConsumer> consumer2 = CreateMockConsumer();
+  consumer2->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+
+  producer->RegisterDataSource("ds_1");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(32);
+  auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+  ds_cfg->set_name("ds_1");
+  trace_config.set_compression_type(TraceConfig::COMPRESSION_TYPE_DEFLATE);
+
+  consumer->EnableTracing(trace_config);
+  producer->WaitForTracingSetup();
+
+  producer->WaitForDataSourceSetup("ds_1");
+
+  producer->WaitForDataSourceStart("ds_1");
+
+  std::unique_ptr<TraceWriter> writer = producer->CreateTraceWriter("ds_1");
+
+  // Add some data.
+  static constexpr size_t kNumTestPackets = 20;
+  for (size_t i = 0; i < kNumTestPackets; i++) {
+    auto tp = writer->NewTracePacket();
+    std::string payload("payload" + std::to_string(i));
+    tp->set_for_testing()->set_str(payload.c_str(), payload.size());
+    tp->set_timestamp(static_cast<uint64_t>(i));
+  }
+
+  auto clone_done = task_runner.CreateCheckpoint("clone_done");
+  EXPECT_CALL(*consumer2, OnSessionCloned(_))
+      .WillOnce(Invoke([clone_done](const Consumer::OnSessionClonedArgs&) {
+        clone_done();
+      }));
+  consumer2->CloneSession(1);
+  // CloneSession() will implicitly issue a flush. Linearize with that.
+  producer->WaitForFlush(std::vector<TraceWriter*>{writer.get()});
+  task_runner.RunUntilCheckpoint("clone_done");
+
+  // Delete the initial tracing session.
+  consumer->DisableTracing();
+  consumer->FreeBuffers();
+  producer->WaitForDataSourceStop("ds_1");
+  consumer->WaitForTracingDisabled();
+
+  // Read back the cloned trace and check that it's compressed
+  std::vector<protos::gen::TracePacket> compressed_packets =
+      consumer2->ReadBuffers();
+  EXPECT_THAT(compressed_packets, Not(IsEmpty()));
+  EXPECT_THAT(compressed_packets,
+              Each(Property(&protos::gen::TracePacket::compressed_packets,
+                            Not(IsEmpty()))));
+}
+
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
 
 // Note: file_write_period_ms is set to a large enough to have exactly one flush
diff --git a/test/trace_processor/diff_tests/android/android_battery_stats_state.out b/test/trace_processor/diff_tests/android/android_battery_stats_state.out
new file mode 100644
index 0000000..04ea953
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_battery_stats_state.out
@@ -0,0 +1,4 @@
+"ts","track_name","value","value_name","dur"
+1000,"battery_stats.audio",1,"active",-1
+1000,"battery_stats.data_conn",13,"lte",3000
+4000,"battery_stats.data_conn",20,"nr",-1
diff --git a/test/trace_processor/diff_tests/android/android_network_activity.out b/test/trace_processor/diff_tests/android/android_network_activity.out
new file mode 100644
index 0000000..14418e4
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_network_activity.out
@@ -0,0 +1,4 @@
+"package_name","ts","dur","packet_count","packet_length"
+"uid=123",1000,1010,2,100
+"uid=123",3000,2500,4,200
+"uid=456",1005,1010,2,300
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index cec98ae..84bafc1 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -159,6 +159,109 @@
         """,
         out=Path('android_battery_stats_event_slices.out'))
 
+  def test_android_battery_stats_counters(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_events {
+            cpu: 1
+            event {
+              timestamp: 1000
+              pid: 1
+              print {
+                buf: "C|1000|battery_stats.data_conn|13\n"
+              }
+            }
+            event {
+              timestamp: 4000
+              pid: 1
+              print {
+                buf: "C|1000|battery_stats.data_conn|20\n"
+              }
+            }
+            event {
+              timestamp: 1000
+              pid: 1
+              print {
+                buf: "C|1000|battery_stats.audio|1\n"
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT IMPORT('android.battery_stats');
+        SELECT * FROM android_battery_stats_state
+        ORDER BY ts, track_name;
+        """,
+        out=Path('android_battery_stats_state.out'))
+
+  def test_android_network_activity(self):
+    # The following should have three activity regions:
+    # * uid=123 from 1000 to 2010 (note: end is max(ts)+idle_ns)
+    # * uid=456 from 1005 to 2015 (note: doesn't group with above due to name)
+    # * uid=123 from 3000 to 5500 (note: gap between 1010 to 3000 > idle_ns)
+    # Note: packet_timestamps are delta encoded from the base timestamp.
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 123
+            }
+            packet_timestamps: [
+              1000, 1010,
+              3000, 3050, 4000, 4500
+            ],
+            packet_lengths: [
+              50, 50,
+              50, 50, 50, 50
+            ],
+          }
+        }
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 456
+            }
+            packet_timestamps: [1005, 1015]
+            packet_lengths: [100, 200]
+          }
+        }
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_INGRESS
+              interface: "loopback"
+              uid: 123
+            }
+            packet_timestamps: [6000]
+            packet_lengths: [100]
+          }
+        }
+        """),
+        query="""
+        SELECT RUN_METRIC(
+          'android/network_activity_template.sql',
+          'view_name', 'android_network_activity',
+          'group_by',  'package_name',
+          'filter',    'iface = "wlan"',
+          'idle_ns',   '1000',
+          'quant_ns',  '100'
+        );
+
+        SELECT * FROM android_network_activity
+        ORDER BY package_name, ts;
+        """,
+        out=Path('android_network_activity.out'))
+
   def test_binder_sync_binder_metrics(self):
     return DiffTestBlueprint(
         trace=DataPath('android_binder_metric_trace.atr'),