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");
+ }
+}