Last active 5 days ago

Hard drive speed test using fio or dd

Revision 29786f90af6e9e82b8e5c6671a3c458556aab289

speedtest-hd.sh Raw
1#!/usr/bin/env bash
2
3# Robust HD/SDD/NVMe performance CLI utility
4# Utilizing FIO for sequential/random writes/writes
5# Dependencies: fio (apt install fio)
6# See: https://cloud.google.com/compute/docs/disks/benchmarking-pd-performance
7# See: https://arstechnica.com/gadgets/2020/02/how-fast-are-your-disks-find-out-the-open-source-way-with-fio/
8# mReschke 2024-01-18
9
10# CLI Parameters
11path="$1"
12option="$2"
13
14# Main application flow
15function main {
16
17 # Show usage if no params
18 if [ ! "$path" ]; then
19 usage
20 fi
21
22 # Understand . path
23 if [ "$path" == '.' ]; then
24 path=$(pwd)
25 fi
26
27 # Check if path exists
28 if [ ! -e "$path" ]; then
29 echo "Path $path does not exist"
30 exit 1
31 fi
32
33 # Must type y or n THEN press enter (which I like better)
34 echo "NOTICE: 1GB free space on '$path' is required to perform the benchmark."
35 echo -n "Are you ready to start a robust IO benchmark against '$path' ?"; read answer
36 if [ "$answer" != "${answer#[Yy]}" ]; then
37 echo "Great! Starting benchmark now!";
38 else
39 echo "Ok, cancelled!"
40 exit 0
41 fi
42
43 # Use dd of fio based on param or defaults
44 if [ "$option" == "--dd" ]; then
45 dd_speedtest
46 elif [ "$option" == "--fio" ]; then
47 fio_speedtest
48 elif [ "$option" == "" ]; then
49 # If fio is installed, use it, else use dd
50 echo ""
51 if ! command -v fio &> /dev/null; then
52 dd_speedtest
53 else
54 fio_speedtest
55 fi
56 fi
57}
58
59function fio_write_single_random_4k {
60 # Single 4k Random Writes
61
62 # This is a single process doing random 4K writes. This is where the pain
63 # really, really lives; it's basically the worst possible thing you can ask a
64 # disk to do. Where this happens most frequently in real life: copying home
65 # directories and dotfiles, manipulating email stuff, some database operations,
66 # source code trees.
67
68 # When I ran this test against the high-performance SSDs in my Ubuntu
69 # workstation, they pushed 127MiB/sec. The server just beneath it in the rack
70 # only managed 33MiB/sec on its "high-performance" 7200RPM rust disks... but
71 # even then, the vast majority of that speed is because the data is being
72 # written asynchronously, allowing the operating system to batch it up into
73 # larger, more efficient write operations.
74
75 # If we add the argument --fsync=1, forcing the operating system to perform
76 # synchronous writes (calling fsync after each block of data is written) the
77 # picture gets much more grim: 2.6MiB/sec on the high-performance SSDs but
78 # only 184KiB/sec on the "high-performance" rust. The SSDs were about four
79 # times faster than the rust when data was written asynchronously but a
80 # whopping fourteen times faster when
81
82 # --name= is a required argument, but it's basically human-friendly fluff—fio will create files based on that name to test with, inside the working directory you're currently in.
83 # --ioengine=posixaio sets the mode fio interacts with the filesystem. POSIX is a standard Windows, Macs, Linux, and BSD all understand, so it's great for portability—although inside fio itself, Windows users need to invoke --ioengine=windowsaio, not --ioengine=posixaio, unfortunately. AIO stands for Asynchronous Input Output and means that we can queue up multiple operations to be completed in whatever order the OS decides to complete them. (In this particular example, later arguments effectively nullify this.)
84 # --rw=randwrite means exactly what it looks like it means: we're going to do random write operations to our test files in the current working directory. Other options include seqread, seqwrite, randread, and randrw, all of which should hopefully be fairly self-explanatory.
85 # --bs=4k blocksize 4K. These are very small individual operations. This is where the pain lives; it's hard on the disk, and it also means a ton of extra overhead in the SATA, USB, SAS, SMB, or whatever other command channel lies between us and the disks, since a separate operation has to be commanded for each 4K of data.
86 # --size=1g our test file(s) will be 1GB in size apiece. (We're only creating one, see next argument.)
87 # --numjobs=1 we're only creating a single file, and running a single process commanding operations within that file. If we wanted to simulate multiple parallel processes, we'd do, eg, --numjobs=16, which would create 16 separate test files of --size size, and 16 separate processes operating on them at the same time.
88 # --iodepth=1 this is how deep we're willing to try to stack commands in the OS's queue. Since we set this to 1, this is effectively pretty much the same thing as the sync IO engine—we're only asking for a single operation at a time, and the OS has to acknowledge receipt of every operation we ask for before we can ask for another. (It does not have to satisfy the request itself before we ask it to do more operations, it just has to acknowledge that we actually asked for it.)
89 # --runtime=15 --time_based Run and even if we complete sooner, just start over again and keep going until 60 seconds is up.
90 # --end_fsync=1 After all operations have been queued, keep the timer going until the OS reports that the very last one of them has been successfully completed—ie, actually written to disk.
91 echo ""
92 echo "Single 4K Random Writes (size=1G, time=15sec, jobs=1, iodepth=1)"
93 x=`sudo fio \
94 --name=fio-write-random-4k \
95 --directory=$path \
96 --ioengine=posixaio \
97 --rw=randwrite \
98 --bs=4k \
99 --size=1g \
100 --numjobs=1 \
101 --iodepth=1 \
102 --time_based --runtime=15 \
103 --end_fsync=1`
104 echo " - $x " | /usr/bin/grep -A1 'Run status group' | tail -n1
105
106 # Cleanup my test files
107 rm -rf $path/fio-write-random-4k*
108}
109
110function fio_write_parallel_random_64k {
111 # Parallel 64k Random Writes
112
113 # This time, we're creating 16 separate 64MB files (still totaling 1GB, when
114 # all put together) and we're issuing 64KB blocksized random write operations.
115 # We're doing it with sixteen separate processes running in parallel, and
116 # we're queuing up to 16 simultaneous asynchronous ops before we pause and wait
117 # for the OS to start acknowledging their receipt.
118
119 # This is a pretty decent approximation of a significantly busy system. It's
120 # not doing any one particularly nasty thing—like running a database engine or
121 # copying tons of dotfiles from a user's home directory—but it is coping with
122 # a bunch of applications doing moderately demanding stuff all at once.
123
124 # This is also a pretty good, slightly pessimistic approximation of a busy,
125 # multi-user system like a NAS, which needs to handle multiple 1MB operations
126 # simultaneously for different users. If several people or processes are trying
127 # to read or write big files (photos, movies, whatever) at once, the OS tries
128 # to feed them all data simultaneously. This pretty quickly devolves down to a
129 # pattern of multiple random small block access. So in addition to "busy desktop
130 # with lots of apps," think "busy fileserver with several people actively using it."
131
132 # You will see a lot more variation in speed as you watch this operation play
133 # out on the console. For example, the 4K single process test we tried first
134 # wrote a pretty consistent 11MiB/sec on my MacBook Air's internal drive—but
135 # this 16-process job fluctuated between about 10MiB/sec and 300MiB/sec during
136 # the run, finishing with an average of 126MiB/sec.
137
138 # Most of the variation you're seeing here is due to the operating system and
139 # SSD firmware sometimes being able to aggregate multiple writes. When it
140 # manages to aggregate them helpfully, it can write them in a way that allows
141 # parallel writes to all the individual physical media stripes inside the SSD.
142 # Sometimes, it still ends up having to give up and write to only a single
143 # physical media stripe at a time—or a garbage collection or other maintenance
144 # operation at the SSD firmware level needs to run briefly in the background,
145 # slowing things down.
146 echo ""
147 echo "Parallel 64K Random Writes (size=1G, time=15sec, jobs=16, iodepth=16)"
148 x=`sudo fio \
149 --name=fio-write-random-64k \
150 --directory=$path \
151 --ioengine=posixaio \
152 --rw=randwrite \
153 --bs=64k \
154 --size=64m \
155 --numjobs=16 \
156 --iodepth=16 \
157 --time_based --runtime=15 \
158 --end_fsync=1`
159 echo " - $x " | /usr/bin/grep -A1 'Run status group' | tail -n1
160
161 # Cleanup my test files
162 rm -rf $path/fio-write-random-64k*
163}
164
165function fio_write_single_sequential_1m {
166 # Single 1M Random Writes
167
168 # This is pretty close to the best-case scenario for a real-world system
169 # doing real-world things. No, it's not quite as fast as a single, truly
170 # contiguous write... but the 1MiB blocksize is large enough that it's quite
171 # close. Besides, if literally any other disk activity is requested simultaneously
172 # with a contiguous write, the "contiguous" write devolves to this level of
173 # performance pretty much instantly, so this is a much more realistic test of
174 # the upper end of storage performance on a typical system.
175
176 # You'll see some kooky fluctuations on SSDs when doing this test. This is largely
177 # due to the SSD's firmware having better luck or worse luck at any given time,
178 # when it's trying to queue operations so that it can write across all physical
179 # media stripes cleanly at once. Rust disks will tend to provide a much more
180 # consistent, though typically lower, throughput across the run.
181
182 # You can also see SSD performance fall off a cliff here if you exhaust an
183 # onboard write cache—TLC and QLC drives tend to have small write cache areas
184 # made of much faster MLC or SLC media. Once those get exhausted, the disk has
185 # to drop to writing directly to the much slower TLC/QLC media where the data
186 # eventually lands. This is the major difference between, for example, Samsung
187 # EVO and Pro SSDs—the EVOs have slow TLC media with a fast MLC cache, where
188 # the Pros use the higher-performance, higher-longevity MLC media throughout
189 # the entire SSD.
190
191 # If you have any doubt at all about a TLC or QLC disk's ability to sustain
192 # heavy writes, you may want to experimentally extend your time duration here.
193 # If you watch the throughput live as the job progresses, you'll see the impact
194 # immediately when you run out of cache—what had been a fairly steady,
195 # several-hundred-MiB/sec throughput will suddenly plummet to half the speed
196 # or less and get considerably less stable as well.
197
198 # However, you might choose to take the opposite position—you might not
199 # expect to do sustained heavy writes very frequently, in which case you
200 # actually are more interested in the on-cache behavior. What's important
201 # here is that you understand both what you want to test, and how to test
202 # it accurately.
203
204 echo ""
205 echo "Single 1M Sequential Writes (size=1G, time=15sec, jobs=1, iodepth=1)"
206 x=`sudo fio \
207 --name=fio-write-random-1m \
208 --directory=$path \
209 --ioengine=posixaio \
210 --rw=write \
211 --bs=1m \
212 --size=1g \
213 --numjobs=1 \
214 --iodepth=1 \
215 --time_based --runtime=15 \
216 --end_fsync=1`
217 echo " - $x " | /usr/bin/grep -A1 'Run status group' | tail -n1
218
219 # Cleanup my test files
220 rm -rf $path/fio-write-random-1m*
221}
222
223function fio_read_sequential_1m {
224 # Sequential Parallel Reads
225
226 echo ""
227 echo "Sequential 4x 1M Reads"
228 x=`sudo fio \
229 --name=fio-read-sequential-1m \
230 --directory=$path \
231 --ioengine=posixaio \
232 --bs=1M \
233 --numjobs=4 \
234 --size=256M \
235 --time_based --runtime=30s \
236 --ramp_time=2s \
237 --direct=1 \
238 --verify=0 \
239 --iodepth=64 \
240 --rw=read \
241 --group_reporting=1 \
242 --iodepth_batch_submit=64 \
243 --iodepth_batch_complete_max=64`
244 echo " - $x " | /usr/bin/grep -A1 'Run status group' | tail -n1
245 rm -rf $path/fio-read-sequential-1m*
246}
247
248function fio_read_random_4k {
249 # Random 4k Reads
250
251 echo ""
252 echo "Random 4k Reads"
253 x=`sudo fio \
254 --name=fio-read-random-4k \
255 --directory=$path \
256 --ioengine=posixaio \
257 --rw=randread \
258 --bs=4k \
259 --size=1g \
260 --time_based --runtime=30s \
261 --ramp_time=2s \
262 --direct=1 \
263 --verify=0 \
264 --iodepth=256 \
265 --rw=read \
266 --group_reporting=1 \
267 --iodepth_batch_submit=256 \
268 --iodepth_batch_complete_max=256`
269 echo " - $x " | /usr/bin/grep -A1 'Run status group' | tail -n1
270 rm -rf $path/fio-read-random-4k*
271}
272
273function fio_speedtest {
274 # Write tests
275 fio_write_single_random_4k
276 fio_write_parallel_random_64k
277 fio_write_single_sequential_1m
278
279 # Read Tests
280 fio_read_sequential_1m
281 fio_read_random_4k
282}
283
284function dd_speedtest {
285 # Basic HD speed test using DD
286 # mReschke 2017-07-11
287
288 file=$path/bigfile
289 size=1024
290
291 echo "Running dd based HD/SSD/NVMe Benchmarks"
292 echo "---------------------------------------"
293
294 printf "Cached write speed...\n"
295 dd if=/dev/zero of=$file bs=1M count=$size
296
297 printf "\nUncached write speed...\n"
298 dd if=/dev/zero of=$file bs=1M count=$size conv=fdatasync,notrunc
299
300 printf "\nUncached read speed...\n"
301 echo 3 | sudo tee /proc/sys/vm/drop_caches > /dev/null
302 dd if=$file of=/dev/null bs=1M count=$size
303
304 printf "\nCached read speed...\n"
305 dd if=$file of=/dev/null bs=1M count=$size
306
307 rm $file
308 printf "\nDone\n"
309}
310
311# Show help and usage information
312function usage {
313 echo "Robust Flexible Input/Output HD Speedtest"
314 echo " If FIO is installed, we use FIO for more detailed performance analysis."
315 echo " If FIO is not installed, we use basic DD analysis."
316 echo " You should apt install fio (pacman -S fio) for detailed analysis."
317 echo "mReschke 2024-01-18"
318 echo ""
319 echo "NOTICE, this creates a 1GB file on the desired destination disk."
320 echo "Please ensure you have write access with 1GB free space on destination."
321 echo ""
322 echo "Usage:"
323 echo " This will use FIO if installed, else DD"
324 echo " ./speedtest-hd /mnt/somedisk"
325 echo " ./speedtest-hd ."
326 echo ""
327 echo " This will force FIO"
328 echo " ./speedtest-hd /mnt/somedisk --fio"
329 echo " ./speedtest-hd . --fio"
330 echo ""
331 echo " This will force DD"
332 echo " ./speedtest-hd /mnt/somedisk --dd"
333 echo " ./speedtest-hd . --dd"
334 exit 0
335}
336
337# Go
338main
339