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