Use IDLE bit instead of referenced bit.

Bug: 132952543
Change-Id: I854a3b4e30b89c2953cf3246a11d0555cb946042
diff --git a/Android.bp b/Android.bp
index a07358c..7268285 100644
--- a/Android.bp
+++ b/Android.bp
@@ -215,6 +215,20 @@
   ],
 }
 
+// GN target: //:idle_alloc
+cc_binary {
+  name: "idle_alloc",
+  srcs: [
+    "tools/idle_alloc.cc",
+  ],
+  shared_libs: [
+    "liblog",
+  ],
+  defaults: [
+    "perfetto_defaults",
+  ],
+}
+
 // GN target: //:libperfetto
 cc_library_shared {
   name: "libperfetto",
diff --git a/BUILD.gn b/BUILD.gn
index 720a291..1173b01 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -353,3 +353,9 @@
     ]
   }
 }
+
+executable("idle_alloc") {
+  sources = [
+    "tools/idle_alloc.cc",
+  ]
+}
diff --git a/heapprofd.rc b/heapprofd.rc
index 2e69f60..86b8c74 100644
--- a/heapprofd.rc
+++ b/heapprofd.rc
@@ -24,7 +24,10 @@
     # DAC_READ_SEARCH is denied by SELinux on user builds because the SELinux
     # permission is userdebug_or_eng only.
     # This is fine as this is not needed for central heapprofd in fork mode.
-    capabilities KILL DAC_READ_SEARCH
+    # SYS_ADMIN and SYS_PTRACE will be denied unless SELinux enforcement is
+    # off. They are needed to implement the idle page tracking feature, which
+    # only works without SELinux enforcement on.
+    capabilities KILL DAC_READ_SEARCH DAC_OVERRIDE SYS_ADMIN SYS_PTRACE
 
 on property:persist.heapprofd.enable=1
     start heapprofd
diff --git a/src/profiling/memory/client.cc b/src/profiling/memory/client.cc
index 0ee9ca8..dc6952d 100644
--- a/src/profiling/memory/client.cc
+++ b/src/profiling/memory/client.cc
@@ -152,11 +152,6 @@
     PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/self/mem");
     return nullptr;
   }
-  base::ScopedFile pagemap(base::OpenFile("/proc/self/pagemap", O_RDONLY));
-  if (!pagemap) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/self/pagemap");
-    return nullptr;
-  }
 
   // Restore original dumpability value if we overrode it.
   unset_dumpable.reset();
@@ -164,7 +159,6 @@
   int fds[kHandshakeSize];
   fds[kHandshakeMaps] = *maps;
   fds[kHandshakeMem] = *mem;
-  fds[kHandshakePageMap] = *pagemap;
 
   // Send an empty record to transfer fds for /proc/self/maps and
   // /proc/self/mem.
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index 4bbe5e6..2c32f07 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -87,6 +87,22 @@
   return i;
 }
 
+base::Optional<PageIdleChecker> MakePageIdleChecker(base::ScopedFile pagemap) {
+  base::Optional<PageIdleChecker> res;
+  if (!pagemap) {
+    PERFETTO_PLOG("Invalid pagemap.");
+    return res;
+  }
+  base::ScopedFile bitmap(
+      base::OpenFile("/sys/kernel/mm/page_idle/bitmap", O_RDWR));
+  if (!bitmap) {
+    PERFETTO_PLOG("Failed to open /sys/kernel/mm/page_idle/bitmap.");
+    return res;
+  }
+  res = PageIdleChecker(std::move(pagemap), std::move(bitmap));
+  return res;
+}
+
 }  // namespace
 
 const uint64_t LogHistogram::kMaxBucket = 0;
@@ -590,6 +606,8 @@
         [&dump_state](const HeapTracker::CallstackAllocations& alloc) {
           dump_state.WriteAllocation(alloc);
         });
+    if (process_state.page_idle_checker)
+      process_state.page_idle_checker->MarkPagesIdle();
   }
 
   dump_state.DumpCallstacks(&callsites_);
@@ -679,8 +697,8 @@
   char buf[1];
   self->Receive(buf, sizeof(buf), fds, base::ArraySize(fds));
 
-  static_assert(kHandshakeSize == 3, "change if and else if below.");
-  if (fds[kHandshakeMaps] && fds[kHandshakeMem] && fds[kHandshakePageMap]) {
+  static_assert(kHandshakeSize == 2, "change if and else if below.");
+  if (fds[kHandshakeMaps] && fds[kHandshakeMem]) {
     auto ds_it =
         producer_->data_sources_.find(pending_process.data_source_instance_id);
     if (ds_it == producer_->data_sources_.end()) {
@@ -694,13 +712,17 @@
 
     ProcessState& process_state = it_and_inserted.first->second;
     if (data_source.config.idle_allocations()) {
-      base::ScopedFile kpageflags(base::OpenFile("/proc/kpageflags", O_RDONLY));
-      if (kpageflags) {
-        process_state.page_idle_checker = PageIdleChecker(
-            std::move(fds[kHandshakePageMap]), std::move(kpageflags));
-      } else {
-        PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/kpageflags");
-      }
+      // We have to open this here, because reading the PFN requires
+      // the process that opened the file to have CAP_SYS_ADMIN. We can work
+      // around this by making this a setenforce 0 only feature, giving
+      // heapprofd very broad capabilities (CAP_SYS_ADMIN and CAP_SYS_PTRACE)
+      // which will get rejected by SELinux on real builds.
+      std::string procfs_path =
+          "/proc/" + std::to_string(self->peer_pid()) + "/pagemap";
+      base::ScopedFile pagemap_fd(
+          base::OpenFile(procfs_path.c_str(), O_RDONLY));
+      process_state.page_idle_checker =
+          MakePageIdleChecker(std::move(pagemap_fd));
     }
 
     PERFETTO_DLOG("%d: Received FDs.", self->peer_pid());
@@ -722,8 +744,7 @@
     producer_->UnwinderForPID(self->peer_pid())
         .PostHandoffSocket(std::move(handoff_data));
     producer_->pending_processes_.erase(it);
-  } else if (fds[kHandshakeMaps] || fds[kHandshakeMem] ||
-             fds[kHandshakePageMap]) {
+  } else if (fds[kHandshakeMaps] || fds[kHandshakeMem]) {
     PERFETTO_DFATAL_OR_ELOG("%d: Received partial FDs.", self->peer_pid());
     producer_->pending_processes_.erase(it);
   } else {
diff --git a/src/profiling/memory/page_idle_checker.cc b/src/profiling/memory/page_idle_checker.cc
index 9dc8726..62e19f6 100644
--- a/src/profiling/memory/page_idle_checker.cc
+++ b/src/profiling/memory/page_idle_checker.cc
@@ -17,6 +17,7 @@
 #include "src/profiling/memory/page_idle_checker.h"
 #include "src/profiling/memory/utils.h"
 
+#include <inttypes.h>
 #include <vector>
 
 namespace perfetto {
@@ -26,57 +27,103 @@
 constexpr uint64_t kIsInRam = 1ULL << 63;
 constexpr uint64_t kRamPhysicalPageMask = ~(~0ULL << 55);
 
-constexpr uint64_t kPhysPageReferenced = 1ULL << 2;
-
 }  // namespace
 
 int64_t PageIdleChecker::OnIdlePage(uint64_t addr, size_t size) {
   uint64_t page_nr = addr / base::kPageSize;
-  uint64_t page_aligned_addr = page_nr * base::kPageSize;
   uint64_t end_page_nr = (addr + size) / base::kPageSize;
   // The trailing division will have rounded down, unless the end is at a page
   // boundary. Add one page if we rounded down.
-  if (addr + size % base::kPageSize != 0)
+  if ((addr + size) % base::kPageSize != 0)
     end_page_nr++;
-  uint64_t page_aligned_end_addr = base::kPageSize * end_page_nr;
 
-  size_t pages = (page_aligned_end_addr - page_aligned_addr) / base::kPageSize;
+  size_t pages = end_page_nr - page_nr;
   std::vector<uint64_t> virt_page_infos(pages);
 
   off64_t virt_off = static_cast<off64_t>(page_nr * sizeof(virt_page_infos[0]));
   size_t virt_rd_size = pages * sizeof(virt_page_infos[0]);
-  if (ReadAtOffsetClobberSeekPos(*pagemap_fd_, &(virt_page_infos[0]),
-                                 virt_rd_size, virt_off) !=
-      static_cast<ssize_t>(virt_rd_size)) {
+  ssize_t rd = ReadAtOffsetClobberSeekPos(*pagemap_fd_, &(virt_page_infos[0]),
+                                          virt_rd_size, virt_off);
+  if (rd != static_cast<ssize_t>(virt_rd_size)) {
+    PERFETTO_ELOG("Invalid read from pagemap: %zd", rd);
     return -1;
   }
 
   int64_t idle_mem = 0;
 
   for (size_t i = 0; i < pages; ++i) {
-    if (!(virt_page_infos[i] & kIsInRam))
+    if (!virt_page_infos[i]) {
+      PERFETTO_DLOG("Empty pageinfo.");
       continue;
-    uint64_t phys_page_nr = virt_page_infos[i] & kRamPhysicalPageMask;
-    uint64_t phys_page_info;
-    off64_t phys_off =
-        static_cast<off64_t>(phys_page_nr * sizeof(phys_page_info));
-    if (ReadAtOffsetClobberSeekPos(*kpageflags_fd_, &phys_page_info,
-                                   sizeof(phys_page_info),
-                                   phys_off) != sizeof(phys_page_info)) {
-      return -1;
     }
-    if (!(phys_page_info & kPhysPageReferenced)) {
+
+    if (!(virt_page_infos[i] & kIsInRam)) {
+      PERFETTO_DLOG("Page is not in RAM.");
+      continue;
+    }
+
+    uint64_t phys_page_nr = virt_page_infos[i] & kRamPhysicalPageMask;
+    if (!phys_page_nr) {
+      PERFETTO_ELOG("Failed to get physical page number.");
+      continue;
+    }
+
+    int idle = IsPageIdle(phys_page_nr);
+    if (idle == -1)
+      continue;
+
+    if (idle) {
       if (i == 0)
         idle_mem += GetFirstPageShare(addr, size);
       else if (i == pages - 1)
         idle_mem += GetLastPageShare(addr, size);
       else
         idle_mem += base::kPageSize;
+    } else {
+      touched_phys_page_nrs_.emplace(phys_page_nr);
     }
   }
   return idle_mem;
 }
 
+void PageIdleChecker::MarkPagesIdle() {
+  for (uint64_t phys_page_nr : touched_phys_page_nrs_)
+    MarkPageIdle(phys_page_nr);
+  touched_phys_page_nrs_.clear();
+}
+
+void PageIdleChecker::MarkPageIdle(uint64_t phys_page_nr) {
+  // The file implements a bitmap where each bit corresponds to a memory page.
+  // The bitmap is represented by an array of 8-byte integers, and the page at
+  // PFN #i is mapped to bit #i%64 of array element #i/64, byte order i
+  // native. When a bit is set, the corresponding page is idle.
+  //
+  // The kernel ORs the value written with the existing bitmap, so we do not
+  // override previously written values.
+  // See https://www.kernel.org/doc/Documentation/vm/idle_page_tracking.txt
+  off64_t offset = 8 * (phys_page_nr / 64);
+  size_t bit_offset = phys_page_nr % 64;
+  uint64_t bit_pattern = 1 << bit_offset;
+  if (WriteAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern,
+                                  sizeof(bit_pattern), offset) !=
+      static_cast<ssize_t>(sizeof(bit_pattern))) {
+    PERFETTO_PLOG("Failed to write bit pattern at %" PRIi64 ".", offset);
+  }
+}
+
+int PageIdleChecker::IsPageIdle(uint64_t phys_page_nr) {
+  off64_t offset = 8 * (phys_page_nr / 64);
+  size_t bit_offset = phys_page_nr % 64;
+  uint64_t bit_pattern;
+  if (ReadAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern, sizeof(bit_pattern),
+                                 offset) !=
+      static_cast<ssize_t>(sizeof(bit_pattern))) {
+    PERFETTO_PLOG("Failed to read bit pattern at %" PRIi64 ".", offset);
+    return -1;
+  }
+  return static_cast<int>(bit_pattern & (1 << bit_offset));
+}
+
 uint64_t GetFirstPageShare(uint64_t addr, size_t size) {
   // Our allocation is xxxx in this illustration:
   //         +----------------------------------------------+
diff --git a/src/profiling/memory/page_idle_checker.h b/src/profiling/memory/page_idle_checker.h
index c7beb9d..5a3dcae 100644
--- a/src/profiling/memory/page_idle_checker.h
+++ b/src/profiling/memory/page_idle_checker.h
@@ -17,6 +17,8 @@
 #ifndef SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
 #define SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
 
+#include <set>
+
 #include <stddef.h>
 #include <stdint.h>
 
@@ -30,18 +32,25 @@
 
 class PageIdleChecker {
  public:
-  PageIdleChecker(base::ScopedFile pagemap_fd, base::ScopedFile kpageflags_fd)
-      : pagemap_fd_(std::move(pagemap_fd)),
-        kpageflags_fd_(std::move(kpageflags_fd)) {}
+  PageIdleChecker(base::ScopedFile pagemap_fd, base::ScopedFile bitmap_fd)
+      : pagemap_fd_(std::move(pagemap_fd)), bitmap_fd_(std::move(bitmap_fd)) {}
 
   // Return number of bytes of allocation of size bytes starting at alloc that
   // are on unreferenced pages.
   // Return -1 on error.
   int64_t OnIdlePage(uint64_t addr, size_t size);
 
+  void MarkPagesIdle();
+
  private:
+  void MarkPageIdle(uint64_t phys_page_nr);
+  // Return 1 if page is idle, 0 if it is not idle, or -1 on error.
+  int IsPageIdle(uint64_t phys_page_nr);
+
+  std::set<uint64_t> touched_phys_page_nrs_;
+
   base::ScopedFile pagemap_fd_;
-  base::ScopedFile kpageflags_fd_;
+  base::ScopedFile bitmap_fd_;
 };
 
 }  // namespace profiling
diff --git a/src/profiling/memory/utils.cc b/src/profiling/memory/utils.cc
index 3e2e78d..b424192 100644
--- a/src/profiling/memory/utils.cc
+++ b/src/profiling/memory/utils.cc
@@ -35,5 +35,21 @@
 #endif
 }
 
+// Behaves as a pread64, emulating it if not already exposed by the standard
+// library.
+// Clobbers the |fd| seek position if emulating.
+ssize_t WriteAtOffsetClobberSeekPos(int fd,
+                                    void* buf,
+                                    size_t count,
+                                    off64_t addr) {
+#ifdef __BIONIC__
+  return pwrite64(fd, buf, count, addr);
+#else
+  if (lseek64(fd, addr, SEEK_SET) == -1)
+    return -1;
+  return write(fd, buf, count);
+#endif
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/utils.h b/src/profiling/memory/utils.h
index d338678..5e375c9 100644
--- a/src/profiling/memory/utils.h
+++ b/src/profiling/memory/utils.h
@@ -26,6 +26,11 @@
                                    void* buf,
                                    size_t count,
                                    off64_t addr);
+
+ssize_t WriteAtOffsetClobberSeekPos(int fd,
+                                    void* buf,
+                                    size_t count,
+                                    off64_t addr);
 }
 }  // namespace perfetto
 
diff --git a/src/profiling/memory/wire_protocol.h b/src/profiling/memory/wire_protocol.h
index b970dd4..9de58d6 100644
--- a/src/profiling/memory/wire_protocol.h
+++ b/src/profiling/memory/wire_protocol.h
@@ -119,9 +119,8 @@
 
 enum HandshakeFDs : size_t {
   kHandshakeMaps = 0,
-  kHandshakeMem = 1,
-  kHandshakePageMap = 2,
-  kHandshakeSize = 3,
+  kHandshakeMem,
+  kHandshakeSize,
 };
 
 struct WireMessage {
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index ba6d4a2..b01a476 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -47,6 +47,7 @@
     '//:heapprofd_client',
     '//:heapprofd',
     '//:trigger_perfetto',
+    '//:idle_alloc',
 ]
 
 # Defines a custom init_rc argument to be applied to the corresponding output
diff --git a/tools/idle_alloc.cc b/tools/idle_alloc.cc
new file mode 100644
index 0000000..09dc645
--- /dev/null
+++ b/tools/idle_alloc.cc
@@ -0,0 +1,227 @@
+/*
+ * 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 <memory>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace {
+
+constexpr auto kIdleSize = 10000 * 4096;
+constexpr auto kNoIdleSize = 1000 * 4096;
+
+static bool interrupted = false;
+
+volatile unsigned char* __attribute__((noinline)) AllocIdle(size_t bytes);
+volatile unsigned char* __attribute__((noinline)) AllocIdle(size_t bytes) {
+  // This volatile is needed to prevent the compiler from trying to be
+  // helpful and compiling a "useless" malloc + free into a noop.
+  volatile unsigned char* x = static_cast<unsigned char*>(malloc(bytes));
+  if (x) {
+    x[1] = 'x';
+  }
+  return x;
+}
+
+volatile unsigned char* __attribute__((noinline)) AllocNoIdle(size_t bytes);
+volatile unsigned char* __attribute__((noinline)) AllocNoIdle(size_t bytes) {
+  // This volatile is needed to prevent the compiler from trying to be
+  // helpful and compiling a "useless" malloc + free into a noop.
+  volatile unsigned char* x = static_cast<unsigned char*>(malloc(bytes));
+  if (x) {
+    x[0] = 'x';
+  }
+  return x;
+}
+
+class MemoryToucher {
+ public:
+  virtual void Touch(volatile unsigned char* nonidle) = 0;
+  virtual ~MemoryToucher() = default;
+};
+
+class ReadDevZeroChunks : public MemoryToucher {
+ public:
+  ReadDevZeroChunks(size_t chunk_size)
+      : chunk_size_(chunk_size), fd_(open("/dev/zero", O_RDONLY)) {
+    if (fd_ == -1) {
+      fprintf(stderr, "Failed to open: %s", strerror(errno));
+      abort();
+    }
+  }
+
+  ~ReadDevZeroChunks() override = default;
+
+  void Touch(volatile unsigned char* nonidle) override {
+    size_t total_rd = 0;
+    while (total_rd < kNoIdleSize) {
+      size_t chunk = chunk_size_;
+      if (chunk > kNoIdleSize - total_rd)
+        chunk = kNoIdleSize - total_rd;
+
+      ssize_t rd =
+          read(fd_, const_cast<unsigned char*>(nonidle) + total_rd, chunk);
+      if (rd == -1) {
+        fprintf(stderr, "Failed to write: %s.", strerror(errno));
+        abort();
+      }
+      total_rd += static_cast<size_t>(rd);
+    }
+  }
+
+ private:
+  size_t chunk_size_;
+  int fd_;
+};
+
+class ReadDevZeroChunksAndSleep : public ReadDevZeroChunks {
+ public:
+  ReadDevZeroChunksAndSleep(size_t chunk_size)
+      : ReadDevZeroChunks(chunk_size) {}
+  void Touch(volatile unsigned char* nonidle) override {
+    ReadDevZeroChunks::Touch(nonidle);
+    sleep(1);
+  }
+};
+
+class SumUp : public MemoryToucher {
+ public:
+  SumUp()
+      : sum_(const_cast<volatile uint64_t*>(
+            static_cast<uint64_t*>(malloc(sizeof(uint64_t))))) {}
+  ~SumUp() override = default;
+
+  void Touch(volatile unsigned char* nonidle) override {
+    for (size_t i = 0; i < kNoIdleSize; ++i)
+      *sum_ += nonidle[i];
+  }
+
+ private:
+  volatile uint64_t* sum_;
+};
+
+class ReadDevZeroChunksAndSum : public ReadDevZeroChunks {
+ public:
+  ReadDevZeroChunksAndSum(size_t chunk_size) : ReadDevZeroChunks(chunk_size) {}
+  void Touch(volatile unsigned char* nonidle) override {
+    ReadDevZeroChunks::Touch(nonidle);
+    sum_up_.Touch(nonidle);
+  }
+
+ private:
+  SumUp sum_up_;
+};
+
+class AssignValues : public MemoryToucher {
+ public:
+  ~AssignValues() override = default;
+
+  void Touch(volatile unsigned char* nonidle) override {
+    for (size_t i = 0; i < kNoIdleSize; ++i)
+      nonidle[i] = static_cast<unsigned char>(i % 256);
+  }
+};
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  volatile auto* idle = AllocIdle(kIdleSize);
+  volatile auto* nonidle = AllocNoIdle(kNoIdleSize);
+
+  printf("Own PID: %" PRIdMAX "\n", static_cast<intmax_t>(getpid()));
+  printf("Idle: %p\n", static_cast<void*>(const_cast<unsigned char*>(idle)));
+  printf("Nonidle: %p\n",
+         static_cast<void*>(const_cast<unsigned char*>(nonidle)));
+
+  for (size_t i = 0; i < kIdleSize; ++i)
+    idle[i] = static_cast<unsigned char>(i % 256);
+  for (size_t i = 0; i < kNoIdleSize; ++i)
+    nonidle[i] = static_cast<unsigned char>(i % 256);
+
+  printf("Allocated everything.\n");
+
+  struct sigaction action = {};
+  action.sa_handler = [](int) { interrupted = true; };
+  if (sigaction(SIGUSR1, &action, nullptr) != 0) {
+    fprintf(stderr, "Failed to register signal handler.\n");
+    abort();
+  }
+
+  if (argc < 2) {
+    fprintf(stderr,
+            "Specifiy one of AssignValues / SumUp / ReadDevZeroChunks\n");
+    abort();
+  }
+
+  std::unique_ptr<MemoryToucher> toucher;
+  if (strcmp(argv[1], "AssignValues") == 0) {
+    toucher.reset(new AssignValues());
+    printf("Using AssignValues.\n");
+  } else if (strcmp(argv[1], "SumUp") == 0) {
+    toucher.reset(new SumUp());
+    printf("Using SumUp.\n");
+  } else if (strcmp(argv[1], "ReadDevZeroChunks") == 0 ||
+             strcmp(argv[1], "ReadDevZeroChunksAndSleep") == 0 ||
+             strcmp(argv[1], "ReadDevZeroChunksAndSum") == 0) {
+    if (argc < 3) {
+      fprintf(stderr, "Specify chunk size.\n");
+      abort();
+    }
+    char* end;
+    long long chunk_arg = strtoll(argv[2], &end, 10);
+    if (*end != '\0' || *argv[2] == '\0') {
+      fprintf(stderr, "Invalid chunk size: %s\n", argv[2]);
+      abort();
+    }
+    if (strcmp(argv[1], "ReadDevZeroChunksAndSleep") == 0) {
+      printf("Using ReadDevZeroChunksAndSleep.\n");
+      toucher.reset(
+          new ReadDevZeroChunksAndSleep(static_cast<size_t>(chunk_arg)));
+    } else if (strcmp(argv[1], "ReadDevZeroChunksAndSum") == 0) {
+      printf("Using ReadDevZeroChunksAndSum.\n");
+      toucher.reset(
+          new ReadDevZeroChunksAndSum(static_cast<size_t>(chunk_arg)));
+    } else {
+      printf("Using ReadDevZeroChunks.\n");
+      toucher.reset(new ReadDevZeroChunks(static_cast<size_t>(chunk_arg)));
+    }
+  } else {
+    fprintf(stderr, "Invalid input.\n");
+    abort();
+  }
+
+  while (true) {
+    bool report = interrupted;
+    if (report) {
+      printf("Waiting to finish touching everything.\n");
+      interrupted = false;
+      sleep(2);
+    }
+
+    toucher->Touch(nonidle);
+
+    if (report)
+      printf("Touched everything.\n");
+  }
+}