Treat multiple, contiguous hugepage regions as a single mapping.

A hugepage_text modified region of memory normally results in a
single mmap() entry reported in /proc/${pid}/maps, but when a
subsequent mlock()/munlock() happens within that region, the
mapping will be broken into multiple entries.

This attempts to deduce mappings by looking for virtually
contiguous mappings that are immediately followed by a
non-anonymous-hugepage (i.e. pgoff!=0) mapping afterwards, and
using that subsequent mapping to roll pgoff down to the range
of anonymous-backed (likely hugepage_text) mappings.

PiperOrigin-RevId: 185541172
diff --git a/quipper/huge_page_deducer.cc b/quipper/huge_page_deducer.cc
index d5ddc2d..d68d703 100644
--- a/quipper/huge_page_deducer.cc
+++ b/quipper/huge_page_deducer.cc
@@ -1,5 +1,7 @@
 #include "huge_page_deducer.h"
 
+#include <limits>
+
 #include "perf_data_utils.h"
 
 #include "base/logging.h"
@@ -17,6 +19,22 @@
   return event.filename() == kAnonFilename;
 }
 
+// IsContiguous returns true if mmap |a| is immediately followed by |b|
+// within a process' address space.
+bool IsContiguous(const MMapEvent& a, const MMapEvent& b) {
+  return a.pid() == b.pid() && (a.start() + a.len()) == b.start();
+}
+
+// IsEquivalentFile returns true iff |a| and |b| have the same name, or if
+// either of them are anonymous memory (and thus likely to be a --hugepage_text
+// version of the same file).
+bool IsEquivalentFile(const MMapEvent& a, const MMapEvent& b) {
+  // perf attributes neighboring anonymous mappings under the argv[0]
+  // filename rather than "//anon", so check filename equality, as well as
+  // anonymous.
+  return a.filename() == b.filename() || IsAnon(a) || IsAnon(b);
+}
+
 // Helper to correctly update a filename on a PerfEvent that contains an
 // MMapEvent.
 void SetMmapFilename(PerfEvent* event, const string& new_filename,
@@ -31,75 +49,217 @@
 }
 }  // namespace
 
-void DeduceHugePages(RepeatedPtrField<PerfEvent>* events) {
-  // |prev_event| is the last mmap_event in |events|, or |nullptr| if no
-  // mmap_events have been processed.
-  PerfEvent* prev_event = nullptr;
+namespace {
 
-  for (int i = 0; i < events->size(); ++i) {
+// MMapRange represents an index into a PerfEvents sequence that contains
+// a contiguous region of mmaps that have all of the same filename and pgoff.
+class MMapRange {
+ public:
+  // Default constructor is an invalid range.
+  MMapRange()
+      : first_(std::numeric_limits<int>::max()),
+        last_(std::numeric_limits<int>::min()) {}
+
+  // Construct a real range.
+  MMapRange(int first_index, int last_index)
+      : first_(first_index), last_(last_index) {}
+
+  uint64 Len(const RepeatedPtrField<PerfEvent>& events) const {
+    auto& first = events.Get(first_).mmap_event();
+    auto& last = events.Get(last_).mmap_event();
+    return last.start() - first.start() + last.len();
+  }
+
+  int FirstIndex() const { return first_; }
+  int LastIndex() const { return last_; }
+  bool IsValid() const { return first_ <= last_; }
+
+  const MMapEvent& FirstMmap(const RepeatedPtrField<PerfEvent>& events) const {
+    return events.Get(first_).mmap_event();
+  }
+
+  const MMapEvent& LastMmap(const RepeatedPtrField<PerfEvent>& events) const {
+    return events.Get(last_).mmap_event();
+  }
+
+ private:
+  int first_;
+  int last_;
+};
+
+std::ostream& operator<<(std::ostream& os, const MMapRange& r) {
+  os << "[" << r.FirstIndex() << "," << r.LastIndex() << "]";
+  return os;
+}
+
+// MMapRange version of IsContiguous(MMapEvent, MMapEvent).
+bool IsContiguous(const RepeatedPtrField<PerfEvent>& events, const MMapRange& a,
+                  const MMapRange& b) {
+  return IsContiguous(a.LastMmap(events), b.FirstMmap(events));
+}
+
+// MMapRange version of IsIsEquivalent(MMapEvent, MMapEvent).
+bool IsEquivalentFile(const RepeatedPtrField<PerfEvent>& events,
+                      const MMapRange& a, const MMapRange& b) {
+  // Because a range has the same file for all mmaps within it, assume that
+  // checking any mmap in |a| with any in |b| is sufficient.
+  return IsEquivalentFile(a.LastMmap(events), b.FirstMmap(events));
+}
+
+// FindRange returns a MMapRange of contiguous MmapEvents that:
+// - either:
+//   - contains 1 or more MmapEvents with pgoff == 0
+//   - is a single MmapEvent with pgoff != 0
+// - and:
+//   - has the same filename for all entries
+// Otherwise, if none can be found, an invalid range will be returned.
+MMapRange FindRange(const RepeatedPtrField<PerfEvent>& events, int start) {
+  const MMapEvent* prev_mmap = nullptr;
+  MMapRange range;
+  for (int i = start; i < events.size(); i++) {
+    const PerfEvent& event = events.Get(i);
+    // Skip irrelevant events
+    if (!event.has_mmap_event()) {
+      continue;
+    }
+    // Skip dynamic mmap() events. Hugepage deduction only works on mmaps as
+    // synthesized by perf from /proc/${pid}/maps, which have timestamp==0.
+    // Support for deducing hugepages from a sequence of mmap()/mremap() calls
+    // would require additional deduction logic.
+    if (event.timestamp() != 0) {
+      continue;
+    }
+    const MMapEvent& mmap = events.Get(i).mmap_event();
+    if (prev_mmap == nullptr) {
+      range = MMapRange(i, i);
+      prev_mmap = &mmap;
+    }
+    // Ranges match exactly: //anon,//anon, or file,file; If they use different
+    // names, then deduction needs to consider them independently.
+    if (prev_mmap->filename() != mmap.filename()) {
+      break;
+    }
+    // If they're not virtually contiguous, they're not a single range.
+    if (start != i && !IsContiguous(*prev_mmap, mmap)) {
+      break;
+    }
+    // If this segment has a page offset, assume that it is *not* hugepage
+    // backed, and thus does not need separate deduction.
+    if (mmap.pgoff() != 0) {
+      break;
+    }
+    CHECK(mmap.pgoff() == 0 || !IsAnon(mmap))
+        << "Anonymous pages can't have pgoff set";
+    prev_mmap = &mmap;
+    range = MMapRange(range.FirstIndex(), i);
+  }
+  // Range has:
+  // - single file
+  // - virtually contiguous
+  // - either: is multiple mappings *or* has pgoff=0
+  return range;
+}
+
+// FindNextRange will return the next range after the given |prev_range| if
+// there is one; otherwise it will return an invalid range.
+MMapRange FindNextRange(const RepeatedPtrField<PerfEvent>& events,
+                        const MMapRange& prev_range) {
+  MMapRange ret;
+  if (prev_range.IsValid() && prev_range.LastIndex() < events.size()) {
+    ret = FindRange(events, prev_range.LastIndex() + 1);
+  }
+  return ret;
+}
+
+// UpdateRangeFromNext will set the filename / pgoff of all mmaps within |range|
+// to be pgoff-contiguous with |next_range|, and match its file information.
+void UpdateRangeFromNext(const MMapRange& range, const MMapRange& next_range,
+                         RepeatedPtrField<PerfEvent>* events) {
+  CHECK(range.LastIndex() < events->size());
+  CHECK(next_range.LastIndex() < events->size());
+  const MMapEvent& src = next_range.FirstMmap(*events);
+  const uint64 start_pgoff = src.pgoff() - range.Len(*events);
+  uint64 pgoff = start_pgoff;
+  for (int i = range.FirstIndex(); i <= range.LastIndex(); i++) {
+    if (!events->Get(i).has_mmap_event()) {
+      continue;
+    }
     PerfEvent* event = events->Mutable(i);
-    if (!event->has_mmap_event()) {
-      continue;
-    }
-
-    if (prev_event == nullptr) {
-      prev_event = event;
-      continue;
-    }
-
     MMapEvent* mmap = event->mutable_mmap_event();
-    MMapEvent* prev_mmap = prev_event->mutable_mmap_event();
 
-    const bool pid_match = prev_mmap->pid() == mmap->pid();
+    // Replace "//anon" with a regular name if possible.
+    if (IsAnon(*mmap)) {
+      CHECK_EQ(mmap->pgoff(), 0) << "//anon should have offset=0 for mmap"
+                                 << event->ShortDebugString();
+      SetMmapFilename(event, src.filename(), src.filename_md5_prefix());
+    }
 
-    // perf attributes neighboring anonymous mappings under the nearby
-    // filename rather than "//anon".
-    const bool file_match = prev_mmap->filename() == mmap->filename() ||
-                            IsAnon(*prev_mmap) || IsAnon(*mmap);
-    const bool address_contiguous =
-        pid_match && (prev_mmap->start() + prev_mmap->len() == mmap->start());
+    if (mmap->pgoff() == 0) {
+      mmap->set_pgoff(pgoff);
+      if (src.has_maj()) {
+        mmap->set_maj(src.maj());
+      }
+      if (src.has_min()) {
+        mmap->set_min(src.min());
+      }
+      if (src.has_ino()) {
+        mmap->set_ino(src.ino());
+      }
+      if (src.has_ino_generation()) {
+        mmap->set_ino_generation(src.ino_generation());
+      }
+    }
+    pgoff += mmap->len();
+  }
+  CHECK_EQ(pgoff, start_pgoff + range.Len(*events));
+}
+}  // namespace
 
-    if (!(file_match && address_contiguous)) {
-      prev_event = event;
+void DeduceHugePages(RepeatedPtrField<PerfEvent>* events) {
+  // |prev_range|, if IsValid(), represents the preview mmap range seen (and
+  // already processed / updated).
+  MMapRange prev_range;
+  // |range| contains the currently-being-processed mmap range, which will have
+  // its hugepages ranges deduced.
+  MMapRange range = FindRange(*events, 0);
+  // |next_range| contains the next range to process, possibily containing
+  // pgoff != 0 or !IsAnon(filename) from which the current range can be
+  // updated.
+  MMapRange next_range = FindNextRange(*events, range);
+
+  for (; range.IsValid(); prev_range = range, range = next_range,
+                          next_range = FindNextRange(*events, range)) {
+    const bool have_next =
+        (next_range.IsValid() && IsContiguous(*events, range, next_range) &&
+         IsEquivalentFile(*events, range, next_range));
+
+    // If there's no mmap after this, then we assume that this is *not* viable
+    // a hugepage_text mapping. This is true unless we're really unlucky. If:
+    // - the binary is mapped such that the limit is hugepage aligned
+    //   (presumably 4Ki/2Mi chance == p=0.03125)
+    // - and the entire binaryis hugepage_text mapped
+    if (!have_next) {
       continue;
     }
 
-    const bool mmap_hugepage =
-        mmap->start() % kHugepageSize == 0 && mmap->len() % kHugepageSize == 0;
-    // |pgoff| == 0 is suspect, as perf reports this for anonymous mappings.
-    const bool before_hugepage = prev_mmap->pgoff() == 0 &&
-                                 prev_mmap->start() % kHugepageSize == 0 &&
-                                 prev_mmap->len() % kHugepageSize == 0;
+    const bool have_prev =
+        (prev_range.IsValid() && IsContiguous(*events, prev_range, range) &&
+         IsEquivalentFile(*events, prev_range, range) &&
+         IsEquivalentFile(*events, prev_range, next_range));
 
-    if (before_hugepage) {
-      if (mmap->pgoff() > 0 && mmap->pgoff() >= prev_mmap->len()) {
-        // Extend |pgoff| downwards, as the existing mmap event may be anonymous
-        // due to |m->pgoff()| == 0.
-        prev_mmap->set_pgoff(mmap->pgoff() - prev_mmap->len());
-      }
-
-      // Replace "//anon" with a regular name if possible.
-      if (IsAnon(*prev_mmap)) {
-        SetMmapFilename(prev_event, mmap->filename(),
-                        mmap->filename_md5_prefix());
-      }
+    uint64 start_pgoff = 0;
+    if (have_prev) {
+      const auto& prev = prev_range.LastMmap(*events);
+      start_pgoff = prev.pgoff() + prev.len();
     }
-
-    if (mmap_hugepage) {
-      if (mmap->pgoff() == 0) {
-        // Extend |pgoff| upwards, as the existing mmap event may be anonymous
-        // due to |m->pgoff()| == 0.
-        mmap->set_pgoff(prev_mmap->pgoff() + prev_mmap->len());
-      }
-
-      // Replace "//anon" with a regular name if possible.
-      if (IsAnon(*mmap)) {
-        SetMmapFilename(event, prev_mmap->filename(),
-                        prev_mmap->filename_md5_prefix());
-      }
+    const auto& next = next_range.FirstMmap(*events);
+    // prev.pgoff should be valid now, so let's double-check that
+    // if next has a non-zero pgoff, that {prev,curr,next} will have
+    // contiguous pgoff once updated.
+    if (next.pgoff() >= range.Len(*events) &&
+        (next.pgoff() - range.Len(*events)) == start_pgoff) {
+      UpdateRangeFromNext(range, next_range, events);
     }
-
-    prev_event = event;
   }
 }
 
@@ -131,14 +291,14 @@
 
     MMapEvent* prev_mmap = new_events.Mutable(prev)->mutable_mmap_event();
 
-    const bool pid_match = prev_mmap->pid() == mmap.pid();
+    // Don't use IsEquivalentFile(); we don't want to combine //anon with
+    // files if DeduceHugepages didn't already fix up the mappings.
     const bool file_match = prev_mmap->filename() == mmap.filename();
-    const bool address_contiguous =
-        pid_match && (prev_mmap->start() + prev_mmap->len() == mmap.start());
     const bool pgoff_contiguous =
         file_match && (prev_mmap->pgoff() + prev_mmap->len() == mmap.pgoff());
 
-    const bool combine_mappings = address_contiguous && pgoff_contiguous;
+    const bool combine_mappings =
+        IsContiguous(*prev_mmap, mmap) && pgoff_contiguous;
     if (!combine_mappings) {
       new_events.Add()->Swap(event);
       prev++;
diff --git a/quipper/huge_page_deducer_test.cc b/quipper/huge_page_deducer_test.cc
index 506f140..3d92f66 100644
--- a/quipper/huge_page_deducer_test.cc
+++ b/quipper/huge_page_deducer_test.cc
@@ -1,12 +1,30 @@
 #include "huge_page_deducer.h"  
 
+#include "base/logging.h"
+#include "compat/string.h"
 #include "compat/test.h"
 
-using PerfEvent = quipper::PerfDataProto::PerfEvent;
-using MMapEvent = quipper::PerfDataProto::MMapEvent;
 
 namespace quipper {
 namespace {
+using PerfEvent = PerfDataProto::PerfEvent;
+using MMapEvent = PerfDataProto::MMapEvent;
+using ::testing::EqualsProto;
+using ::testing::Pointwise;
+using ::testing::proto::Partially;
+
+// AddMmap is a helper function to create simple MMapEvents, with which
+// testcases can encode "maps" entries similar to /proc/self/maps in a tabular
+// one-line-per-entry.
+void AddMmap(uint32_t pid, uint64_t mmap_start, uint64_t length, uint64_t pgoff,
+             const string& file, RepeatedPtrField<PerfEvent>* events) {
+  MMapEvent* ev = events->Add()->mutable_mmap_event();
+  ev->set_pid(pid);
+  ev->set_start(mmap_start);
+  ev->set_len(length);
+  ev->set_pgoff(pgoff);
+  ev->set_filename(file);
+}
 
 TEST(HugePageDeducer, HugePagesMappings) {
   RepeatedPtrField<PerfEvent> events;
@@ -62,7 +80,19 @@
   DeduceHugePages(&events);
   CombineMappings(&events);
 
-  ASSERT_EQ(3, events.size());
+  ASSERT_GE(events.size(), 3);
+  EXPECT_EQ(events.size(), 3);
+
+  EXPECT_THAT(events,
+              Pointwise(Partially(EqualsProto()),
+                        {
+                            "mmap_event: { start: 0x40000000 len:0x18000 "
+                            "pgoff: 0 filename: '/usr/lib/libfoo.so'}",
+                            "mmap_event: { start: 0x40018000 len:0x5de8000 "
+                            "pgoff: 0 filename: '/opt/google/chrome/chrome'}",
+                            "mmap_event: { start: 0x45e00000 len:0x5e00000 "
+                            "pgoff: 0 filename: '/opt/google/chrome/chrome'}",
+                        }));
 
   EXPECT_EQ("/usr/lib/libfoo.so", events[0].mmap_event().filename());
   EXPECT_EQ(0x40000000, events[0].mmap_event().start());
@@ -81,6 +111,369 @@
   EXPECT_EQ(0x0, events[2].mmap_event().pgoff());
 }
 
+enum HugepageTextStyle {
+  kAnonHugepageText,
+  kNoHugepageText,
+};
+
+class HugepageTextStyleDependent
+    : public ::testing::TestWithParam<HugepageTextStyle> {
+ protected:
+  void AddHugepageTextMmap(uint32_t pid, uint64_t mmap_start, uint64_t length,
+                           uint64_t pgoff, string file,
+                           RepeatedPtrField<PerfEvent>* events) {
+    // Various hugepage implementations and perf versions result in various
+    // quirks in how hugepages are reported.
+
+    switch (GetParam()) {
+      case kNoHugepageText:
+        // Do nothing; the maps are complete and file-backed
+        break;
+      case kAnonHugepageText:
+        // exec is remapped into anonymous memory, which perf reports as
+        // '//anon'. Anonymous sections have no pgoff.
+        file = "//anon";
+        pgoff = 0;
+        break;
+      default:
+        CHECK(false) << "Unimplemented";
+    }
+    AddMmap(pid, mmap_start, length, pgoff, file, events);
+  }
+};
+
+TEST_P(HugepageTextStyleDependent, OnlyOneMappingThatIsHuge) {
+  RepeatedPtrField<PerfEvent> events;
+  AddHugepageTextMmap(1, 0x100200000, 0x200000, 0, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  // Don't check filename='file'; if it's backed by anonymous memory, it isn't
+  // possible for quipper to deduce the filename without other mmaps immediately
+  // adjacent.
+  EXPECT_THAT(
+      events,
+      Pointwise(Partially(EqualsProto()),
+                {"mmap_event: { start: 0x100200000 len: 0x200000 pgoff: 0}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, OnlyOneMappingUnaligned) {
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(2, 0x200201000, 0x200000, 0, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x200201000 "
+                                 "len:0x200000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, FirstPageIsHugeWithSmallTail) {
+  RepeatedPtrField<PerfEvent> events;
+  AddHugepageTextMmap(3, 0x300400000, 0x400000, 0, "file", &events);
+  AddMmap(3, 0x300800000, 0x001000, 0x400000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x300400000 "
+                                 "len:0x401000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, DISABLED_FirstPageIsSmallWithHugeTail) {
+  // This test is disabled because DeduceHugePage requires a non-zero pgoff
+  // *after* a hugepage_text section in order to correctly deduce it, so it
+  // is unable to deduce these cases.
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(4, 0x4003ff000, 0x001000, 0, "file", &events);
+  AddHugepageTextMmap(4, 0x400400000, 0x200000, 0x001000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x4003ff000 "
+                                 "len:0x201000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, HugePageBetweenTwoSmallSections) {
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(5, 0x5003ff000, 0x001000, 0, "file", &events);
+  AddHugepageTextMmap(5, 0x500400000, 0x200000, 0x001000, "file", &events);
+  AddMmap(5, 0x500600000, 0x001000, 0x201000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x5003ff000 "
+                                 "len:0x202000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, HugePageSplitByEarlyMlockBetweenTwoSmall) {
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(6, 0x6003ff000, 0x001000, 0, "file", &events);
+  AddHugepageTextMmap(6, 0x600400000, 0x3f8000, 0x001000, "file", &events);
+  AddHugepageTextMmap(6, 0x6007f8000, 0x008000, 0x3f9000, "file", &events);
+  AddMmap(6, 0x600800000, 0x001000, 0x401000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x6003ff000 "
+                                 "len:0x402000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, HugePageSplitByLateMlockBetweenTwoSmall) {
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(7, 0x7003ff000, 0x001000, 0, "file", &events);
+  AddHugepageTextMmap(7, 0x700400000, 0x008000, 0x001000, "file", &events);
+  AddHugepageTextMmap(7, 0x700408000, 0x3f8000, 0x009000, "file", &events);
+  AddMmap(7, 0x700800000, 0x001000, 0x401000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x7003ff000 "
+                                 "len:0x402000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, HugePageSplitEvenlyByMlockBetweenTwoSmall) {
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(8, 0x8003ff000, 0x001000, 0, "file", &events);
+  AddHugepageTextMmap(8, 0x800400000, 0x0f8000, 0x001000, "file", &events);
+  AddHugepageTextMmap(8, 0x8004f8000, 0x008000, 0x0f9000, "file", &events);
+  AddHugepageTextMmap(8, 0x800500000, 0x100000, 0x101000, "file", &events);
+  AddMmap(8, 0x800600000, 0x001000, 0x201000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x8003ff000 "
+                                 "len:0x202000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, MultipleContiguousHugepages) {
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(9, 0x9003ff000, 0x001000, 0, "file", &events);
+  AddHugepageTextMmap(9, 0x900400000, 0x200000, 0x001000, "file", &events);
+  AddHugepageTextMmap(9, 0x900600000, 0x200000, 0x201000, "file", &events);
+  AddMmap(9, 0x900800000, 0x001000, 0x401000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0x9003ff000 "
+                                 "len:0x402000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, MultipleContiguousMlockSplitHugepages) {
+  // Think:
+  // - hugepage_text 4MiB range
+  // - mlock alternating 512-KiB chunks
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(10, 0xa003ff000, 0x001000, 0, "file", &events);
+  AddHugepageTextMmap(10, 0xa00400000, 0x080000, 0x001000, "file", &events);
+  AddHugepageTextMmap(10, 0xa00480000, 0x080000, 0x081000, "file", &events);
+  AddHugepageTextMmap(10, 0xa00500000, 0x080000, 0x101000, "file", &events);
+  AddHugepageTextMmap(10, 0xa00580000, 0x080000, 0x181000, "file", &events);
+  AddHugepageTextMmap(10, 0xa00600000, 0x080000, 0x201000, "file", &events);
+  AddHugepageTextMmap(10, 0xa00680000, 0x080000, 0x281000, "file", &events);
+  AddHugepageTextMmap(10, 0xa00700000, 0x080000, 0x301000, "file", &events);
+  AddHugepageTextMmap(10, 0xa00780000, 0x080000, 0x381000, "file", &events);
+  AddMmap(10, 0xa00800000, 0x001000, 0x401000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0xa003ff000 "
+                                 "len:0x402000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, MultipleWithUnalignedInitialHugePage) {
+  // Base on real program
+  RepeatedPtrField<PerfEvent> events;
+
+  AddHugepageTextMmap(11, 0x85d32e000, 0x6d2000, 0x0, "file", &events);
+  AddHugepageTextMmap(11, 0x85da00000, 0x6a00000, 0x6d2000, "file", &events);
+  AddMmap(11, 0x864400000, 0x200000, 0x70d2000, "file", &events);
+  AddHugepageTextMmap(11, 0x864600000, 0x200000, 0x72d2000, "file", &events);
+  AddMmap(11, 0x864800000, 0x600000, 0x74d2000, "file", &events);
+  AddHugepageTextMmap(11, 0x864e00000, 0x200000, 0x7ad2000, "file", &events);
+  AddMmap(11, 0x865000000, 0x4a000, 0x7cd2000, "file", &events);
+  AddMmap(11, 0x86504a000, 0x1000, 0x7d1c000, "file", &events);
+  AddMmap(11, 0xa3d368000, 0x3a96000, 0x0, "file2", &events);
+  AddMmap(11, 0xa467cc000, 0x2000, 0x0, "file3", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {
+                                    "mmap_event: { start: 0x85d32e000 "
+                                    "len:0x7d1d000 pgoff: 0 filename: 'file'}",
+                                    "mmap_event: { start: 0xa3d368000 "
+                                    "len:0x3a96000 pgoff: 0 filename: 'file2'}",
+                                    "mmap_event: { start: 0xa467cc000 "
+                                    "len:0x2000,  pgoff: 0 filename: 'file3'}",
+                                }));
+}
+
+TEST_P(HugepageTextStyleDependent, MultipleWithUnalignedInitialHugePage2) {
+  // Base on real program
+  RepeatedPtrField<PerfEvent> events;
+  AddHugepageTextMmap(12, 0xbcff6000, 0x200000, 0x00000000, "file", &events);
+  AddMmap(12, 0xbd1f6000, 0x300a000, 0x200000, "file", &events);
+  AddHugepageTextMmap(12, 0xc0200000, 0x2b374000, 0x320a000, "file", &events);
+  AddHugepageTextMmap(12, 0xeb574000, 0x514000, 0x2e57e000, "file", &events);
+  AddHugepageTextMmap(12, 0xeba88000, 0x1d78000, 0x2ea92000, "file", &events);
+  AddMmap(12, 0xed800000, 0x1200000, 0x3080a000, "file", &events);
+  AddHugepageTextMmap(12, 0xeea00000, 0x200000, 0x31a0a000, "file", &events);
+  AddMmap(12, 0xeec00000, 0x2800000, 0x31c0a000, "file", &events);
+  AddHugepageTextMmap(12, 0xf1400000, 0x200000, 0x3440a000, "file", &events);
+  AddMmap(12, 0xf1600000, 0x89f000, 0x3460a000, "file", &events);
+  AddMmap(12, 0xf1e9f000, 0x1000, 0x34ea9000, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { start: 0xbcff6000 "
+                                 "len:0x34eaa000 pgoff: 0 filename: 'file'}"}));
+}
+
+TEST_P(HugepageTextStyleDependent, NoMmaps) {
+  RepeatedPtrField<PerfEvent> events;
+  events.Add();
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(EqualsProto(), std::vector<PerfEvent>(1)));
+}
+TEST_P(HugepageTextStyleDependent, MultipleNonMmaps) {
+  RepeatedPtrField<PerfEvent> events;
+  events.Add();
+  events.Add();
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(EqualsProto(), std::vector<PerfEvent>(2)));
+}
+TEST_P(HugepageTextStyleDependent, NonMmapFirstMmap) {
+  RepeatedPtrField<PerfEvent> events;
+  events.Add();
+  AddHugepageTextMmap(12, 0, 0x200000, 0, "file", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"", "mmap_event: { pgoff: 0 }"}));
+}
+TEST_P(HugepageTextStyleDependent, NonMmapAfterLastMmap) {
+  RepeatedPtrField<PerfEvent> events;
+  AddHugepageTextMmap(12, 0, 0x200000, 0, "file", &events);
+  events.Add();
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events, Pointwise(Partially(EqualsProto()),
+                                {"mmap_event: { pgoff: 0 }", ""}));
+}
+
+INSTANTIATE_TEST_CASE_P(NoHugepageText, HugepageTextStyleDependent,
+                        ::testing::Values(kNoHugepageText));
+INSTANTIATE_TEST_CASE_P(AnonHugepageText, HugepageTextStyleDependent,
+                        ::testing::Values(kAnonHugepageText));
+
+TEST(HugePageDeducer, DoesNotChangeVirtuallyContiguousPgoffNonContiguous) {
+  // We've seen programs with strange memory layouts having virtually contiguous
+  // memory backed by non-contiguous bits of a file.
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(758463, 0x2f278000, 0x20000, 0, "lib0.so", &events);
+  AddMmap(758463, 0x2f29d000, 0x2000, 0, "shm", &events);
+  AddMmap(758463, 0x2f2a2000, 0xa000, 0, "lib1.so", &events);
+  AddMmap(758463, 0x3d400000, 0x9ee000, 0, "lib2.so", &events);
+  AddMmap(758463, 0x3e000000, 0x16000, 0, "lib3.so", &events);
+  AddMmap(758463, 0x3e400000, 0x270000, 0x1a00000, "shm", &events);
+  AddMmap(758463, 0x3e670000, 0x10000, 0x1aaac000, "shm", &events);
+  AddMmap(758463, 0x3e680000, 0x10000, 0x1b410000, "shm", &events);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(events,
+              Pointwise(Partially(EqualsProto()),
+                        {
+                            "mmap_event: { pgoff: 0 filename: 'lib0.so' }",
+                            "mmap_event: { pgoff: 0 filename: 'shm' }",
+                            "mmap_event: { pgoff: 0 filename: 'lib1.so' }",
+                            "mmap_event: { pgoff: 0 filename: 'lib2.so' }",
+                            "mmap_event: { pgoff: 0 filename: 'lib3.so' }",
+                            "mmap_event: { pgoff: 0x1a00000 filename: 'shm' }",
+                            "mmap_event: { pgoff: 0x1aaac000 filename: 'shm' }",
+                            "mmap_event: { pgoff: 0x1b410000 filename: 'shm' }",
+                        }));
+}
+
+TEST(HugePageDeducer, IgnoresDynamicMmaps) {
+  // Now, let's watch a binary hugepage_text itself.
+  RepeatedPtrField<PerfEvent> events;
+  AddMmap(6531, 0x560d76b25000, 0x24ce000, 0, "main", &events);
+  events.rbegin()->set_timestamp(700413232676401);
+  AddMmap(6531, 0x7f686a1ec000, 0x24000, 0, "ld.so", &events);
+  events.rbegin()->set_timestamp(700413232691935);
+  AddMmap(6531, 0x7ffea5dc8000, 0x2000, 0, "[vdso]", &events);
+  events.rbegin()->set_timestamp(700413232701418);
+  AddMmap(6531, 0x7f686a1e3000, 0x5000, 0, "lib1.so", &events);
+  events.rbegin()->set_timestamp(700413232824216);
+  AddMmap(6531, 0x7f686a1a8000, 0x3a000, 0, "lib2.so", &events);
+  events.rbegin()->set_timestamp(700413232854520);
+  AddMmap(6531, 0x7f6869ea7000, 0x5000, 0, "lib3.so", &events);
+  events.rbegin()->set_timestamp(700413248827794);
+  AddMmap(6531, 0x7f6867e00000, 0x200000, 0, "/anon_hugepage (deleted)",
+          &events);
+  events.rbegin()->set_timestamp(700413295816043);
+  AddMmap(6531, 0x7f6867c00000, 0x200000, 0, "/anon_hugepage (deleted)",
+          &events);
+  events.rbegin()->set_timestamp(700413305947499);
+  AddMmap(6531, 0x7f68663f8000, 0x1e00000, 0x7f68663f8000, "//anon", &events);
+  events.rbegin()->set_timestamp(700413306012797);
+  AddMmap(6531, 0x7f6866525000, 0x1a00000, 0x7f6866525000, "//anon", &events);
+  events.rbegin()->set_timestamp(700413312132909);
+
+  DeduceHugePages(&events);
+  CombineMappings(&events);
+
+  EXPECT_THAT(
+      events,
+      Pointwise(
+          Partially(EqualsProto()),
+          {
+              "mmap_event: { pgoff: 0 filename: 'main' }",
+              "mmap_event: { pgoff: 0 filename: 'ld.so' }",
+              "mmap_event: { pgoff: 0 filename: '[vdso]' }",
+              "mmap_event: { pgoff: 0 filename: 'lib1.so' }",
+              "mmap_event: { pgoff: 0 filename: 'lib2.so' }",
+              "mmap_event: { pgoff: 0 filename: 'lib3.so' }",
+              "mmap_event: { pgoff: 0 filename: '/anon_hugepage (deleted)' }",
+              "mmap_event: { pgoff: 0 filename: '/anon_hugepage (deleted)' }",
+              "mmap_event: { pgoff: 0x7f68663f8000 filename: '//anon' }",
+              "mmap_event: { pgoff: 0x7f6866525000 filename: '//anon' }",
+          }));
+}
+
 TEST(HugePageDeducer, Regression62446346) {
   RepeatedPtrField<PerfEvent> events;
 
diff --git a/quipper/perf_parser.cc b/quipper/perf_parser.cc
index dee7f41..49aae70 100644
--- a/quipper/perf_parser.cc
+++ b/quipper/perf_parser.cc
@@ -71,7 +71,7 @@
 
   // Find huge page mappings.
   if (options_.deduce_huge_page_mappings) {
-    DeduceHugePages(reader_->mutable_events());
+      DeduceHugePages(reader_->mutable_events());
   }
 
   // Combine split mappings.  Because the remapping process makes addresses
diff --git a/quipper/perf_parser_test.cc b/quipper/perf_parser_test.cc
index 196663f..37ed0dc 100644
--- a/quipper/perf_parser_test.cc
+++ b/quipper/perf_parser_test.cc
@@ -2022,18 +2022,27 @@
   PerfParser parser(&reader, options);
   EXPECT_TRUE(parser.ParseRawEvents());
 
-  EXPECT_EQ(2, parser.stats().num_mmap_events);
+  EXPECT_EQ(3, parser.stats().num_mmap_events);
   EXPECT_EQ(0, parser.stats().num_sample_events);
   EXPECT_EQ(0, parser.stats().num_sample_events_mapped);
 
-  // The first two mappings should have been combined.  The third should not.
+  // The first two mappings should not combine, since we cannot know if the
+  // middle one follows the first, or preceeds the last in the binary.
   PerfDataProto expected;
   {
     auto *ev = expected.add_events();
     ev->mutable_header()->set_type(PERF_RECORD_MMAP);
     ev->mutable_mmap_event()->set_filename("file");
     ev->mutable_mmap_event()->set_start(0x7f489000);
-    ev->mutable_mmap_event()->set_len(0xd77000 + 0x200000);
+    ev->mutable_mmap_event()->set_len(0xd77000);
+    ev->mutable_mmap_event()->set_pgoff(0x0);
+  }
+  {
+    auto *ev = expected.add_events();
+    ev->mutable_header()->set_type(PERF_RECORD_MMAP);
+    ev->mutable_mmap_event()->set_filename("file");
+    ev->mutable_mmap_event()->set_start(0x80200000);
+    ev->mutable_mmap_event()->set_len(0x200000);
     ev->mutable_mmap_event()->set_pgoff(0x0);
   }
   {
@@ -2049,6 +2058,6 @@
   CopyActualEvents(parser.parsed_events(), &actual);
 
   EXPECT_TRUE(PartiallyEqualsProto(actual, expected));
-  EXPECT_EQ(2, parser.parsed_events().size());
+  EXPECT_EQ(3, parser.parsed_events().size());
 }
 }  // namespace quipper
diff --git a/quipper/testdata/perf.data-4.13.io.out.pb_data b/quipper/testdata/perf.data-4.13.io.out.pb_data
index f1b59d6..fa01d6e 100644
--- a/quipper/testdata/perf.data-4.13.io.out.pb_data
+++ b/quipper/testdata/perf.data-4.13.io.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data-4.13.io.out.pb_text b/quipper/testdata/perf.data-4.13.io.out.pb_text
index d859269..1bf4f16 100644
--- a/quipper/testdata/perf.data-4.13.io.out.pb_text
+++ b/quipper/testdata/perf.data-4.13.io.out.pb_text
@@ -446001,43 +446001,14 @@
   header {
     type: 10
     misc: 2
-    size: 104
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 112
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -720628,7 +720599,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 3022
-  num_mmap_events: 16381
+  num_mmap_events: 16380
   num_fork_events: 4890
   num_exit_events: 29
   num_sample_events_mapped: 3022
diff --git a/quipper/testdata/perf.data-4.13.parse.out.pb_data b/quipper/testdata/perf.data-4.13.parse.out.pb_data
index f1b59d6..fa01d6e 100644
--- a/quipper/testdata/perf.data-4.13.parse.out.pb_data
+++ b/quipper/testdata/perf.data-4.13.parse.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data-4.13.parse.out.pb_text b/quipper/testdata/perf.data-4.13.parse.out.pb_text
index d859269..1bf4f16 100644
--- a/quipper/testdata/perf.data-4.13.parse.out.pb_text
+++ b/quipper/testdata/perf.data-4.13.parse.out.pb_text
@@ -446001,43 +446001,14 @@
   header {
     type: 10
     misc: 2
-    size: 104
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 112
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -720628,7 +720599,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 3022
-  num_mmap_events: 16381
+  num_mmap_events: 16380
   num_fork_events: 4890
   num_exit_events: 29
   num_sample_events_mapped: 3022
diff --git a/quipper/testdata/perf.data-4.13.parse.remap.out.pb_data b/quipper/testdata/perf.data-4.13.parse.remap.out.pb_data
index 265915f..b14896b 100644
--- a/quipper/testdata/perf.data-4.13.parse.remap.out.pb_data
+++ b/quipper/testdata/perf.data-4.13.parse.remap.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data-4.13.parse.remap.out.pb_text b/quipper/testdata/perf.data-4.13.parse.remap.out.pb_text
index 7c2a355..9a0dd5b 100644
--- a/quipper/testdata/perf.data-4.13.parse.remap.out.pb_text
+++ b/quipper/testdata/perf.data-4.13.parse.remap.out.pb_text
@@ -446001,43 +446001,14 @@
   header {
     type: 10
     misc: 2
-    size: 104
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 1362210816
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 112
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 1395765248
-    len: 196608
-    pgoff: 33554432
+    start: 1362210816
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -720628,7 +720599,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 3022
-  num_mmap_events: 16381
+  num_mmap_events: 16380
   num_fork_events: 4890
   num_exit_events: 29
   num_sample_events_mapped: 3022
diff --git a/quipper/testdata/perf.data-4.13.pb_text b/quipper/testdata/perf.data-4.13.pb_text
index 8e70bbb..a4f7092 100644
--- a/quipper/testdata/perf.data-4.13.pb_text
+++ b/quipper/testdata/perf.data-4.13.pb_text
@@ -446001,43 +446001,14 @@
   header {
     type: 10
     misc: 2
-    size: 104
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 112
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -720628,7 +720599,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 3022
-  num_mmap_events: 16381
+  num_mmap_events: 16380
   num_fork_events: 4890
   num_exit_events: 29
   num_sample_events_mapped: 3022
diff --git a/quipper/testdata/perf.data-4.13.pr.out.pb_data b/quipper/testdata/perf.data-4.13.pr.out.pb_data
index f1b59d6..fa01d6e 100644
--- a/quipper/testdata/perf.data-4.13.pr.out.pb_data
+++ b/quipper/testdata/perf.data-4.13.pr.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data-4.13.pr.out.pb_text b/quipper/testdata/perf.data-4.13.pr.out.pb_text
index d859269..1bf4f16 100644
--- a/quipper/testdata/perf.data-4.13.pr.out.pb_text
+++ b/quipper/testdata/perf.data-4.13.pr.out.pb_text
@@ -446001,43 +446001,14 @@
   header {
     type: 10
     misc: 2
-    size: 104
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 112
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -720628,7 +720599,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 3022
-  num_mmap_events: 16381
+  num_mmap_events: 16380
   num_fork_events: 4890
   num_exit_events: 29
   num_sample_events_mapped: 3022
diff --git a/quipper/testdata/perf.data-4.13.ser.comm.out.pb_data b/quipper/testdata/perf.data-4.13.ser.comm.out.pb_data
index e791b1b..057f662 100644
--- a/quipper/testdata/perf.data-4.13.ser.comm.out.pb_data
+++ b/quipper/testdata/perf.data-4.13.ser.comm.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data-4.13.ser.comm.out.pb_text b/quipper/testdata/perf.data-4.13.ser.comm.out.pb_text
index cc9aebe..99a2a73 100644
--- a/quipper/testdata/perf.data-4.13.ser.comm.out.pb_text
+++ b/quipper/testdata/perf.data-4.13.ser.comm.out.pb_text
@@ -446001,43 +446001,14 @@
   header {
     type: 10
     misc: 2
-    size: 104
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 112
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -720628,7 +720599,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 3022
-  num_mmap_events: 16381
+  num_mmap_events: 16380
   num_fork_events: 4890
   num_exit_events: 29
   num_sample_events_mapped: 3022
diff --git a/quipper/testdata/perf.data-4.13.serialized.out.pb_data b/quipper/testdata/perf.data-4.13.serialized.out.pb_data
index f1b59d6..fa01d6e 100644
--- a/quipper/testdata/perf.data-4.13.serialized.out.pb_data
+++ b/quipper/testdata/perf.data-4.13.serialized.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data-4.13.serialized.out.pb_text b/quipper/testdata/perf.data-4.13.serialized.out.pb_text
index d859269..1bf4f16 100644
--- a/quipper/testdata/perf.data-4.13.serialized.out.pb_text
+++ b/quipper/testdata/perf.data-4.13.serialized.out.pb_text
@@ -446001,43 +446001,14 @@
   header {
     type: 10
     misc: 2
-    size: 104
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 112
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -720628,7 +720599,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 3022
-  num_mmap_events: 16381
+  num_mmap_events: 16380
   num_fork_events: 4890
   num_exit_events: 29
   num_sample_events_mapped: 3022
diff --git a/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_data b/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_data
index 954f0a4..d25d924 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_data
+++ b/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_text b/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_text
index 9a6135a..93a0b48 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_text
+++ b/quipper/testdata/perf.data.group_desc-4.4.io.out.pb_text
@@ -461140,44 +461140,14 @@
   header {
     type: 10
     misc: 2
-    size: 112
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      id: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 120
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -733060,7 +733030,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 168
-  num_mmap_events: 16433
+  num_mmap_events: 16432
   num_fork_events: 5504
   num_exit_events: 43
   num_sample_events_mapped: 168
diff --git a/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_data b/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_data
index 954f0a4..d25d924 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_data
+++ b/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_text b/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_text
index 9a6135a..93a0b48 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_text
+++ b/quipper/testdata/perf.data.group_desc-4.4.parse.out.pb_text
@@ -461140,44 +461140,14 @@
   header {
     type: 10
     misc: 2
-    size: 112
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      id: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 120
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -733060,7 +733030,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 168
-  num_mmap_events: 16433
+  num_mmap_events: 16432
   num_fork_events: 5504
   num_exit_events: 43
   num_sample_events_mapped: 168
diff --git a/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_data b/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_data
index babb965..439affe 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_data
+++ b/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_text b/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_text
index 3c0be1b..bab8ce7 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_text
+++ b/quipper/testdata/perf.data.group_desc-4.4.parse.remap.out.pb_text
@@ -461140,44 +461140,14 @@
   header {
     type: 10
     misc: 2
-    size: 112
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 1362210816
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      id: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 120
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 1395765248
-    len: 196608
-    pgoff: 33554432
+    start: 1362210816
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -733060,7 +733030,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 168
-  num_mmap_events: 16433
+  num_mmap_events: 16432
   num_fork_events: 5504
   num_exit_events: 43
   num_sample_events_mapped: 168
diff --git a/quipper/testdata/perf.data.group_desc-4.4.pb_text b/quipper/testdata/perf.data.group_desc-4.4.pb_text
index 9a6135a..93a0b48 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.pb_text
+++ b/quipper/testdata/perf.data.group_desc-4.4.pb_text
@@ -461140,44 +461140,14 @@
   header {
     type: 10
     misc: 2
-    size: 112
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      id: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 120
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -733060,7 +733030,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 168
-  num_mmap_events: 16433
+  num_mmap_events: 16432
   num_fork_events: 5504
   num_exit_events: 43
   num_sample_events_mapped: 168
diff --git a/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_data b/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_data
index 954f0a4..d25d924 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_data
+++ b/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_text b/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_text
index 9a6135a..93a0b48 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_text
+++ b/quipper/testdata/perf.data.group_desc-4.4.pr.out.pb_text
@@ -461140,44 +461140,14 @@
   header {
     type: 10
     misc: 2
-    size: 112
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      id: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 120
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -733060,7 +733030,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 168
-  num_mmap_events: 16433
+  num_mmap_events: 16432
   num_fork_events: 5504
   num_exit_events: 43
   num_sample_events_mapped: 168
diff --git a/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_data b/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_data
index 61ab319..b9ec417 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_data
+++ b/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_text b/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_text
index d978a36..0648ffe 100644
--- a/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_text
+++ b/quipper/testdata/perf.data.group_desc-4.4.ser.comm.out.pb_text
@@ -461140,44 +461140,14 @@
   header {
     type: 10
     misc: 2
-    size: 112
-  }
-  mmap_event {
-    pid: 93980
-    tid: 93980
-    start: 94676532137984
-    len: 33554432
-    pgoff: 0
-    filename: "//anon"
-    filename_md5_prefix: 11476964321930150911
-    sample_info {
-      pid: 0
-      tid: 0
-      sample_time_ns: 0
-      id: 0
-      cpu: 0
-    }
-    maj: 0
-    min: 0
-    ino: 0
-    ino_generation: 0
-    prot: 5
-    flags: 2
-  }
-  timestamp: 0
-}
-events {
-  header {
-    type: 10
-    misc: 2
     size: 120
   }
   mmap_event {
     pid: 93980
     tid: 93980
-    start: 94676565692416
-    len: 196608
-    pgoff: 33554432
+    start: 94676532137984
+    len: 33751040
+    pgoff: 0
     filename: "/usr/sbin/loasd"
     filename_md5_prefix: 2724541329225358946
     sample_info {
@@ -733060,7 +733030,7 @@
 timestamp_sec: 0
 stats {
   num_sample_events: 168
-  num_mmap_events: 16433
+  num_mmap_events: 16432
   num_fork_events: 5504
   num_exit_events: 43
   num_sample_events_mapped: 168
diff --git a/quipper/testdata/perf.data.piped-4.13.pr.out.pb_data b/quipper/testdata/perf.data.piped-4.13.pr.out.pb_data
index 9a58105..6afa85b 100644
--- a/quipper/testdata/perf.data.piped-4.13.pr.out.pb_data
+++ b/quipper/testdata/perf.data.piped-4.13.pr.out.pb_data
Binary files differ
diff --git a/quipper/testdata/perf.data.piped-4.13.pr.out.pb_text b/quipper/testdata/perf.data.piped-4.13.pr.out.pb_text
index 914713a..4058ed9 100644
--- a/quipper/testdata/perf.data.piped-4.13.pr.out.pb_text
+++ b/quipper/testdata/perf.data.piped-4.13.pr.out.pb_text
@@ -458198,9 +458198,9 @@
       sample_time_ns: 0
       cpu: 0
     }
-    maj: 0
-    min: 0
-    ino: 0
+    maj: 252
+    min: 1
+    ino: 3405872
     ino_generation: 0
     prot: 5
     flags: 2