Jonnie Disk IO Test
Program Documentation

Introduction

Jonnie is a Disk IO test program written in Java. Its goal is to test the disk sub-systems of computers. While it does produce performance numbers, the primary goals of Jonnie are: The tests performed by Jonnie are:
  1. File Creation Test
  2. Sequential I/O Test
  3. Random I/O Test
The most important thing to understand is that when read operations are performed, the data is compared to the source data. This ensures that if anything happened to corrupt the data (bad RAM, OS Bugs, firmware or driver bugs, etc) those errors will be detected. Media or hardware errors in the disk devices will be caught and reported by the OS.

To maximize the chance of data corruption being detected, each block of data is filled with random data from a pseudo-random number generator (PRNG). On the read cycle, the PRNG is re-seeded and used to generate the block data for comparison.

System Requirements

Running Jonnie

The command to run jonnie is:

        java -server -jar jonnie.jar tests.xml
    
where tests.xml is the XML configuration file that describes how to perform the tests.

It's important to use the -server JVM argument. Testing shows this can double throughput on the Sequential Read test, and improve performance on other tests by up to 25%.

Configuration

Configuration of Jonnie is handled by editing an XML file. You can edit this file, to remove specific tests, add your own custom tests, or configure tests.

Global Configuration Settings

Each test receives all global configuration settings plus all per-test settings. If a test setting is present in both sections, then the per-test setting will override the global configuration setting.

Note that count/size arguments can take power of 2 suffixes (i.e. K,M,G).

If you get ridiculously large throughput values from the tests, try increasing the test file sizes.
TestPath
This is the path where test files are created.
ThreadCount (Global Configuration only)
If this value is not specified, one test thread is created. If you want, you can have multiple test threads running at the same time. This can further stress your computer's I/O sub-system by forcing seeks over a wider portion of the disk.

Keep in mind that if you specify a 16GB test file, and you run 5 threads, then the total disk requirement for the test is 80GB.
IterationCount (Global Configuration only)
This is the number of times that Jonnie should run the configured tests. If this value is negative, then tests will run until the program is terminated.

If ThreadCount is greater than one, then each thread will execute IterationCount tests.
VerifyRead
If this value is true, then as the file is read, the data is compared with the source data. If this value is false, then data will not be checked for accuracy during the read cycle.We STRONGLY urge you to use the default setting of true.
TestCorruption
If this value is set to true, then each test will randomly corrupt the test file. One byte in the test file will be modified. This is a diagnostic facility used to test Jonnie itself to make sure that it can detect a corruption error. In normal use, you would not turn this on.

Individual Test Configuration

com.mhsoftware.jonnie.FileCreationTest

The system creates the specified number of files. As each file is created, the specified amount of data is written to the file, and it is then closed. After all files have been created, each file is opened in turn, the data read and verified, and then the file is deleted.
TestPath
See Global Configuration
FileCount
This is the number of files the File Creation Test should create. If you get really high numbers, try increasing the count to larger values.
FileSize
Rather than creating a 0 byte sized file, Jonnie writes actual data to the file. This is the amount of data that should be written. Normal values would be 1k-16k.
VerifyRead
See Global Configuration

com.mhsoftware.jonnie.SequentialIOTest

A new file is created, and filled with randomly generated data. The file is closed, and then re-opened. The test then reads the file, verifying the data blocks.
TestPath
See Global Configuration Settings
File Size
The size of the file to create for testing. This should be around 2-4 times the size of the physical RAM on the test computer. Smaller values will yield totally ridiculous numbers because of OS caching.
BlockSize
This is the size of the block used for disk input/output. Where possible, a direct I/O buffer is allocated. Normal values would be 32k-64k.
VerifyRead
See Global Configuration - VerifyRead

com.mhsoftware.jonnie.RandomIOTest

A new file of the specified size is created, and filled with randomly generated

The program then does BlockCount random writes to the file, filling each block with more randomly generated data.

After the write cycle has completed, the program does BlockCount random reads from the file.

The location of the read/writes is calculated using the java.util.Random class. Using a pseudo-random number generator will generally force a seek to a new location in the file, and if the source file is large enough, disable the operating systems cache of the file.

After the random read cycle is completed, the areas of the file not written to during the random write cycle are verified to ensure the data has not been overwritten.

This test mimics the behavior of a database that is being randomly read/updated. Note that this is pretty much worst case scenario for a database. Most database applications will have a few tables that are "hot" where most reads and writes are made, so this test will report the worst possible throughput.
TestPath
See Global Configuration Settings
File Size
The size of the file to use for testing. It should be 2-4 times the size of the computer's physical RAM. If you get ridiculously large numbers, you should increase this value.
BlockSize
This is the size of the block of data to use for read-write operations. Normal values would be 16k-64k.
BlockCount
This is the number of blocks to read and write as part of the test.
SyncOnWrite
Setting this argument will open the file with a setting to force data to be flushed to the operating system on write. See the mode argument for java.io.RandomAccessFile's constructor.

If you don't have this set, you can get artificially larger numbers because of write caching by the OS. On the other hand, this setting this value to false can demonstrate the effectiveness of the OS/driver write coalesce/elevator routines.
VerifyRead
See Global Configuration - VerifyRead

Frequently Asked Questions

Why don't you just use bonnie or bonnie++
While these are fine programs, they lack one element that I consider essential. They don't verify the integrity of the data read/write process.

I had a server that had a very funny bug in the disk sub-system/motherboard. The file system on the computer would become corrupted. The more heavily used the computer was, the faster it would corrupt. Initially, the corruption only appeared every couple of weeks. Under heavy use the file system was getting corrupted every day.

During the whole troubleshooting process, I repeatedly did system burn-in using bonnie++ which never reported an error.

After a lot of trouble-shooting, the manufacturer replaced the motherboard with an updated revision and the problem was solved.

The conclusion I finally came to was that if the disk I/O testing had done a verification of the data it would have detected the data corruption issue. I would never have placed the defective machine in production, and I would have saved myself a TON of time.
Why don't you just use badblocks with -wv?
Badblocks is designed to detect media defects. It sequentially writes then reads set patterns to the disk.

  • Sequential I/O does not generate the same sort of disk and power system loading that a series of random read/write operations does. It's just not a representative test.

    In my case the data corruption issue was only evident when a heavy load was present.

    I have seen several other instances where hardware raid systems would fail under heavy loads because of power supply inadequacies.
  • Badblocks is rarely run using the actual production OS/drivers because it's a destructive test. This means that when badblocks is run it's being done from a live boot CD like Knoppix. This means you're using a different kernel/os/driver version for performing your testing than you'll use in production.
  • Badblocks is meaningless in a RAID environment.


In short, if you want to use badblocks to check the media for errors, that's probably OK. For anything else it's a waste of time.
Why are you using Java?
The reasons I use Java are:
  1. because I like it
  2. It is portable. I can use the software on the different operating systems I support like MacOS, Linux, and Windows.
  3. It's dead easy to write multi-threaded code in.
  4. It's easy to localize. If there's interest, we'll provide a method to allow easy translation of messages. Numeric output is already formatted according to the locale of the machine.
Isn't Java too slow to perform these kinds of tests?
I tested bonnie++ on a machine using a hardware RAID controller, and then I tested jonnie. bonnie++ reported block level I/O rates of 79MB/ Second while jonnie reported block level I/O rates of 75MB/Second. Setting the thread count to 2 for testing yielded a combined I/O rate of 79MB/Second from Jonnie. So, it appears that running single-threaded Jonnie is about 6% slower than bonnie++. Running two threads brings the I/O rate to virtually the same value.
Are there any drawbacks compared to bonnie/bonnie++?
The drawbacks that I see are:
  1. bonnie/bonnie++ produce CSV output that's easy to import into a charting program.
  2. They produce CPU utilization numbers. Because Jonnie uses a PRNG to fill each output block with randomly generated data, you're not going to be able to get meaningful CPU utilization/Disk IO numbers from it.
Again, Jonnie isn't so much focused on measuring performance as making sure that the whole disk sub-system is working correctly.
Can I write my own tests?
Yes. In a nutshell, you have to sub-class the com.mhsoftware.jonnie.JonnieTest class, and implement the run() method. Your methods can use the JonnieTest.getProperty() method to retrieve configuration values.

If you want to integrate testing your class into Jonnie, you should call the testCorruption() method, and if it returns true introduce some corruption at some point into your test file.

If your test detects corruption in the read data cycle, then you should call reportException() with your Exception object.

Once you've created your class, and compiled it, you can add it to the test cycle by putting it in the tests.xml file.

The only final thing to remember is that you'll have to add the class path for your new test to the command line when you start Jonnie.

Licensing

This program is licensed under the terms of the GNU GPL V3. Refer to the accompanying LICENSE file, or the GNU web site for additional information.

Copyright 2008, MH Software, Inc. All Rights Reserved.