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