1 // @file mmaptests.cpp 2 3 4 /** 5 * Copyright (C) 2018-present MongoDB, Inc. 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the Server Side Public License, version 1, 9 * as published by MongoDB, Inc. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * Server Side Public License for more details. 15 * 16 * You should have received a copy of the Server Side Public License 17 * along with this program. If not, see 18 * <http://www.mongodb.com/licensing/server-side-public-license>. 19 * 20 * As a special exception, the copyright holders give permission to link the 21 * code of portions of this program with the OpenSSL library under certain 22 * conditions as described in each individual source file and distribute 23 * linked combinations including the program with the OpenSSL library. You 24 * must comply with the Server Side Public License in all respects for 25 * all of the code used other than as permitted herein. If you modify file(s) 26 * with this exception, you may extend this exception to your version of the 27 * file(s), but you are not obligated to do so. If you do not wish to do so, 28 * delete this exception statement from your version. If you delete this 29 * exception statement from all source files in the program, then also delete 30 * it in the license file. 31 */ 32 33 #include "mongo/platform/basic.h" 34 35 #include <boost/filesystem/operations.hpp> 36 #include <iostream> 37 38 #include "mongo/db/concurrency/d_concurrency.h" 39 #include "mongo/db/concurrency/lock_state.h" 40 #include "mongo/db/service_context.h" 41 #include "mongo/db/storage/mmap_v1/data_file.h" 42 #include "mongo/db/storage/mmap_v1/durable_mapped_file.h" 43 #include "mongo/db/storage/mmap_v1/extent.h" 44 #include "mongo/db/storage/mmap_v1/extent_manager.h" 45 #include "mongo/db/storage/mmap_v1/mmap_v1_extent_manager.h" 46 #include "mongo/db/storage/mmap_v1/mmap_v1_options.h" 47 #include "mongo/db/storage/storage_options.h" 48 #include "mongo/dbtests/dbtests.h" 49 #include "mongo/util/scopeguard.h" 50 #include "mongo/util/timer.h" 51 52 namespace MMapTests { 53 54 using std::endl; 55 using std::string; 56 57 class LeakTest { 58 const string fn; 59 const int optOld; 60 61 public: LeakTest()62 LeakTest() 63 : fn((boost::filesystem::path(storageGlobalParams.dbpath) / "testfile.map").string()), 64 optOld(mmapv1GlobalOptions.journalOptions) { 65 mmapv1GlobalOptions.journalOptions = 0; // DurParanoid doesn't make sense with this test 66 } ~LeakTest()67 ~LeakTest() { 68 mmapv1GlobalOptions.journalOptions = optOld; 69 try { 70 boost::filesystem::remove(fn); 71 } catch (...) { 72 } 73 } run()74 void run() { 75 try { 76 boost::filesystem::remove(fn); 77 } catch (...) { 78 } 79 80 auto opCtx = cc().makeOperationContext(); 81 Lock::GlobalWrite lk(opCtx.get()); 82 83 { 84 DurableMappedFile f(opCtx.get()); 85 ON_BLOCK_EXIT([&f, &opCtx] { 86 LockMongoFilesExclusive lock(opCtx.get()); 87 f.close(opCtx.get()); 88 }); 89 unsigned long long len = 256 * 1024 * 1024; 90 verify(f.create(opCtx.get(), fn, len)); 91 { 92 char* p = (char*)f.getView(); 93 verify(p); 94 // write something to the private view as a test 95 if (storageGlobalParams.dur) 96 privateViews.makeWritable(p, 6); 97 strcpy(p, "hello"); 98 } 99 if (storageGlobalParams.dur) { 100 char* w = (char*)f.view_write(); 101 strcpy(w + 6, "world"); 102 } 103 MongoFileFinder ff(opCtx.get()); 104 ASSERT(ff.findByPath(fn)); 105 ASSERT(ff.findByPath("asdf") == 0); 106 } 107 { 108 MongoFileFinder ff(opCtx.get()); 109 ASSERT(ff.findByPath(fn) == 0); 110 } 111 112 int N = 10000; 113 #if !defined(_WIN32) && !defined(__linux__) 114 // seems this test is slow on OS X. 115 N = 100; 116 #endif 117 118 // we make a lot here -- if we were leaking, presumably it would fail doing this many. 119 Timer t; 120 for (int i = 0; i < N; i++) { 121 // Every 4 iterations we pass the sequential hint. 122 DurableMappedFile f{opCtx.get(), 123 i % 4 == 1 ? MongoFile::Options::SEQUENTIAL 124 : MongoFile::Options::NONE}; 125 ON_BLOCK_EXIT([&f, &opCtx] { 126 LockMongoFilesExclusive lock(opCtx.get()); 127 f.close(opCtx.get()); 128 }); 129 verify(f.open(opCtx.get(), fn)); 130 { 131 char* p = (char*)f.getView(); 132 verify(p); 133 if (storageGlobalParams.dur) 134 privateViews.makeWritable(p, 4); 135 strcpy(p, "zzz"); 136 } 137 if (storageGlobalParams.dur) { 138 char* w = (char*)f.view_write(); 139 if (i % 2 == 0) 140 ++(*w); 141 verify(w[6] == 'w'); 142 } 143 } 144 if (t.millis() > 10000) { 145 mongo::unittest::log() << "warning: MMap LeakTest is unusually slow N:" << N << ' ' 146 << t.millis() << "ms" << endl; 147 } 148 } 149 }; 150 151 class ExtentSizing { 152 public: run()153 void run() { 154 MmapV1ExtentManager em("x", "x", false); 155 156 ASSERT_EQUALS(em.maxSize(), em.quantizeExtentSize(em.maxSize())); 157 158 // test that no matter what we start with, we always get to max extent size 159 for (int obj = 16; obj < BSONObjMaxUserSize; obj += 111) { 160 int sz = em.initialSize(obj); 161 162 double totalExtentSize = sz; 163 164 int numFiles = 1; 165 int sizeLeftInExtent = em.maxSize() - 1; 166 167 for (int i = 0; i < 100; i++) { 168 sz = em.followupSize(obj, sz); 169 ASSERT(sz >= obj); 170 ASSERT(sz >= em.minSize()); 171 ASSERT(sz <= em.maxSize()); 172 ASSERT(sz <= em.maxSize()); 173 174 totalExtentSize += sz; 175 176 if (sz < sizeLeftInExtent) { 177 sizeLeftInExtent -= sz; 178 } else { 179 numFiles++; 180 sizeLeftInExtent = em.maxSize() - sz; 181 } 182 } 183 ASSERT_EQUALS(em.maxSize(), sz); 184 185 double allocatedOnDisk = (double)numFiles * em.maxSize(); 186 187 ASSERT((totalExtentSize / allocatedOnDisk) > .95); 188 189 invariant(em.numFiles() == 0); 190 } 191 } 192 }; 193 194 class All : public Suite { 195 public: All()196 All() : Suite("mmap") {} setupTests()197 void setupTests() { 198 if (!getGlobalServiceContext()->getGlobalStorageEngine()->isMmapV1()) 199 return; 200 201 add<LeakTest>(); 202 add<ExtentSizing>(); 203 } 204 }; 205 206 SuiteInstance<All> myall; 207 208 #if 0 209 210 class CopyOnWriteSpeedTest { 211 public: 212 void run() { 213 214 string fn = "/tmp/testfile.map"; 215 boost::filesystem::remove(fn); 216 217 MemoryMappedFile f; 218 char *p = (char *) f.create(fn, 1024 * 1024 * 1024, true); 219 verify(p); 220 strcpy(p, "hello"); 221 222 { 223 void *x = f.testGetCopyOnWriteView(); 224 Timer tt; 225 for( int i = 11; i < 1000000000; i++ ) 226 p[i] = 'z'; 227 cout << "fill 1GB time: " << tt.millis() << "ms" << endl; 228 f.testCloseCopyOnWriteView(x); 229 } 230 231 /* test a lot of view/unviews */ 232 { 233 Timer t; 234 235 char *q; 236 for( int i = 0; i < 1000; i++ ) { 237 q = (char *) f.testGetCopyOnWriteView(); 238 verify( q ); 239 if( i == 999 ) { 240 strcpy(q+2, "there"); 241 } 242 f.testCloseCopyOnWriteView(q); 243 } 244 245 cout << "view unview: " << t.millis() << "ms" << endl; 246 } 247 248 f.flush(true); 249 250 /* plain old mmaped writes */ 251 { 252 Timer t; 253 for( int i = 0; i < 10; i++ ) { 254 memset(p+100, 'c', 200 * 1024 * 1024); 255 } 256 cout << "traditional writes: " << t.millis() << "ms" << endl; 257 } 258 259 f.flush(true); 260 261 /* test doing some writes */ 262 { 263 Timer t; 264 char *q = (char *) f.testGetCopyOnWriteView(); 265 for( int i = 0; i < 10; i++ ) { 266 verify( q ); 267 memset(q+100, 'c', 200 * 1024 * 1024); 268 } 269 f.testCloseCopyOnWriteView(q); 270 271 cout << "inc style some writes: " << t.millis() << "ms" << endl; 272 } 273 274 /* test doing some writes */ 275 { 276 Timer t; 277 for( int i = 0; i < 10; i++ ) { 278 char *q = (char *) f.testGetCopyOnWriteView(); 279 verify( q ); 280 memset(q+100, 'c', 200 * 1024 * 1024); 281 f.testCloseCopyOnWriteView(q); 282 } 283 284 cout << "some writes: " << t.millis() << "ms" << endl; 285 } 286 287 /* more granular */ 288 { 289 Timer t; 290 for( int i = 0; i < 100; i++ ) { 291 char *q = (char *) f.testGetCopyOnWriteView(); 292 verify( q ); 293 memset(q+100, 'c', 20 * 1024 * 1024); 294 f.testCloseCopyOnWriteView(q); 295 } 296 297 cout << "more granular some writes: " << t.millis() << "ms" << endl; 298 } 299 300 p[10] = 0; 301 cout << p << endl; 302 } 303 }; 304 305 class All : public Suite { 306 public: 307 All() : Suite( "mmap" ) {} 308 void setupTests() { 309 add< CopyOnWriteSpeedTest >(); 310 } 311 } myall; 312 313 #endif 314 } // namespace MMapTests 315