From b0cf5d7636210843fddda5d360450858bab2182c Mon Sep 17 00:00:00 2001
From: Glauber Costa <glommer@cloudius-systems.com>
Date: Fri, 7 Mar 2014 16:40:44 +0400
Subject: [PATCH] tests: test for larger-than-memory mmaps.

It will create a file on-disk twice as large as memory, and then will map it entirely
in memory. The file is then read from using 3 different sequential patterns, and then
later on 2 threaded patterns.

This test does not handle writes.

It goes in misc because it takes a very long time to run (especially with a random pattern)

Example output:

Total Ram 586 Mb
Write done
Double Pass OK (13.6323 usec / page)
Recency OK (3.35954 usec / page)
Random Access OK (640.926 usec / page)
Threaded pass 1 address ended OK
Threaded pass many addresses ended OK
PASSED

Signed-off-by: Glauber Costa <glommer@cloudius-systems.com>
---
 build.mk                    |   1 +
 tests/misc-mmap-big-file.cc | 185 ++++++++++++++++++++++++++++++++++++
 2 files changed, 186 insertions(+)
 create mode 100644 tests/misc-mmap-big-file.cc

diff --git a/build.mk b/build.mk
index aa59a8d1f..d5c8a5172 100644
--- a/build.mk
+++ b/build.mk
@@ -202,6 +202,7 @@ tests += tests/tst-hub.so
 tests += tests/misc-leak.so
 tests += tests/misc-mmap-anon-perf.so
 tests += tests/tst-mmap-file.so
+tests += tests/misc-mmap-big-file.so
 tests += tests/tst-mmap.so
 tests += tests/tst-huge.so
 tests += tests/misc-mutex.so
diff --git a/tests/misc-mmap-big-file.cc b/tests/misc-mmap-big-file.cc
new file mode 100644
index 000000000..32bdf0ff4
--- /dev/null
+++ b/tests/misc-mmap-big-file.cc
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#include <iostream>
+#include <vector>
+#include <random>
+#include <chrono>
+#include <functional>
+#include <thread>
+
+#include <sys/mman.h>
+#include <sys/sysinfo.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+
+
+#define PAGE_SIZE (4 << 10)
+#define FNAME "/tmp/mmap-file-test"
+#define FNAMEANON "/tmp/mmap-file-anon"
+
+char buf[PAGE_SIZE];
+
+typedef std::chrono::time_point<std::chrono::high_resolution_clock> tpoint;
+void report(const char *phase, unsigned long passes,
+            unsigned long pages, const tpoint start, const tpoint end)
+{
+    auto usec = std::chrono::duration_cast<std::chrono::microseconds> (end - start).count();
+    std::cout << phase << " OK (" << float(usec) / (pages * passes) << " usec / page)\n";
+}
+
+char ch(int i)
+{
+    return '@' + (i % ('}' - '@'));
+}
+
+void map_pass(unsigned char *addr, unsigned long passes, unsigned long pages,
+              std::function<unsigned long (unsigned long)> conv)
+{
+    for (unsigned long j = 0; j < pages * passes; ++j) {
+        auto i = conv(j);
+        char x = *(addr + i * PAGE_SIZE);
+        assert(x == ch(i));
+    }
+}
+
+void map_pass(const char *phase, unsigned char *addr, unsigned long passes,
+              unsigned long pages, std::function<unsigned long (unsigned long)> conv)
+{
+    auto start = std::chrono::high_resolution_clock::now();
+    map_pass(addr, passes, pages, conv);
+    auto end = std::chrono::high_resolution_clock::now();
+    report(phase, passes, pages, start, end);
+}
+
+char safe_buffers[PAGE_SIZE][2];
+
+int main(int ac, char** av)
+{
+    int fd = open(FNAME, O_RDWR | O_CREAT, 0666);
+    assert(fd >= 0);
+    int fdanon = open(FNAMEANON, O_RDWR | O_CREAT, 0666);
+    assert(fdanon >= 0);
+
+    struct sysinfo sinfo;
+    assert(sysinfo(&sinfo) == 0);
+
+    std::cout << "Total Ram " << (sinfo.totalram >> 20) << " Mb\n";
+    unsigned long pages = sinfo.totalram / PAGE_SIZE;
+
+    for (unsigned long i = 0; i < pages * 2; ++i) {
+        memset(buf, ch(i), PAGE_SIZE);
+        assert(write(fd, buf, PAGE_SIZE) == PAGE_SIZE);
+    }
+
+    // 256k should take at least 2 ZFS buffers.
+    for (unsigned long i = 0; i < (256 << 10) / PAGE_SIZE; ++i) {
+        memset(buf, '-', PAGE_SIZE);
+        assert(write(fdanon, buf, PAGE_SIZE) == PAGE_SIZE);
+    }
+
+    close(fd);
+    close(fdanon);
+    std::cout << "Write done\n";
+
+    // We need to be careful with mappings in the edges of the file mapping. If we are not careful, invalidation will also
+    // destroy adjacent mappings. It is hard for us to destroy a specific mapping, so what we are going to do is to map a
+    // different file (to avoid recency issues), in the beginning of the test. By the end of it, the new file should have
+    // flushed everything from this file out of the cache.
+    fdanon = open(FNAMEANON, O_RDONLY);
+    assert(fdanon >= 0);
+    void *retanon = mmap(nullptr, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+    void *retself = mmap(retanon + 2 * PAGE_SIZE, 2 * PAGE_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED, fdanon, 2 * PAGE_SIZE);
+    void *retanon2 = mmap(retself + 2 * PAGE_SIZE, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
+
+    for (int i = 0; i < 2; i++) {
+        memcpy(retanon + i * PAGE_SIZE, retself + i * PAGE_SIZE, PAGE_SIZE);
+        memcpy(retanon2 + i * PAGE_SIZE, retself + i * PAGE_SIZE, PAGE_SIZE);
+        memcpy(safe_buffers[i], retself + i * PAGE_SIZE, PAGE_SIZE);
+    }
+
+    fd = open(FNAME, O_RDONLY);
+    assert(fd >= 0);
+
+    std::default_random_engine generator;
+    std::uniform_int_distribution<unsigned long> distribution(0, (pages * 2) - 1);
+
+
+    void *ret = mmap(nullptr, sinfo.totalram * 2, PROT_READ, MAP_SHARED, fd, 0);
+    unsigned char *base = static_cast<unsigned char *>(ret);
+
+    // Read the whole file twice. The file itself is twice the size of memory,
+    // so once evictions will surely happen. We need to see how well we handle
+    // them.
+    map_pass("Double Pass", base, 4, pages, [&](unsigned long j) { return j % (pages * 2); });
+
+    // In this pass, we will keep looping in a subset of half of memory. The number of evictions
+    // is expected to be way lower in this case.
+    map_pass("Recency", base, 4, pages, [&](unsigned long j) { return j % (pages / 2); });
+
+    // Now with random access, we should pretty much destroy any pattern
+    map_pass("Random Access", base, 1, pages, [&](unsigned long j) { return distribution(generator); });
+
+    std::thread t1( [&] { map_pass(base, 4, pages, [&](unsigned long j) { return j % (pages * 2); } ); } );
+    std::thread t2( [&] { map_pass(base, 4, pages, [&](unsigned long j) { return j % (pages / 2); } ); } );
+    std::thread t3( [&] { map_pass(base, 4, pages, [&](unsigned long j) { return j % (pages * 2); } ); } );
+    std::thread t4( [&] { map_pass(base, 4, pages, [&](unsigned long j) { return j % (pages / 2); } ); } );
+
+    t1.join();
+    t2.join();
+    t3.join();
+    t4.join();
+    std::cout << "Threaded pass 1 address ended OK\n";
+
+    ret = mmap(nullptr, sinfo.totalram * 2, PROT_READ, MAP_SHARED, fd, 0);
+    unsigned char *base1 = static_cast<unsigned char *>(ret);
+
+    ret = mmap(nullptr, sinfo.totalram * 2, PROT_READ, MAP_SHARED, fd, 0);
+    unsigned char *base2 = static_cast<unsigned char *>(ret);
+
+    ret = mmap(nullptr, sinfo.totalram * 2, PROT_READ, MAP_SHARED, fd, 0);
+    unsigned char *base3 = static_cast<unsigned char *>(ret);
+
+    std::thread ta1( [&] { map_pass(base,  4, pages, [&](unsigned long j) { return j % (pages * 2); } ); } );
+    std::thread ta2( [&] { map_pass(base1, 4, pages, [&](unsigned long j) { return j % (pages / 2); } ); } );
+    std::thread ta3( [&] { map_pass(base2, 4, pages, [&](unsigned long j) { return j % (pages * 2); } ); } );
+    std::thread ta4( [&] { map_pass(base3, 4, pages, [&](unsigned long j) { return j % (pages / 2); } ); } );
+
+    ta1.join();
+    ta2.join();
+    ta3.join();
+    ta4.join();
+    std::cout << "Threaded pass many addresses ended OK\n";
+
+    for (int i = 0; i < 2; i++) {
+        assert(memcmp(retanon + i * PAGE_SIZE, safe_buffers[i], PAGE_SIZE) == 0);
+        assert(memcmp(retanon2 + i * PAGE_SIZE, safe_buffers[i], PAGE_SIZE) == 0);
+    }
+
+    std::cout << "Anonymous boundaries ended OK\n";
+
+    close(fd);
+    close(fdanon);
+
+    munmap(base1, sinfo.totalram * 2);
+    munmap(base2, sinfo.totalram * 2);
+    munmap(base3, sinfo.totalram * 2);
+
+    // Make sure the split code works.
+    munmap(base + sinfo.totalram, PAGE_SIZE);
+    map_pass(base, 1, pages, [&](unsigned long j) { return j; }); 
+    munmap(base, sinfo.totalram * 2);
+    std::cout << "Split mapping ended OK\n";
+
+    std::cout << "PASSED\n";
+    return 0;
+}
-- 
GitLab