1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.IOException;
25
26import java.lang.management.BufferPoolMXBean;
27import java.lang.management.ManagementFactory;
28
29import java.nio.ByteBuffer;
30
31import java.nio.channels.FileChannel;
32
33import java.nio.file.Path;
34import java.nio.file.Paths;
35
36import static java.nio.file.StandardOpenOption.CREATE;
37import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
38import static java.nio.file.StandardOpenOption.WRITE;
39
40import java.util.List;
41import java.util.Random;
42
43/*
44 * @test
45 * @requires sun.arch.data.model == "64"
46 * @modules java.management
47 * @build TestMaxCachedBufferSize
48 * @run main/othervm TestMaxCachedBufferSize
49 * @run main/othervm -Djdk.nio.maxCachedBufferSize=0 TestMaxCachedBufferSize
50 * @run main/othervm -Djdk.nio.maxCachedBufferSize=2000 TestMaxCachedBufferSize
51 * @run main/othervm -Djdk.nio.maxCachedBufferSize=100000 TestMaxCachedBufferSize
52 * @run main/othervm -Djdk.nio.maxCachedBufferSize=10000000 TestMaxCachedBufferSize
53 * @summary Test the implementation of the jdk.nio.maxCachedBufferSize property.
54 */
55public class TestMaxCachedBufferSize {
56    private static final int DEFAULT_ITERS = 10 * 1000;
57    private static final int DEFAULT_THREAD_NUM = 4;
58
59    private static final int SMALL_BUFFER_MIN_SIZE =  4 * 1024;
60    private static final int SMALL_BUFFER_MAX_SIZE = 64 * 1024;
61    private static final int SMALL_BUFFER_DIFF_SIZE =
62                                 SMALL_BUFFER_MAX_SIZE - SMALL_BUFFER_MIN_SIZE;
63
64    private static final int LARGE_BUFFER_MIN_SIZE =      512 * 1024;
65    private static final int LARGE_BUFFER_MAX_SIZE = 4 * 1024 * 1024;
66    private static final int LARGE_BUFFER_DIFF_SIZE =
67                                 LARGE_BUFFER_MAX_SIZE - LARGE_BUFFER_MIN_SIZE;
68
69    private static final int LARGE_BUFFER_FREQUENCY = 100;
70
71    private static final String FILE_NAME_PREFIX = "nio-out-file-";
72    private static final int VERBOSE_PERIOD = 5 * 1000;
73
74    private static int iters = DEFAULT_ITERS;
75    private static int threadNum = DEFAULT_THREAD_NUM;
76
77    private static BufferPoolMXBean getDirectPool() {
78        final List<BufferPoolMXBean> pools =
79                  ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);
80        for (BufferPoolMXBean pool : pools) {
81            if (pool.getName().equals("direct")) {
82                return pool;
83            }
84        }
85        throw new Error("could not find direct pool");
86    }
87    private static final BufferPoolMXBean directPool = getDirectPool();
88
89    // Each worker will do write operations on a file channel using
90    // buffers of various sizes. The buffer size is randomly chosen to
91    // be within a small or a large range. This way we can control
92    // which buffers can be cached (all, only the small ones, or none)
93    // by setting the jdk.nio.maxCachedBufferSize property.
94    private static class Worker implements Runnable {
95        private final int id;
96        private final Random random = new Random();
97        private long smallBufferCount = 0;
98        private long largeBufferCount = 0;
99
100        private int getWriteSize() {
101            int minSize = 0;
102            int diff = 0;
103            if (random.nextInt() % LARGE_BUFFER_FREQUENCY != 0) {
104                // small buffer
105                minSize = SMALL_BUFFER_MIN_SIZE;
106                diff = SMALL_BUFFER_DIFF_SIZE;
107                smallBufferCount += 1;
108            } else {
109                // large buffer
110                minSize = LARGE_BUFFER_MIN_SIZE;
111                diff = LARGE_BUFFER_DIFF_SIZE;
112                largeBufferCount += 1;
113            }
114            return minSize + random.nextInt(diff);
115        }
116
117        private void loop() {
118            final String fileName = String.format("%s%d", FILE_NAME_PREFIX, id);
119
120            try {
121                for (int i = 0; i < iters; i += 1) {
122                    final int writeSize = getWriteSize();
123
124                    // This will allocate a HeapByteBuffer. It should not
125                    // be a direct buffer, otherwise the write() method on
126                    // the channel below will not create a temporary
127                    // direct buffer for the write.
128                    final ByteBuffer buffer = ByteBuffer.allocate(writeSize);
129
130                    // Put some random data on it.
131                    while (buffer.hasRemaining()) {
132                        buffer.put((byte) random.nextInt());
133                    }
134                    buffer.rewind();
135
136                    final Path file = Paths.get(fileName);
137                    try (FileChannel outChannel = FileChannel.open(file, CREATE, TRUNCATE_EXISTING, WRITE)) {
138                        // The write() method will create a temporary
139                        // direct buffer for the write and attempt to cache
140                        // it. It's important that buffer is not a
141                        // direct buffer, otherwise the temporary buffer
142                        // will not be created.
143                        long res = outChannel.write(buffer);
144                    }
145
146                    if ((i + 1) % VERBOSE_PERIOD == 0) {
147                        System.out.printf(
148                          " Worker %3d | %8d Iters | Small %8d Large %8d | Direct %4d / %7dK\n",
149                          id, i + 1, smallBufferCount, largeBufferCount,
150                          directPool.getCount(), directPool.getTotalCapacity() / 1024);
151                    }
152                }
153            } catch (IOException e) {
154                throw new Error("I/O error", e);
155            }
156        }
157
158        @Override
159        public void run() {
160            loop();
161        }
162
163        public Worker(int id) {
164            this.id = id;
165        }
166    }
167
168    public static void checkDirectBuffers(long expectedCount, long expectedMax) {
169        final long directCount = directPool.getCount();
170        final long directTotalCapacity = directPool.getTotalCapacity();
171        System.out.printf("Direct %d / %dK\n",
172                          directCount, directTotalCapacity / 1024);
173
174        // Note that directCount could be < expectedCount. This can
175        // happen if a GC occurs after one of the worker threads exits
176        // since its thread-local DirectByteBuffer could be cleaned up
177        // before we reach here.
178        if (directCount > expectedCount) {
179            throw new Error(String.format(
180                "inconsistent direct buffer total count, expected = %d, found = %d",
181                expectedCount, directCount));
182        }
183
184        if (directTotalCapacity > expectedMax) {
185            throw new Error(String.format(
186                "inconsistent direct buffer total capacity, expectex max = %d, found = %d",
187                expectedMax, directTotalCapacity));
188        }
189    }
190
191    public static void main(String[] args) {
192        final String maxBufferSizeStr = System.getProperty("jdk.nio.maxCachedBufferSize");
193        final long maxBufferSize =
194            (maxBufferSizeStr != null) ? Long.valueOf(maxBufferSizeStr) : Long.MAX_VALUE;
195
196        // We assume that the max cannot be equal to a size of a
197        // buffer that can be allocated (makes sanity checking at the
198        // end easier).
199        if ((SMALL_BUFFER_MIN_SIZE <= maxBufferSize &&
200                                     maxBufferSize <= SMALL_BUFFER_MAX_SIZE) ||
201            (LARGE_BUFFER_MIN_SIZE <= maxBufferSize &&
202                                     maxBufferSize <= LARGE_BUFFER_MAX_SIZE)) {
203            throw new Error(String.format("max buffer size = %d not allowed",
204                                          maxBufferSize));
205        }
206
207        System.out.printf("Threads %d | Iterations %d | MaxBufferSize %d\n",
208                          threadNum, iters, maxBufferSize);
209        System.out.println();
210
211        final Thread[] threads = new Thread[threadNum];
212        for (int i = 0; i < threadNum; i += 1) {
213            threads[i] = new Thread(new Worker(i));
214            threads[i].start();
215        }
216
217        try {
218            for (int i = 0; i < threadNum; i += 1) {
219                threads[i].join();
220            }
221        } catch (InterruptedException e) {
222            throw new Error("join() interrupted!", e);
223        }
224
225        // There is an assumption here that, at this point, only the
226        // cached DirectByteBuffers should be active. Given we
227        // haven't used any other DirectByteBuffers in this test, this
228        // should hold.
229        //
230        // Also note that we can only do the sanity checking at the
231        // end and not during the run given that, at any time, there
232        // could be buffers currently in use by some of the workers
233        // that will not be cached.
234
235        System.out.println();
236        if (maxBufferSize < SMALL_BUFFER_MAX_SIZE) {
237            // The max buffer size is smaller than all buffers that
238            // were allocated. No buffers should have been cached.
239            checkDirectBuffers(0, 0);
240        } else if (maxBufferSize < LARGE_BUFFER_MIN_SIZE) {
241            // The max buffer size is larger than all small buffers
242            // but smaller than all large buffers that were
243            // allocated. Only small buffers could have been cached.
244            checkDirectBuffers(threadNum,
245                               (long) threadNum * (long) SMALL_BUFFER_MAX_SIZE);
246        } else {
247            // The max buffer size is larger than all buffers that
248            // were allocated. All buffers could have been cached.
249            checkDirectBuffers(threadNum,
250                               (long) threadNum * (long) LARGE_BUFFER_MAX_SIZE);
251        }
252    }
253}
254