001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.store.kahadb.disk.util;
018
019import java.io.File;
020import java.io.RandomAccessFile;
021import java.util.ArrayList;
022import java.util.Arrays;
023
024import org.apache.activemq.util.RecoverableRandomAccessFile;
025
026/**
027 * This class is used to get a benchmark the raw disk performance.
028 */
029public class DiskBenchmark {
030
031    private static final boolean SKIP_METADATA_UPDATE =
032        Boolean.getBoolean("org.apache.activemq.file.skipMetadataUpdate");
033
034    boolean verbose;
035    // reads and writes work with 4k of data at a time.
036    int bs = 1024 * 4;
037    // Work with 100 meg file.
038    long size = 1024 * 1024 * 500;
039    long sampleInterval = 10 * 1000;
040
041    public static void main(String[] args) {
042
043        DiskBenchmark benchmark = new DiskBenchmark();
044        args = CommandLineSupport.setOptions(benchmark, args);
045        ArrayList<String> files = new ArrayList<String>();
046        if (args.length == 0) {
047            files.add("disk-benchmark.dat");
048        } else {
049            files.addAll(Arrays.asList(args));
050        }
051
052        for (String f : files) {
053            try {
054                File file = new File(f);
055                if (file.exists()) {
056                    System.out.println("File " + file + " already exists, will not benchmark.");
057                } else {
058                    System.out.println("Benchmarking: " + file.getCanonicalPath());
059                    Report report = benchmark.benchmark(file);
060                    file.delete();
061                    System.out.println(report.toString());
062                }
063            } catch (Throwable e) {
064                if (benchmark.verbose) {
065                    System.out.println("ERROR:");
066                    e.printStackTrace(System.out);
067                } else {
068                    System.out.println("ERROR: " + e);
069                }
070            }
071        }
072
073    }
074
075    public static class Report {
076
077        public int size;
078
079        public int writes;
080        public long writeDuration;
081
082        public int syncWrites;
083        public long syncWriteDuration;
084
085        public int reads;
086        public long readDuration;
087
088        @Override
089        public String toString() {
090            return "Writes: \n" + "  " + writes + " writes of size " + size + " written in " + (writeDuration / 1000.0) + " seconds.\n" + "  " + getWriteRate()
091                + " writes/second.\n" + "  " + getWriteSizeRate() + " megs/second.\n" + "\n" + "Sync Writes: \n" + "  " + syncWrites + " writes of size "
092                + size + " written in " + (syncWriteDuration / 1000.0) + " seconds.\n" + "  " + getSyncWriteRate() + " writes/second.\n" + "  "
093                + getSyncWriteSizeRate() + " megs/second.\n" + "\n" + "Reads: \n" + "  " + reads + " reads of size " + size + " read in "
094                + (readDuration / 1000.0) + " seconds.\n" + "  " + getReadRate() + " writes/second.\n" + "  " + getReadSizeRate() + " megs/second.\n" + "\n"
095                + "";
096        }
097
098        private float getWriteSizeRate() {
099            float rc = writes;
100            rc *= size;
101            rc /= (1024 * 1024); // put it in megs
102            rc /= (writeDuration / 1000.0); // get rate.
103            return rc;
104        }
105
106        private float getWriteRate() {
107            float rc = writes;
108            rc /= (writeDuration / 1000.0); // get rate.
109            return rc;
110        }
111
112        private float getSyncWriteSizeRate() {
113            float rc = syncWrites;
114            rc *= size;
115            rc /= (1024 * 1024); // put it in megs
116            rc /= (syncWriteDuration / 1000.0); // get rate.
117            return rc;
118        }
119
120        private float getSyncWriteRate() {
121            float rc = syncWrites;
122            rc /= (syncWriteDuration / 1000.0); // get rate.
123            return rc;
124        }
125
126        private float getReadSizeRate() {
127            float rc = reads;
128            rc *= size;
129            rc /= (1024 * 1024); // put it in megs
130            rc /= (readDuration / 1000.0); // get rate.
131            return rc;
132        }
133
134        private float getReadRate() {
135            float rc = reads;
136            rc /= (readDuration / 1000.0); // get rate.
137            return rc;
138        }
139
140        public int getSize() {
141            return size;
142        }
143
144        public void setSize(int size) {
145            this.size = size;
146        }
147
148        public int getWrites() {
149            return writes;
150        }
151
152        public void setWrites(int writes) {
153            this.writes = writes;
154        }
155
156        public long getWriteDuration() {
157            return writeDuration;
158        }
159
160        public void setWriteDuration(long writeDuration) {
161            this.writeDuration = writeDuration;
162        }
163
164        public int getSyncWrites() {
165            return syncWrites;
166        }
167
168        public void setSyncWrites(int syncWrites) {
169            this.syncWrites = syncWrites;
170        }
171
172        public long getSyncWriteDuration() {
173            return syncWriteDuration;
174        }
175
176        public void setSyncWriteDuration(long syncWriteDuration) {
177            this.syncWriteDuration = syncWriteDuration;
178        }
179
180        public int getReads() {
181            return reads;
182        }
183
184        public void setReads(int reads) {
185            this.reads = reads;
186        }
187
188        public long getReadDuration() {
189            return readDuration;
190        }
191
192        public void setReadDuration(long readDuration) {
193            this.readDuration = readDuration;
194        }
195    }
196
197    public Report benchmark(File file) throws Exception {
198        Report rc = new Report();
199
200        // Initialize the block we will be writing to disk.
201        byte[] data = new byte[bs];
202        for (int i = 0; i < data.length; i++) {
203            data[i] = (byte) ('a' + (i % 26));
204        }
205        rc.size = data.length;
206
207        long start;
208        long now;
209        int ioCount;
210
211        try(RecoverableRandomAccessFile raf = new RecoverableRandomAccessFile(file, "rw")) {
212            preallocateDataFile(raf, file.getParentFile());
213            start = System.currentTimeMillis();
214            now = System.currentTimeMillis();
215            ioCount = 0;
216
217            // Figure out how many writes we can do in the sample interval.
218            while (true) {
219                if ((now - start) > sampleInterval) {
220                    break;
221                }
222                raf.seek(0);
223                for (long i = 0; i + data.length < size; i += data.length) {
224                    raf.write(data);
225                    ioCount++;
226                    now = System.currentTimeMillis();
227                    if ((now - start) > sampleInterval) {
228                        break;
229                    }
230                }
231                // Sync to disk so that the we actually write the data to disk..
232                // otherwise OS buffering might not really do the write.
233                raf.getChannel().force(!SKIP_METADATA_UPDATE);
234            }
235            raf.getChannel().force(!SKIP_METADATA_UPDATE);
236        }
237        now = System.currentTimeMillis();
238
239        rc.size = data.length;
240        rc.writes = ioCount;
241        rc.writeDuration = (now - start);
242
243        try(RecoverableRandomAccessFile raf = new RecoverableRandomAccessFile(file, "rw")) {
244            start = System.currentTimeMillis();
245            now = System.currentTimeMillis();
246            ioCount = 0;
247            while (true) {
248                if ((now - start) > sampleInterval) {
249                    break;
250                }
251                for (long i = 0; i + data.length < size; i += data.length) {
252                    raf.seek(i);
253                    raf.write(data);
254                    raf.getChannel().force(!SKIP_METADATA_UPDATE);
255                    ioCount++;
256                    now = System.currentTimeMillis();
257                    if ((now - start) > sampleInterval) {
258                        break;
259                    }
260                }
261            }
262        }
263        now = System.currentTimeMillis();
264        rc.syncWrites = ioCount;
265        rc.syncWriteDuration = (now - start);
266
267        try(RecoverableRandomAccessFile raf = new RecoverableRandomAccessFile(file, "rw")) {
268            start = System.currentTimeMillis();
269            now = System.currentTimeMillis();
270            ioCount = 0;
271            while (true) {
272                if ((now - start) > sampleInterval) {
273                    break;
274                }
275                raf.seek(0);
276                for (long i = 0; i + data.length < size; i += data.length) {
277                    raf.seek(i);
278                    raf.readFully(data);
279                    ioCount++;
280                    now = System.currentTimeMillis();
281                    if ((now - start) > sampleInterval) {
282                        break;
283                    }
284                }
285            }
286        }
287
288        rc.reads = ioCount;
289        rc.readDuration = (now - start);
290        return rc;
291    }
292
293    private void preallocateDataFile(RecoverableRandomAccessFile raf, File location) throws Exception {
294        File tmpFile;
295        if (location != null && location.isDirectory()) {
296            tmpFile = new File(location, "template.dat");
297        }else {
298            tmpFile = new File("template.dat");
299        }
300        if (tmpFile.exists()) {
301            tmpFile.delete();
302        }
303        RandomAccessFile templateFile = new RandomAccessFile(tmpFile, "rw");
304        templateFile.setLength(size);
305        templateFile.getChannel().force(true);
306        templateFile.getChannel().transferTo(0, size, raf.getChannel());
307        templateFile.close();
308        tmpFile.delete();
309    }
310
311    public boolean isVerbose() {
312        return verbose;
313    }
314
315    public void setVerbose(boolean verbose) {
316        this.verbose = verbose;
317    }
318
319    public int getBs() {
320        return bs;
321    }
322
323    public void setBs(int bs) {
324        this.bs = bs;
325    }
326
327    public long getSize() {
328        return size;
329    }
330
331    public void setSize(long size) {
332        this.size = size;
333    }
334
335    public long getSampleInterval() {
336        return sampleInterval;
337    }
338
339    public void setSampleInterval(long sampleInterval) {
340        this.sampleInterval = sampleInterval;
341    }
342}