Fio Grundlagen

Aus Thomas-Krenn-Wiki
Zur Navigation springen Zur Suche springen

Fio ist ein Acronym für Flexible IO Tester und bezeichnet ein Werkzeug zum Messen von IO-Performance. Mit Fio können Devices wie Festplatten oder SSDs auf ihre Geschwindigkeit getestet werden, indem ein vom User definierter Workload ausgeführt und Performance-Daten gesammelt werden. Der folgende Artikel zeigt Antworten auf die wichtigsten Fragen, die vor einem Performance-Test beantwortet werden müssen, und deren Zusammenhang mit Fio-Parametern.

Testen eines ganzen Devices oder mit einzelnen Dateien?

Diese Frage wird über den Parameter "--filename" beantwortet. Für diesen Parameter kann entweder ein ganzes Block Device eingetragen oder ein Dateiname spezifiziert werden.

Achtung: Beim Verwenden eines Block Devices wird das ganze Device überschrieben!

Wird der Parameter nicht verwendet, so legt Fio basierend auf dem Parameter "--name" die Test-Dateien an. Die Angabe der Größe der Datei ist in diesem Fall verpflichtend, bei der Verwendung eines Block-Devices wird ohne "--size" das ganze Device verwendet.

$ fio --rw=write --name=test --size=10M
[...]
$ ls -sh
total 10M
10M test.1.0
$ fio --rw=write --name=test --size=10M --filename=file1
[...]
$ ls -sh
total 10M
10M file1

Wie groß sollen die Test-Dateien werden?

Der Parameter "--size" gibt die Größe der Dateien an. Wird mittels "--filename" ein ganzes Block-Device eingesetzt, wird ohne "--size" das ganze Device verwendet.

$ fio --rw=write --name=test --size=20M
[...]
$ ls -sh
total 20M
20M test.1.0

Verwendung des Arbeitsspeichers (v.a. Page Cache unter Linux) oder Einsatz von Direct I/O?

Mit dem Einsatz von "--direct=1" wird die Verwendung von Direct_und_synchronized_I/O_unter_Linux#O_DIRECT Direct I/O aktiviert. D.h. vor allem, dass der Page Cache umgangen wird und daher nicht mehr der Arbeitsspeicher verwendet wird.

Für den Einsatz von asynchronen Zugriffen (z.B. über die Bibliothek libaio) ist "--direct" eine Voraussetzung, da der Page Cache nicht asynchron angesprochen werden kann.

 
$ fio --rw=write --name=test --size=20M --direct=1
[...]
Run status group 0 (all jobs):
  WRITE: io=20480KB, aggrb=28563KB/s

Ohne "--direct" wird die Geschwindigkeit des Arbeitsspeichers gemessen:

$ fio --rw=write --name=test --size=20M
[...]
Run status group 0 (all jobs):
  WRITE: io=20480KB, aggrb=930909KB/s

Welche Blockgröße soll eingesetzt werden?

Standardmäßig werden 4 KB als Blockgröße eingesetzt. Dem Parameter "--bs" kann die gewünschte Blockgröße übergeben werden:

$ fio --rw=write --name=test --size=20M --direct=1
test: (g=0): rw=write, bs=4K-4K/4K-4K
[...]
$ fio --rw=write --name=test --size=20M --direct=1 --bs=1024k
test: (g=0): rw=write, bs=1M-1M/1M-1M, ioengine=sync, iodepth=1

Sequentielle oder Zufällige Zugriffe?

Der Parameter "--rw" entscheidet darüber, ob die IO-Zugriffe sequentiell oder zufällig abgesetzt werden. Hat man sich für eine Variante entschieden ist es auch möglich einen gemischten Workload zu definieren, bei dem z.B. 50% lesend und 50% schreibend erfolgen. Folgende Optionen sind für "--rw" verfügbar:

  • read - Sequentielles Lesen
  • write - Sequentielles Schreiben
  • randread - Zufälliges Lesen
  • randwrite - Zufälliges Schreiben
  • readwrite,rw - Gemischter, sequentieller Workload
  • randrw - Gemischter, zufälliger Workload

Standardmäßig wird bei einem gemischten Workload der Prozentanteil für Lesen und Schreiben auf 50%/50% gesetzt. Um eine andere Verteilung zu erzielen kann zusätzlich der Parameter "--rwmixread" oder "--rwmixwrite" angegeben werden.

$ fio --rw=readwrite --name=test --size=20M --direct=1 --bs=1024k
[...]
  read : io=11264KB, bw=54154KB/s, iops=52 , runt=   208msec
[...]
  write: io=9216.0KB, bw=44308KB/s, iops=43 , runt=   208msec
[...]
Disk stats (read/write):
  sda: ios=14/12, merge=0/0, ticks=156/76, in_queue=232, util=54.55%

Mit einer 80% zu 20% Aufteilung wird folgendes Ergebnis erzielt:

$ fio --rw=readwrite --name=test --size=20M --direct=1 --bs=1024k --rwmixread=80
[...]
  read : io=17408KB, bw=83292KB/s, iops=81 , runt=   209msec
[...]
  write: io=3072.0KB, bw=14699KB/s, iops=14 , runt=   209msec
[...]
Disk stats (read/write):
  sda: ios=24/3, merge=0/0, ticks=192/16, in_queue=216, util=56.67%

Welche Art von Daten setzt Fio ein?

Fio verwendet grundsätzlich zufällige Daten. Um den Aufwand der Generierung der zufälligen Daten etwas zu mindern, wird zu Beginn ein Puffer von zufälligen Daten erstellt, auf den während des Tests laufend zurückgegriffen wird. Zumeist sollen sich aber auch diese zufälligen Daten komprimieren lassen.[1]

Diese Wiederverwendung des Puffers führt bei aktuellen SSDs (z.B. der Intel 520 Series SSDs) tatsächlich dazu, dass der SSD-Controller Daten komprimieren kann. Performance gleicht dann nahezu jener, wenn lauter Nullen als Daten verwendet werden - z.B. mittels Option "--zero_buffers".

Um diesen SSD Kompressions-Effekt zu umgehen, kann Fio mit "--refill_buffers" angewiesen werden, den Puffer bei jedem IO-Submit neu zu befüllen:[2]

refill_buffers If this option is given, fio will refill the IO buffers on every submit.

Dazu ein Beispiel zur Demonstration des oben genannten Effekts mit der Intel 520 Series SSDs.

  • Standardmäßig können Daten komprimiert werden. Vgl. dazu die Spezifikationen in [1] (intel.com):
    • Compressible Performance - Sequential Write Sata 6Gb/s: 520 MB/s
    • Incompressible Performance - Sequential Write (up to): 235 MB/s
/usr/local/bin/fio --rw=write --name=intel520-run0 --bs=128k --direct=1 --filename=/dev/sdb --numjobs=1 --ioengine=libaio --iodepth=32
intel520-run0: (g=0): rw=write, bs=128K-128K/128K-128K, ioengine=libaio, iodepth=32
Jobs: 1 (f=1): [W] [100.0% done] [0K/475.7M /s] [0 /3629  iops] [eta 00m:00s]
  write: io=228937MB, bw=394254KB/s, iops=3080 , runt=594619msec

Mit "--zero_buffers" bleibt die Performance nahezu gleich:

/usr/local/bin/fio --rw=write --name=intel520-run0 --bs=128k --direct=1 --filename=/dev/sdb --numjobs=1 --ioengine=libaio --iodepth=32 --zero_buffers
intel520-run0: (g=0): rw=write, bs=128K-128K/128K-128K, ioengine=libaio, iodepth=32
Jobs: 1 (f=1): [W] [100.0% done] [0K/490.4M /s] [0 /3741  iops] [eta 00m:00s]
intel520-run0: (groupid=0, jobs=1): err= 0: pid=13274
  write: io=228937MB, bw=401393KB/s, iops=3135 , runt=584044msec

Mit der Option "--refill-buffers" wird die Incompressible Performance erreicht:

/usr/local/bin/fio --rw=write --name=intel520-run0 --bs=128k --direct=1 --filename=/dev/sdb --numjobs=1 --ioengine=libaio --iodepth=32 --refill_buffers
Jobs: 1 (f=1): [W] [100.0% done] [0K/242.8M /s] [0 /1852  iops] [eta 00m:00s]
intel520-run0: (groupid=0, jobs=1): err= 0: pid=13289
  write: io=228937MB, bw=203629KB/s, iops=1590 , runt=1151267msec

Wie können parallele Zugriffe durchgeführt werden?

Parallele Zugriffe können über unterschiedliche Wege realisiert werden. Zum einen können mehrere Prozesse gestartet werden, die parallel Jobs ausführen ("--numjobs"), zum anderen kann mittels asynchroner I/O-Engine die I/O-Tiefe erhöht werden. Vor allem für SSDs bringen parallele I/O-Requests einen Performance-Zuwachs, da SSDs intern mehrere Flash-Channels für die Abarbeitung besitzen (siehe Intel SSDs im Überblick).

  • numjobs

Spezifiziert die Anzahl an Prozessen, die jeweils den definierten Workload erzeugen (Standardwert 1). Werte > 1 erzeugen für numbjobs=n, n parallele Prozesse, die den gleichen Workload/Job ausführen. Vor allem der Parameter "--size" muss dabei ins Auge gefasst werden, da z.B. mit numjobs=4, 4 * size an Speicherplatz benötigt wird.

  • group_reporting

Wenn aktiviert, erzeugt diese Option einen Gruppenbericht bei Tests mit numjobs > 1 (anstelle von einzelnen Job Reports).

$ fio --rw=readwrite --name=test --size=50M --direct=1 --bs=1024k --numjobs=2
test: (g=0): rw=rw, bs=1M-1M/1M-1M, ioengine=sync, iodepth=1
test: (g=0): rw=rw, bs=1M-1M/1M-1M, ioengine=sync, iodepth=1
fio-2.0.8-9-gfb9f0
Starting 2 processes
[...]
test: (groupid=0, jobs=1): err= 0: pid=25753: Thu Aug 30 10:40:30 2012
[...]
test: (groupid=0, jobs=1): err= 0: pid=25754: Thu Aug 30 10:40:30 2012
[...]
$ ls -sh
total 100M
50M test.1.0  50M test.2.0

Mit "--group_reporting" werden die Statistiken zusammengefasst:

$ fio --rw=readwrite --name=test --size=50M --direct=1 --bs=1024k --numjobs=2 --group_reporting
test: (g=0): rw=rw, bs=1M-1M/1M-1M, ioengine=sync, iodepth=1
test: (g=0): rw=rw, bs=1M-1M/1M-1M, ioengine=sync, iodepth=1
fio-2.0.8-9-gfb9f0
Starting 2 processes
Jobs: 2 (f=2)
test: (groupid=0, jobs=2): err= 0: pid=25773: Thu Aug 30 10:43:00 2012
  read : io=56320KB, bw=41020KB/s, iops=40 , runt=  1373msec
[...]
$ ls -sh
total 100M
50M test.1.0  50M test.2.0

libaio und iodepth

libaio[3] ermöglicht asynchrone Zugriffe von Applikations-Ebene aus. Damit können parallel I/O-Requests abgesetzt werden. Über den Parameter "--iodepth" kann die Anzahl an Requests angegeben werden. "--libaio" verlangt zusätzlich die Option "--direct=1" da der Page Cache unter Linux nicht asynchron angesprochen werden kann:[4][5]

Hierzu ein Beispiel, bei dem als IO-Tiefe 16 verwendet wird:

$ fio --rw=readwrite --name=test --size=50M --direct=1 --bs=1024k --ioengine=libaio --iodepth=16
test: (g=0): rw=rw, bs=1M-1M/1M-1M, ioengine=libaio, iodepth=16
[...]
 IO depths    : 1=2.0%, 2=4.0%, 4=8.0%, 8=16.0%, 16=70.0%, 32=0.0%, >=64=0.0%
[...]

Wie das Beispiel zeigt, kann nach einem Job-Run die exakte Verteilung der IO-Tiefen überprüft werden. Dies ist insofern von Vorteil, da aufgrund von OS-Restriktionen nicht immer die IO-Tiefe erzwungen werden kann:[2]

Even async engines may impose OS restrictions causing the desired depth not to be achieved.[...] Keep an eye on the IO depth distribution in the fio output to verify that the achieved depth is as expected.

Wie beschränke ich die Laufzeit eines Tests?

Wie lange ein Test laufen soll, kann durch den Parameter "--runtime" angegeben werden. Soll sicher gestellt sein, dass der Test nicht früher als vor der angegebenen Zeit beendet wird, empfiehlt sich der Parameter "--time_based". Mit ihm wird der Workload solange wiederholt, bis die gewünschte Laufzeit erreicht wurde.

$ fio --rw=readwrite --name=test --size=50M --direct=1 --bs=1024k --numjobs=2 --group_reporting --runtime=2
[...]
  read : io=50176KB, bw=36545KB/s, iops=35 , runt=  1373msec
[...]

Die Laufzeit dieses Tests beträgt 1,373 Sekunden, trotz der Angabe von "--runtime". Mit "--time_based" kann die exakte Laufzeit erreicht werden:

$ fio --rw=readwrite --name=test --size=50M --direct=1 --bs=1024k --numjobs=2 --group_reporting --runtime=2 --time_based
[...]
  read : io=77824KB, bw=38718KB/s, iops=37 , runt=  2010msec
[...]

Beispiele

Die folgenden Beispiele zeigen fio Aufrufe für IOPS, Throughput und Latency Tests. Diese einfachen Beispiele führen kein Preconditioning durch, das bei SSDs durchaus relevant ist. Für SSD Tests empfehlen wir daher den Einsatz von TKperf.

Optional Preconditioning

Vollständiges Beschreiben des zu testenden Geräts (Achtung: Dabei gehen alle Daten am Gerät verloren).

fio --rw=write --name=IOPS-write --bs=128k --direct=1 --filename=/dev/TESTDEVICE --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting  

IOPS Test

Schreibtest:

fio --rw=randwrite --name=IOPS-write --bs=4k --direct=1 --filename=/dev/TESTDEVICE --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based 

Lesetest:

fio --rw=randread --name=IOPS-read --bs=4k --direct=1 --filename=/dev/TESTDEVICE --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based 

Throughput Test

Schreibtest:

fio --rw=write --name=IOPS-write --bs=1024k --direct=1 --filename=/dev/TESTDEVICE --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based 

Lesetest:

fio --rw=read --name=IOPS-read --bs=1024k --direct=1 --filename=/dev/TESTDEVICE --numjobs=4 --ioengine=libaio --iodepth=32 --refill_buffers --group_reporting --runtime=60 --time_based 

Latency Test

Schreibtest:

fio --rw=randwrite --name=IOPS-write --bs=4k --direct=1 --filename=/dev/TESTDEVICE --numjobs=1 --ioengine=libaio --iodepth=1 --refill_buffers --group_reporting --runtime=60 --time_based 

Lesetest:

fio --rw=randread --name=IOPS-read --bs=4k --direct=1 --filename=/dev/TESTDEVICE --numjobs=1 --ioengine=libaio --iodepth=1 --refill_buffers --group_reporting --runtime=60 --time_based

Einzelnachweise

  1. Re: Does fio write only 0x00s? (spinics.net)
  2. 2,0 2,1 Fio HOWTO (git.kernel.dk)
  3. Kernel Asynchronous I/O (lse.sourceforge.net)
  4. fio man page Note that increasing iodepth beyond 1 will not affect synchronous ioengines (except for small degress when verify_async is in use). Even async engines my impose OS restrictions causing the desired depth not to be achieved. This may happen on Linux when using libaio and not setting direct=1, since buffered IO is not async on that OS.
  5. Measuring IOPS (spinics.net)


Foto Georg Schönberger.jpg

Autor: Georg Schönberger

Georg Schönberger, Abteilung DevOps bei der XORTEX eBusiness GmbH, absolvierte an der FH OÖ am Campus Hagenberg sein Studium zum Bachelor Computer- und Mediensicherheit, Studium Master Sichere Informationssysteme. Seit 2015 ist Georg bei XORTEX beschäftigt und arbeitet sehr lösungsorientiert und hat keine Angst vor schwierigen Aufgaben. Zu seinen Hobbys zählt neben Linux auch Tennis, Klettern und Reisen.


Das könnte Sie auch interessieren

Balkendiagramme aus Fio Logs
Fio Logs mit fio2gnuplot visualisieren
Fio unter Windows nutzen