trace_processor: add parsing and ingestion of mm_id of rss_stat

This CL adds support for parsing (in traced_probes) and ingesting mm_id
field of rss_stat which lets us track cross-process rss changes (that is
changes of an mm struct of process A by another process B).

In the past, these sort of changes were inaccurately tracked. With mm_id
and is_curr, we can accurately attribute it to the correct process and
drop events when we don't know the process whose struct is being
changed.

We also add tests to check both the old and new behaviour.

Bug: 144825618
Change-Id: If9ce3a2215e364335130c40feb923009f6a660eb
diff --git a/Android.bp b/Android.bp
index 40693fa..89562ab 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5776,6 +5776,7 @@
     "src/trace_processor/importers/ftrace/ftrace_module_impl.cc",
     "src/trace_processor/importers/ftrace/ftrace_parser.cc",
     "src/trace_processor/importers/ftrace/ftrace_tokenizer.cc",
+    "src/trace_processor/importers/ftrace/rss_stat_tracker.cc",
     "src/trace_processor/importers/ftrace/sched_event_tracker.cc",
     "src/trace_processor/importers/proto/android_probes_module.cc",
     "src/trace_processor/importers/proto/android_probes_parser.cc",
diff --git a/BUILD b/BUILD
index f94416d..1db6944 100644
--- a/BUILD
+++ b/BUILD
@@ -838,6 +838,8 @@
         "src/trace_processor/importers/ftrace/ftrace_parser.h",
         "src/trace_processor/importers/ftrace/ftrace_tokenizer.cc",
         "src/trace_processor/importers/ftrace/ftrace_tokenizer.h",
+        "src/trace_processor/importers/ftrace/rss_stat_tracker.cc",
+        "src/trace_processor/importers/ftrace/rss_stat_tracker.h",
         "src/trace_processor/importers/ftrace/sched_event_tracker.cc",
         "src/trace_processor/importers/ftrace/sched_event_tracker.h",
         "src/trace_processor/importers/proto/android_probes_module.cc",
diff --git a/protos/perfetto/trace/ftrace/kmem.proto b/protos/perfetto/trace/ftrace/kmem.proto
index a7a0df9..b207d2b 100644
--- a/protos/perfetto/trace/ftrace/kmem.proto
+++ b/protos/perfetto/trace/ftrace/kmem.proto
@@ -219,6 +219,8 @@
 message RssStatFtraceEvent {
   optional int32 member = 1;
   optional int64 size = 2;
+  optional uint32 curr = 3;
+  optional uint32 mm_id = 4;
 }
 message IonHeapShrinkFtraceEvent {
   optional string heap_name = 1;
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index ab51af6..b794b92 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -2642,6 +2642,8 @@
 message RssStatFtraceEvent {
   optional int32 member = 1;
   optional int64 size = 2;
+  optional uint32 curr = 3;
+  optional uint32 mm_id = 4;
 }
 message IonHeapShrinkFtraceEvent {
   optional string heap_name = 1;
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 76835be..45d5e0f 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -200,6 +200,8 @@
     "importers/ftrace/ftrace_parser.h",
     "importers/ftrace/ftrace_tokenizer.cc",
     "importers/ftrace/ftrace_tokenizer.h",
+    "importers/ftrace/rss_stat_tracker.cc",
+    "importers/ftrace/rss_stat_tracker.h",
     "importers/ftrace/sched_event_tracker.cc",
     "importers/ftrace/sched_event_tracker.h",
     "importers/proto/android_probes_module.cc",
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index af72c18..b3013a6 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -61,6 +61,7 @@
 FtraceParser::FtraceParser(TraceProcessorContext* context)
     : context_(context),
       binder_tracker_(context),
+      rss_stat_tracker_(context),
       sched_wakeup_name_id_(context->storage->InternString("sched_wakeup")),
       sched_waking_name_id_(context->storage->InternString("sched_waking")),
       cpu_freq_name_id_(context->storage->InternString("cpufreq")),
@@ -74,13 +75,6 @@
       oom_score_adj_id_(context->storage->InternString("oom_score_adj")),
       lmk_id_(context->storage->InternString("mem.lmk")),
       comm_name_id_(context->storage->InternString("comm")) {
-  rss_members_.emplace_back(context->storage->InternString("mem.rss.file"));
-  rss_members_.emplace_back(context->storage->InternString("mem.rss.anon"));
-  rss_members_.emplace_back(context->storage->InternString("mem.swap"));
-  rss_members_.emplace_back(context->storage->InternString("mem.rss.shmem"));
-  rss_members_.emplace_back(
-      context->storage->InternString("mem.rss.unknown"));  // Keep this last.
-
   // Build the lookup table for the strings inside ftrace events (e.g. the
   // name of ftrace event fields and the names of their args).
   for (size_t i = 0; i < GetDescriptorsSize(); i++) {
@@ -272,7 +266,7 @@
         break;
       }
       case FtraceEvent::kRssStatFieldNumber: {
-        ParseRssStat(ts, pid, data);
+        rss_stat_tracker_.ParseRssStat(ts, pid, data);
         break;
       }
       case FtraceEvent::kIonHeapGrowFieldNumber: {
@@ -320,27 +314,27 @@
         break;
       }
       case FtraceEvent::kBinderTransactionFieldNumber: {
-        ParseBinderTransaction(ts, pid, data);
+        binder_tracker_.Transaction(ts, pid);
         break;
       }
       case FtraceEvent::kBinderTransactionReceivedFieldNumber: {
-        ParseBinderTransactionReceived(ts, pid, data);
+        binder_tracker_.TransactionReceived(ts, pid);
         break;
       }
       case FtraceEvent::kBinderTransactionAllocBufFieldNumber: {
-        ParseBinderTransactionAllocBuf(ts, pid, data);
+        binder_tracker_.TransactionAllocBuf(ts, pid);
         break;
       }
       case FtraceEvent::kBinderLockFieldNumber: {
-        ParseBinderLock(ts, pid, data);
+        binder_tracker_.Lock(ts, pid);
         break;
       }
       case FtraceEvent::kBinderUnlockFieldNumber: {
-        ParseBinderUnlock(ts, pid, data);
+        binder_tracker_.Unlock(ts, pid);
         break;
       }
       case FtraceEvent::kBinderLockedFieldNumber: {
-        ParseBinderLocked(ts, pid, data);
+        binder_tracker_.Locked(ts, pid);
         break;
       }
       case FtraceEvent::kSdeTracingMarkWriteFieldNumber: {
@@ -557,25 +551,6 @@
       evt.trace_name(), tgid, evt.value());
 }
 
-void FtraceParser::ParseRssStat(int64_t ts, uint32_t pid, ConstBytes blob) {
-  protos::pbzero::RssStatFtraceEvent::Decoder rss(blob.data, blob.size);
-  const auto kRssStatUnknown = static_cast<uint32_t>(rss_members_.size()) - 1;
-  auto member = static_cast<uint32_t>(rss.member());
-  int64_t size = rss.size();
-  if (member >= rss_members_.size()) {
-    context_->storage->IncrementStats(stats::rss_stat_unknown_keys);
-    member = kRssStatUnknown;
-  }
-
-  if (size >= 0) {
-    UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
-    context_->event_tracker->PushProcessCounterForThread(
-        ts, size, rss_members_[member], utid);
-  } else {
-    context_->storage->IncrementStats(stats::rss_stat_negative_size);
-  }
-}
-
 void FtraceParser::ParseIonHeapGrowOrShrink(int64_t ts,
                                             uint32_t pid,
                                             ConstBytes blob,
@@ -772,50 +747,5 @@
   context_->process_tracker->UpdateProcessNameFromThreadName(tid, comm);
 }
 
-void FtraceParser::ParseBinderTransaction(int64_t timestamp,
-                                          uint32_t pid,
-                                          ConstBytes blob) {
-  protos::pbzero::BinderTransactionFtraceEvent::Decoder evt(blob.data,
-                                                            blob.size);
-  binder_tracker_.Transaction(timestamp, pid);
-}
-
-void FtraceParser::ParseBinderTransactionReceived(int64_t timestamp,
-                                                  uint32_t pid,
-                                                  ConstBytes blob) {
-  protos::pbzero::BinderTransactionReceivedFtraceEvent::Decoder evt(blob.data,
-                                                                    blob.size);
-  binder_tracker_.TransactionReceived(timestamp, pid);
-}
-
-void FtraceParser::ParseBinderTransactionAllocBuf(int64_t timestamp,
-                                                  uint32_t pid,
-                                                  ConstBytes blob) {
-  protos::pbzero::BinderTransactionAllocBufFtraceEvent::Decoder evt(blob.data,
-                                                                    blob.size);
-  binder_tracker_.TransactionAllocBuf(timestamp, pid);
-}
-
-void FtraceParser::ParseBinderLocked(int64_t timestamp,
-                                     uint32_t pid,
-                                     ConstBytes blob) {
-  protos::pbzero::BinderLockedFtraceEvent::Decoder evt(blob.data, blob.size);
-  binder_tracker_.Locked(timestamp, pid);
-}
-
-void FtraceParser::ParseBinderLock(int64_t timestamp,
-                                   uint32_t pid,
-                                   ConstBytes blob) {
-  protos::pbzero::BinderLockFtraceEvent::Decoder evt(blob.data, blob.size);
-  binder_tracker_.Lock(timestamp, pid);
-}
-
-void FtraceParser::ParseBinderUnlock(int64_t timestamp,
-                                     uint32_t pid,
-                                     ConstBytes blob) {
-  protos::pbzero::BinderUnlockFtraceEvent::Decoder evt(blob.data, blob.size);
-  binder_tracker_.Unlock(timestamp, pid);
-}
-
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 6dfdfb7..bd8ff81 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -21,6 +21,7 @@
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/importers/ftrace/binder_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
+#include "src/trace_processor/importers/ftrace/rss_stat_tracker.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_blob_view.h"
@@ -59,7 +60,6 @@
   void ParseSdeTracingMarkWrite(int64_t timestamp,
                                 uint32_t pid,
                                 protozero::ConstBytes);
-  void ParseRssStat(int64_t ts, uint32_t pid, protozero::ConstBytes);
   void ParseIonHeapGrowOrShrink(int64_t ts,
                                 uint32_t pid,
                                 protozero::ConstBytes,
@@ -77,25 +77,10 @@
                         uint32_t source_tid,
                         protozero::ConstBytes);
   void ParseTaskRename(protozero::ConstBytes);
-  void ParseBinderTransaction(int64_t timestamp,
-                              uint32_t pid,
-                              protozero::ConstBytes);
-  void ParseBinderTransactionReceived(int64_t timestamp,
-                                      uint32_t pid,
-                                      protozero::ConstBytes);
-  void ParseBinderTransactionAllocBuf(int64_t timestamp,
-                                      uint32_t pid,
-                                      protozero::ConstBytes);
-  void ParseBinderLocked(int64_t timestamp,
-                         uint32_t pid,
-                         protozero::ConstBytes);
-  void ParseBinderLock(int64_t timestamp, uint32_t pid, protozero::ConstBytes);
-  void ParseBinderUnlock(int64_t timestamp,
-                         uint32_t pid,
-                         protozero::ConstBytes);
 
   TraceProcessorContext* context_;
   BinderTracker binder_tracker_;
+  RssStatTracker rss_stat_tracker_;
 
   const StringId sched_wakeup_name_id_;
   const StringId sched_waking_name_id_;
@@ -110,8 +95,6 @@
   const StringId lmk_id_;
   const StringId comm_name_id_;
 
-  std::vector<StringId> rss_members_;
-
   struct FtraceMessageStrings {
     // The string id of name of the event field (e.g. sched_switch's id).
     StringId message_name_id = 0;
diff --git a/src/trace_processor/importers/ftrace/rss_stat_tracker.cc b/src/trace_processor/importers/ftrace/rss_stat_tracker.cc
new file mode 100644
index 0000000..db444a7
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/rss_stat_tracker.cc
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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/importers/ftrace/rss_stat_tracker.h"
+
+#include "src/trace_processor/event_tracker.h"
+#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/trace_processor_context.h"
+
+#include "protos/perfetto/trace/ftrace/kmem.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+RssStatTracker::RssStatTracker(TraceProcessorContext* context)
+    : context_(context) {
+  rss_members_.emplace_back(context->storage->InternString("mem.rss.file"));
+  rss_members_.emplace_back(context->storage->InternString("mem.rss.anon"));
+  rss_members_.emplace_back(context->storage->InternString("mem.swap"));
+  rss_members_.emplace_back(context->storage->InternString("mem.rss.shmem"));
+  rss_members_.emplace_back(
+      context->storage->InternString("mem.rss.unknown"));  // Keep this last.
+}
+
+void RssStatTracker::ParseRssStat(int64_t ts, uint32_t pid, ConstBytes blob) {
+  protos::pbzero::RssStatFtraceEvent::Decoder rss(blob.data, blob.size);
+  const auto kRssStatUnknown = static_cast<uint32_t>(rss_members_.size()) - 1;
+  auto member = static_cast<uint32_t>(rss.member());
+  int64_t size = rss.size();
+  if (member >= rss_members_.size()) {
+    context_->storage->IncrementStats(stats::rss_stat_unknown_keys);
+    member = kRssStatUnknown;
+  }
+
+  if (size < 0) {
+    context_->storage->IncrementStats(stats::rss_stat_negative_size);
+    return;
+  }
+
+  base::Optional<UniqueTid> utid;
+  if (rss.has_mm_id()) {
+    PERFETTO_DCHECK(rss.has_curr());
+    utid = FindUtidForMmId(rss.mm_id(), rss.curr(), pid);
+  } else {
+    utid = context_->process_tracker->GetOrCreateThread(pid);
+  }
+
+  if (utid) {
+    context_->event_tracker->PushProcessCounterForThread(
+        ts, size, rss_members_[member], *utid);
+  } else {
+    context_->storage->IncrementStats(stats::rss_stat_unknown_thread_for_mm_id);
+  }
+}
+
+base::Optional<UniqueTid> RssStatTracker::FindUtidForMmId(int64_t mm_id,
+                                                          bool is_curr,
+                                                          uint32_t pid) {
+  auto it = mm_id_to_utid_.find(mm_id);
+  if (!is_curr) {
+    return it == mm_id_to_utid_.end() ? base::nullopt
+                                      : base::make_optional(it->second);
+  }
+
+  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+  if (it != mm_id_to_utid_.end() && it->second != utid) {
+    // Since both of these structs have the same mm hash and both say that
+    // the mm hash is for the current project, we can assume they belong to
+    // the same process so we can associate them together.
+    // TODO(lalitm): investigate if it's possible for mm_id to be reused
+    // between different processes if we have pid reuse and get unlucky. If
+    // so, we'll need to do some more careful tracking here.
+    context_->process_tracker->AssociateThreads(it->second, utid);
+  }
+  mm_id_to_utid_[mm_id] = utid;
+  return utid;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/rss_stat_tracker.h b/src/trace_processor/importers/ftrace/rss_stat_tracker.h
new file mode 100644
index 0000000..7d297fb
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/rss_stat_tracker.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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_IMPORTERS_FTRACE_RSS_STAT_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_RSS_STAT_TRACKER_H_
+
+#include <unordered_map>
+
+#include "perfetto/protozero/field.h"
+#include "src/trace_processor/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class RssStatTracker {
+ public:
+  using ConstBytes = protozero::ConstBytes;
+
+  explicit RssStatTracker(TraceProcessorContext*);
+
+  void ParseRssStat(int64_t ts, uint32_t pid, ConstBytes blob);
+
+ private:
+  base::Optional<UniqueTid> FindUtidForMmId(int64_t mm_id,
+                                            bool is_curr,
+                                            uint32_t pid);
+
+  std::unordered_map<int64_t, UniqueTid> mm_id_to_utid_;
+  std::vector<StringId> rss_members_;
+  TraceProcessorContext* const context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_RSS_STAT_TRACKER_H_
diff --git a/src/trace_processor/stats.h b/src/trace_processor/stats.h
index b64ad6b..99a831b 100644
--- a/src/trace_processor/stats.h
+++ b/src/trace_processor/stats.h
@@ -65,6 +65,7 @@
   F(proc_stat_unknown_counters,               kSingle,  kError,    kAnalysis), \
   F(rss_stat_unknown_keys,                    kSingle,  kError,    kAnalysis), \
   F(rss_stat_negative_size,                   kSingle,  kInfo,     kAnalysis), \
+  F(rss_stat_unknown_thread_for_mm_id,        kSingle,  kInfo,     kAnalysis), \
   F(sched_switch_out_of_order,                kSingle,  kError,    kAnalysis), \
   F(slice_out_of_order,                       kSingle,  kError,    kAnalysis), \
   F(stackprofile_invalid_string_id,           kSingle,  kError,    kTrace),    \
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index a011d7b..b9976da 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -4911,6 +4911,12 @@
            {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
             "size", 2, ProtoSchemaType::kInt64,
             TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "curr", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "mm_id", 4, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
        },
        kUnsetFtraceId,
        313,
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/kmem/rss_stat/format b/src/traced/probes/ftrace/test/data/synthetic/events/kmem/rss_stat/format
index c4713fe..4ab4b95 100644
--- a/src/traced/probes/ftrace/test/data/synthetic/events/kmem/rss_stat/format
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/kmem/rss_stat/format
@@ -1,12 +1,14 @@
 name: rss_stat
-ID: 243
+ID: 269
 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:int member;	offset:8;	size:4;	signed:1;
-	field:long size;	offset:16;	size:8;	signed:1;
+	field:unsigned int mm_id;	offset:8;	size:4;	signed:0;
+	field:unsigned int curr;	offset:12;	size:4;	signed:0;
+	field:int member;	offset:16;	size:4;	signed:1;
+	field:long size;	offset:24;	size:8;	signed:1;
 
-print fmt: "member=%d size=%ldB", REC->member, REC->size
+print fmt: "mm_id=%u curr=%d member=%d size=%ldB", REC->mm_id, REC->curr, REC->member, REC->size
diff --git a/test/synth_common.py b/test/synth_common.py
index 272220d..f950294 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -47,11 +47,15 @@
     ftrace.pid = tid
     return ftrace
 
-  def add_rss_stat(self, ts, tid, member, size):
+  def add_rss_stat(self, ts, tid, member, size, mm_id=None, curr=None):
     ftrace = self.__add_ftrace_event(ts, tid)
     rss_stat = ftrace.rss_stat
     rss_stat.member = member
     rss_stat.size = size
+    if mm_id is not None:
+      rss_stat.mm_id = mm_id
+    if curr is not None:
+      rss_stat.curr = curr
 
   def add_ion_event(self, ts, tid, heap_name, size):
     ftrace = self.__add_ftrace_event(ts, tid)
diff --git a/test/trace_processor/index b/test/trace_processor/index
index e72321d..5935290 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -55,6 +55,10 @@
 kernel_lmk.py lmk.sql lmk_kernel_lmk.out
 ../data/lmk_userspace.pb lmk.sql lmk_userspace_lmk.out
 
+# Rss stats
+rss_stat_mm_id.py rss_stat.sql rss_stat_mm_id.out
+rss_stat_legacy.py rss_stat.sql rss_stat_legacy.out
+
 # Memory counters
 ../data/memory_counters.pb args_string_filter_null.sql memory_counters_args_string_filter_null.out
 ../data/memory_counters.pb args_string_is_null.sql memory_counters_args_string_is_null.out
diff --git a/test/trace_processor/rss_stat.sql b/test/trace_processor/rss_stat.sql
new file mode 100644
index 0000000..44e4387
--- /dev/null
+++ b/test/trace_processor/rss_stat.sql
@@ -0,0 +1,5 @@
+SELECT c.ts, t.name, p.pid, p.name, c.value
+FROM counter c
+JOIN process_counter_track t ON c.track_id = t.id
+JOIN process p USING (upid)
+ORDER BY ts
diff --git a/test/trace_processor/rss_stat_legacy.out b/test/trace_processor/rss_stat_legacy.out
new file mode 100644
index 0000000..c821ed0
--- /dev/null
+++ b/test/trace_processor/rss_stat_legacy.out
@@ -0,0 +1,6 @@
+"ts","name","pid","name","value"
+90,"mem.rss.file",2,"kthreadd",9.000000
+91,"mem.rss.file",2,"kthreadd",900.000000
+99,"mem.rss.file",10,"process",10.000000
+100,"mem.rss.file",10,"process",1000.000000
+101,"mem.rss.file",2,"kthreadd",900.000000
diff --git a/test/trace_processor/rss_stat_legacy.py b/test/trace_processor/rss_stat_legacy.py
new file mode 100644
index 0000000..2fb682f
--- /dev/null
+++ b/test/trace_processor/rss_stat_legacy.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+# Copyright (C) 2019 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.
+
+# This synthetic trace tests legacy rss_stat events on old kernels which
+# do not have the mm_id change and thus we cannot accurately track
+# rss changes when one process changes another's mm struct.
+
+from os import sys, path
+
+sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_process_tree_packet(ts=1)
+trace.add_process(10, 0, "process")
+
+trace.add_ftrace_packet(0)
+
+# Create a new child process (treated internally as a thread) of kthreadd.
+trace.add_newtask(ts=50, tid=2, new_tid=3, new_comm="kthread_child", flags=0)
+
+# Add an event on tid 3 which affects its own rss.
+trace.add_rss_stat(ts=90, tid=3, member=0, size=9)
+
+# Add an event on tid 10 from tid 3. This emlates e.g. background reclaim
+# where kthreadd is cleaning up the mm struct of another process.
+trace.add_rss_stat(ts=91, tid=3, member=0, size=900)
+
+# Add an event for tid 3 from tid 10. This emulates e.g. direct reclaim
+# where a process reaches into another process' mm struct.
+trace.add_rss_stat(ts=99, tid=10, member=0, size=10)
+
+# Add an event on tid 10 which affects its own rss.
+trace.add_rss_stat(ts=100, tid=10, member=0, size=1000)
+
+# Add an event on tid 10 from tid 3.
+trace.add_rss_stat(ts=101, tid=3, member=0, size=900)
+
+print(trace.trace.SerializeToString())
diff --git a/test/trace_processor/rss_stat_mm_id.out b/test/trace_processor/rss_stat_mm_id.out
new file mode 100644
index 0000000..99dca66
--- /dev/null
+++ b/test/trace_processor/rss_stat_mm_id.out
@@ -0,0 +1,5 @@
+"ts","name","pid","name","value"
+90,"mem.rss.file",2,"kthreadd",9.000000
+99,"mem.rss.file",2,"kthreadd",10.000000
+100,"mem.rss.file",10,"process",1000.000000
+101,"mem.rss.file",10,"process",900.000000
diff --git a/test/trace_processor/rss_stat_mm_id.py b/test/trace_processor/rss_stat_mm_id.py
new file mode 100644
index 0000000..d2b5e5a
--- /dev/null
+++ b/test/trace_processor/rss_stat_mm_id.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+# Copyright (C) 2019 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.
+
+# This synthetic trace tests handling of the mm_id field in the rss_stat
+# event.
+
+from os import sys, path
+
+sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_process_tree_packet(ts=1)
+trace.add_process(10, 0, "process")
+
+trace.add_ftrace_packet(0)
+
+# Create a new child process (treated internally as a thread) of kthreadd.
+trace.add_newtask(ts=50, tid=2, new_tid=3, new_comm="kthread_child", flags=0)
+
+# Add an event on tid 3 which affects its own rss.
+trace.add_rss_stat(ts=90, tid=3, member=0, size=9, mm_id=4321, curr=True)
+
+# Try to add an event for tid 10. However, as we've not seen an event
+# with curr == True for tid == 10, this event will be dropped.
+trace.add_rss_stat(ts=91, tid=3, member=0, size=900, mm_id=1234, curr=False)
+
+# Add an event for tid 3 from tid 10. This emulates e.g. direct reclaim
+# where a process reaches into another process' mm struct.
+trace.add_rss_stat(ts=99, tid=10, member=0, size=10, mm_id=4321, curr=False)
+
+# Add an event on tid 10 which affects its own rss.
+trace.add_rss_stat(ts=100, tid=10, member=0, size=1000, mm_id=1234, curr=True)
+
+# Add an event on tid 10 from tid 3. This emlates e.g. background reclaim
+# where kthreadd is cleaning up the mm struct of another process.
+trace.add_rss_stat(ts=101, tid=3, member=0, size=900, mm_id=1234, curr=False)
+
+print(trace.trace.SerializeToString())